From 66132e12774db8718676dc916d68fdc4cd1f3555 Mon Sep 17 00:00:00 2001 From: Nick Overfield Date: Tue, 1 Apr 2014 10:27:37 -0500 Subject: [PATCH] added -d option flag for printing full duplicates --- lib/csscss/cli.rb | 13 ++++++--- lib/csscss/redundancy_analyzer.rb | 29 ++++++++++++++++++++ lib/csscss/reporter.rb | 5 ++++ test/csscss/redundancy_analyzer_test.rb | 35 +++++++++++++++++++++++++ test/csscss/reporter_test.rb | 12 +++++++++ 5 files changed, 91 insertions(+), 3 deletions(-) diff --git a/lib/csscss/cli.rb b/lib/csscss/cli.rb index 0adec9e..f04a5e2 100644 --- a/lib/csscss/cli.rb +++ b/lib/csscss/cli.rb @@ -10,6 +10,7 @@ def initialize(argv) @ignored_selectors = [] @match_shorthand = true @ignore_sass_mixins = false + @find_duplicates = false end def run @@ -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 @@ -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 diff --git a/lib/csscss/redundancy_analyzer.rb b/lib/csscss/redundancy_analyzer.rb index be9de91..dacc839 100644 --- a/lib/csscss/redundancy_analyzer.rb +++ b/lib/csscss/redundancy_analyzer.rb @@ -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 = {} @@ -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 @@ -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 diff --git a/lib/csscss/reporter.rb b/lib/csscss/reporter.rb index 8509872..1ffc4e5 100644 --- a/lib/csscss/reporter.rb +++ b/lib/csscss/reporter.rb @@ -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| @@ -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 diff --git a/test/csscss/redundancy_analyzer_test.rb b/test/csscss/redundancy_analyzer_test.rb index 8f72e25..66e3e34 100644 --- a/test/csscss/redundancy_analyzer_test.rb +++ b/test/csscss/redundancy_analyzer_test.rb @@ -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 diff --git a/test/csscss/reporter_test.rb b/test/csscss/reporter_test.rb index 17d3607..5f5e197 100644 --- a/test/csscss/reporter_test.rb +++ b/test/csscss/reporter_test.rb @@ -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 ""