From 2dc3f7cf7f10a3325ee36c965ed723cf2889f1d0 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 29 May 2024 13:46:46 +0200 Subject: [PATCH] Fix selectors :not(.a,.b) Fix #160 --- lib/css_parser/parser.rb | 21 +++++++++++++-- lib/css_parser/rule_set.rb | 13 +-------- test/test_css_parser_misc.rb | 52 +++++++++++++++++++++++++++++------- test/test_rule_set.rb | 8 +----- 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/lib/css_parser/parser.rb b/lib/css_parser/parser.rb index 310a65c..b2e0e59 100644 --- a/lib/css_parser/parser.rb +++ b/lib/css_parser/parser.rb @@ -32,6 +32,20 @@ def self.create_declaration_from_properties(properties) declarations end + + # it is expecting the selector tokens from node: :style_rule, not just + # from Crass::Tokenizer.tokenize(input) + def self.split_selectors(tokens) + tokens + .each_with_object([[]]) do |token, sum| + case token + in node: :comma + sum << [] + else + sum.last << token + end + end + end end # == Parser class @@ -107,7 +121,7 @@ def find_rule_sets(selectors, media_types = :all) rule_sets = [] selectors.each do |selector| - selector = selector.gsub(/\s+/, ' ').strip + selector = selector.strip each_rule_set(media_types) do |rule_set, _media_type| if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector) rule_sets << rule_set @@ -157,9 +171,12 @@ def add_block!(block, options = {}) case node in node: :style_rule declarations = ParserFx.create_declaration_from_properties(node[:children]) + selectors = ParserFx + .split_selectors(node[:selector][:tokens]) + .map { Crass::Parser.stringify(_1).strip } add_rule_options = { - selectors: node[:selector][:value], + selectors: selectors, block: declarations, media_types: current_media_queries } diff --git a/lib/css_parser/rule_set.rb b/lib/css_parser/rule_set.rb index 304164b..ac63f3b 100644 --- a/lib/css_parser/rule_set.rb +++ b/lib/css_parser/rule_set.rb @@ -60,7 +60,7 @@ def initialize(selectors: nil, block: nil, offset: nil, filename: nil, specifici @offset = offset @filename = filename - parse_selectors!(selectors) if selectors + @selectors = Array(selectors) if selectors parse_declarations!(block) end @@ -522,17 +522,6 @@ def parse_declarations!(block) # :nodoc: end end - #-- - # TODO: way too simplistic - #++ - def parse_selectors!(selectors) # :nodoc: - @selectors = selectors.split(',').map do |s| - s.gsub!(/\s+/, ' ') - s.strip! - s - end - end - def split_value_preserving_function_whitespace(value) split_value = value.gsub(RE_FUNCTIONS) do |c| c.gsub!(/\s+/, WHITESPACE_REPLACEMENT) diff --git a/test/test_css_parser_misc.rb b/test/test_css_parser_misc.rb index 9fef608..4c286fb 100644 --- a/test/test_css_parser_misc.rb +++ b/test/test_css_parser_misc.rb @@ -133,7 +133,7 @@ def test_multiline_declarations end def test_find_rule_sets - css = <<-CSS + css = <<~CSS h1, h2 { color: blue; } h1 { font-size: 10px; } h2 { font-size: 5px; } @@ -144,9 +144,37 @@ def test_find_rule_sets @cp.add_block!(css) assert_equal 2, @cp.find_rule_sets(["h2"]).size + assert_equal 2, @cp.find_rule_sets([" h2 "]).size assert_equal 3, @cp.find_rule_sets(["h1", "h2"]).size - assert_equal 2, @cp.find_rule_sets(["article h3"]).size - assert_equal 2, @cp.find_rule_sets([" article \t \n h3 \n "]).size + assert_equal 1, @cp.find_rule_sets(["article h3"]).size + assert_equal 1, @cp.find_rule_sets(["article\nh3"]).size + assert_equal 0, @cp.find_rule_sets([" article \t \n h3 \n "]).size + end + + def test_whitespace_in_selector_names + css = <<~CSS + h1 {} + h1 pre{} + .a.b.c + .d.e.f {} + h1 > pre {} + input[name="Joe"]{} + input[name="Joe Doe"]{} + input:not(a,b){} + CSS + + @cp.add_block!(css) + + assert_equal( + ["h1", + "h1 pre", + ".a.b.c\n.d.e.f", + "h1 > pre", + "input[name=\"Joe\"]", + "input[name=\"Joe Doe\"]", + "input:not(a,b)"], + @cp.rules_by_media_query[:all].flat_map(&:selectors) + ) end def test_calculating_specificity @@ -171,13 +199,17 @@ def test_calculating_specificity def test_converting_uris base_uri = 'http://www.example.org/style/basic.css' - ["body { background: url(yellow) };", "body { background: url('yellow') };", - "body { background: url('/style/yellow') };", - "body { background: url(\"../style/yellow\") };", - "body { background: url(\"lib/../../style/yellow\") };"].each do |css| - converted_css = CssParser.convert_uris(css, base_uri) - assert_equal "body { background: url('http://www.example.org/style/yellow') };", converted_css - end + [ + "body { background: url(yellow) };", + "body { background: url('yellow') };", + "body { background: url('/style/yellow') };", + "body { background: url(\"../style/yellow\") };", + "body { background: url(\"lib/../../style/yellow\") };" + ] + .each do |css| + converted_css = CssParser.convert_uris(css, base_uri) + assert_equal "body { background: url('http://www.example.org/style/yellow') };", converted_css + end converted_css = CssParser.convert_uris("body { background: url(../style/yellow-dot_symbol$.png?abc=123&def=456&ghi=789#1011) };", base_uri) assert_equal "body { background: url('http://www.example.org/style/yellow-dot_symbol$.png?abc=123&def=456&ghi=789#1011') };", converted_css diff --git a/test/test_rule_set.rb b/test/test_rule_set.rb index e4bfd5b..72254c9 100644 --- a/test/test_rule_set.rb +++ b/test/test_rule_set.rb @@ -38,7 +38,7 @@ def test_each_selector ] actual = [] - rs = RuleSet.new(selectors: '#content p, a', block: 'color: #fff;') + rs = RuleSet.new(selectors: ['#content p', 'a'], block: 'color: #fff;') rs.each_selector do |sel, decs, spec| actual << {selector: sel, declarations: decs, specificity: spec} end @@ -78,12 +78,6 @@ def test_each_declaration_containing_semicolons assert_equal('no-repeat;', rs['background-repeat']) end - def test_selector_sanitization - selectors = "h1, h2,\nh3 " - rs = RuleSet.new(selectors: selectors, block: "color: #fff;") - assert rs.selectors.member?("h3") - end - def test_multiple_selectors_to_s selectors = "#content p, a" rs = RuleSet.new(selectors: selectors, block: "color: #fff;")