Skip to content

Commit

Permalink
Fix selectors :not(.a,.b)
Browse files Browse the repository at this point in the history
Fix #160
  • Loading branch information
stoivo committed May 29, 2024
1 parent faab4cb commit 33e89e7
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 31 deletions.
26 changes: 24 additions & 2 deletions lib/css_parser/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ class RemoteFileError < IOError; end
# Exception class used if a request is made to load a CSS file more than once.
class CircularReferenceError < StandardError; end

# We have a Parser class which you create and instance of but we have some
# functions which is nice to have outside of this instance
module ParserFx
def self.split_selectors(tokens)
# it is expecting the selector tokens from node: :style_rule, not just
# from Crass::Tokenizer.tokenize(input)

tokens
.each_with_object([[]]) do |token, sum|
case token
in node: :comma
sum << []
else
sum.last << token
end
end
end
end

# == Parser class
#
# All CSS is converted to UTF-8.
Expand Down Expand Up @@ -80,7 +99,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
Expand Down Expand Up @@ -130,9 +149,12 @@ def add_block!(block, options = {})
case node
in node: :style_rule
declarations = 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
}
Expand Down
13 changes: 1 addition & 12 deletions lib/css_parser/rule_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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

Expand Down Expand Up @@ -450,17 +450,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)
Expand Down
52 changes: 42 additions & 10 deletions test/test_css_parser_misc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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
Expand All @@ -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&amp;def=456&ghi=789#1011) };", base_uri)
assert_equal "body { background: url('http://www.example.org/style/yellow-dot_symbol$.png?abc=123&amp;def=456&ghi=789#1011') };", converted_css
Expand Down
8 changes: 1 addition & 7 deletions test/test_rule_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;")
Expand Down

0 comments on commit 33e89e7

Please sign in to comment.