Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions lib/csscss/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def initialize(argv)
@ignored_selectors = []
@match_shorthand = true
@ignore_sass_mixins = false
@find_duplicates = false
end

def run
Expand Down Expand Up @@ -37,17 +38,19 @@ def execute
end.join("\n")

unless all_contents.strip.empty?
redundancies = RedundancyAnalyzer.new(all_contents).redundancies(
analyzer = RedundancyAnalyzer.new(all_contents)
redundancies = analyzer.redundancies(
minimum: @minimum,
ignored_properties: @ignored_properties,
ignored_selectors: @ignored_selectors,
match_shorthand: @match_shorthand
match_shorthand: @match_shorthand,
duplicates: @find_duplicates
)

if @json
puts JSONReporter.new(redundancies).report
else
report = Reporter.new(redundancies).report(verbose:@verbose, color:@color)
report = Reporter.new(redundancies).report(verbose:@verbose, color:@color, duplicates: analyzer.duplicates)
puts report unless report.empty?
end
end
Expand All @@ -72,6 +75,10 @@ def parse(argv)
@verbose = v
end

opts.on("-d", "--[no-]duplicates", "Display Identical rules") do |d|
@find_duplicates = d
end

opts.on("--[no-]color", "Colorize output", "(default is #{@color})") do |c|
@color = c
end
Expand Down
29 changes: 29 additions & 0 deletions lib/csscss/redundancy_analyzer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def redundancies(opts = {})
ignored_properties = opts[:ignored_properties] || []
ignored_selectors = opts[:ignored_selectors] || []
match_shorthand = opts.fetch(:match_shorthand, true)
find_duplicates = opts.fetch(:duplicates, false)

rule_sets = Parser::Css.parse(@raw_css)
matches = {}
Expand Down Expand Up @@ -55,6 +56,8 @@ def redundancies(opts = {})
end
end

@duplicates = find_all_duplicates(matches) if find_duplicates && matches

inverted_matches = {}
matches.each do |declaration, selector_groups|
if selector_groups.size > 1
Expand Down Expand Up @@ -119,7 +122,33 @@ def redundancies(opts = {})
end
end

def duplicates(opts = {})
redundancies(opts) unless @duplicates
@duplicates
end

private
def find_all_duplicates(matches)
# get the full selectors key declaration value hash
inverted = {}
duplicates = []
matches.each do |declaration, selector_group|
selector_group.each do |selector|
inverted[selector] ||= [] # at most empty
inverted[selector] << declaration unless inverted[selector].include?(declaration) # no duplicate values
end
end
inverted_copy = inverted
inverted.each do |key, value|
#remove the key value from inverted copy
inverted_copy.delete(key)
#if it still contains the value, log as duplicate
copy = inverted_copy.key(value)
duplicates << [copy, key, value] if copy
end
duplicates
end

def shorthand_parser(property)
case property
when "background" then Parser::Background
Expand Down
5 changes: 5 additions & 0 deletions lib/csscss/reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def initialize(redundancies)
def report(options = {})
verbose = options.fetch(:verbose, false)
should_color = options.fetch(:color, true)
duplicates = options.fetch(:duplicates, [])

io = StringIO.new
@redundancies.each do |selector_groups, declarations|
Expand All @@ -19,6 +20,10 @@ def report(options = {})
end
end

duplicates.each do |duplicate|
io.puts "[#{maybe_color(duplicate[0], :red, should_color)} AND #{maybe_color(duplicate[1], :red, should_color)}] are identical selectors that share #{maybe_color(duplicate[2].size, :red, should_color)} attributes"
end

io.rewind
io.read
end
Expand Down
35 changes: 35 additions & 0 deletions test/csscss/redundancy_analyzer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,41 @@ module Csscss
})
end

it "correctly finds duplicates when duplicates flag is given" do
css = %$
h1, h2 { display: none; position: relative; outline:none}
.foo { display: none; width: 1px }
.bar { position: relative; width: 1px; outline: none }
.baz { display: none }
.alpha { width: 2px }
.beta { width: 2px }
$
analyzer = RedundancyAnalyzer.new(css)

analyzer.redundancies(duplicates:true).must_equal({
[sel(".bar"), sel("h1, h2")] => [dec("outline", "none"), dec("position", "relative")],
[sel(".bar"), sel(".foo")] => [dec("width", "1px")],
[sel(".baz"), sel(".foo"), sel("h1, h2")] => [dec("display", "none")],
[sel(".alpha"), sel(".beta")] => [dec("width", "2px")]
})

analyzer.duplicates.must_equal([
[sel(".beta"), sel(".alpha"), [dec("width", "2px")]]
])
end

it "correctly finds no duplicates when the flag is given if there are none" do
css = %$
h1, h2 { display: none; position: relative; outline:none}
.foo { display: none; width: 1px }
.bar { position: relative; width: 1px; outline: none }
.baz { display: none }
$
analyzer = RedundancyAnalyzer.new(css)
analyzer.redundancies(duplicates:true)
analyzer.duplicates.must_be_empty
end


# TODO: someday
# it "reports duplication within the same selector" do
Expand Down
12 changes: 12 additions & 0 deletions test/csscss/reporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ module Csscss
reporter.report(verbose:true, color:false).must_equal expected
end

it "prints the regular results and the specific duplicates" do
reporter = Reporter.new({
[sel(".foo"), sel(".bar")] => [dec("width", "1px"), dec("border", "black")]
})

expected =<<-EXPECTED
{.foo} AND {.bar} share 2 rules
[foo AND bar] are identical selectors that share 2 attributes
EXPECTED
reporter.report(color:false, duplicates: [['foo', 'bar', %w('attribute', 'other_attribute')]]).must_equal expected
end

it "prints a new line if there is nothing" do
reporter = Reporter.new({})
reporter.report().must_equal ""
Expand Down