diff --git a/lib/syntax_tree/css/format.rb b/lib/syntax_tree/css/format.rb index a940f69..4f869f5 100644 --- a/lib/syntax_tree/css/format.rb +++ b/lib/syntax_tree/css/format.rb @@ -108,14 +108,24 @@ def visit_pseudo_element_selector(node) # Visit a Selectors::Combinator node. def visit_combinator(node) - node.value.format(q) + case node.value + when WhitespaceToken + q.text(" ") + when Array + q.text(" ") + node.value.each { |val| val.format(q) } + q.text(" ") + else + q.text(" ") + node.value.format(q) + q.text(" ") + end end # Visit a Selectors::ComplexSelector node. def visit_complex_selector(node) q.group do node.child_nodes.each_with_index do |child_node, j| - q.text(" ") unless j == 0 child_node.format(q) end end diff --git a/lib/syntax_tree/css/pretty_print.rb b/lib/syntax_tree/css/pretty_print.rb index 0094006..91bc5cf 100644 --- a/lib/syntax_tree/css/pretty_print.rb +++ b/lib/syntax_tree/css/pretty_print.rb @@ -423,7 +423,7 @@ def visit_wqname(node) # Visit a Selectors::Combinator node. def visit_combinator(node) - token("combinator") do + token(node.class::PP_NAME) do q.breakable q.pp(node.value) end diff --git a/lib/syntax_tree/css/selectors.rb b/lib/syntax_tree/css/selectors.rb index 32bf420..076f7d0 100644 --- a/lib/syntax_tree/css/selectors.rb +++ b/lib/syntax_tree/css/selectors.rb @@ -93,6 +93,36 @@ def deconstruct_keys(keys) end end + # §15.1 https://www.w3.org/TR/selectors-4/#descendant-combinators + class DescendantCombinator < Combinator + TOKEN = WhitespaceToken + PP_NAME = "descendant-combinator" + end + + # §15.2 https://www.w3.org/TR/selectors-4/#child-combinators + class ChildCombinator < Combinator + TOKEN = ">" + PP_NAME = "child-combinator" + end + + # §15.3 https://www.w3.org/TR/selectors-4/#adjacent-sibling-combinators + class NextSiblingCombinator < Combinator + TOKEN = "+" + PP_NAME = "next-sibling-combinator" + end + + # §15.4 https://www.w3.org/TR/selectors-4/#general-sibling-combinators + class SubsequentSiblingCombinator < Combinator + TOKEN = "~" + PP_NAME = "subsequent-sibling-combinator" + end + + # §16.1 https://www.w3.org/TR/selectors-4/#the-column-combinator + class ColumnSiblingCombinator < Combinator + TOKEN = ["|", "|"] + PP_NAME = "column-sibling-combinator" + end + class ComplexSelector < Node attr_reader :child_nodes @@ -333,12 +363,12 @@ def relative_selector_list def complex_selector child_nodes = [compound_selector] + combinator_ = nil + compound_selector_ = nil loop do - if (c = maybe { combinator }) - child_nodes << c - end - if (s = maybe { compound_selector }) - child_nodes << s + if maybe { (combinator_ = combinator) && (compound_selector_ = compound_selector) } + child_nodes << combinator_ + child_nodes << compound_selector_ else break end @@ -363,8 +393,6 @@ def relative_selector # = [ ? * # [ * ]* ]! def compound_selector - consume_whitespace - type = maybe { type_selector } subclasses = [] @@ -401,17 +429,13 @@ def simple_selector # = '>' | '+' | '~' | [ '|' '|' ] def combinator - consume_whitespace - - value = - options do - maybe { consume(">") } || - maybe { consume("+") } || - maybe { consume("~") } || - maybe { consume("|", "|") } - end - - Combinator.new(value: value) + options do + maybe { consume_combinator(ChildCombinator) } || + maybe { consume_combinator(NextSiblingCombinator) } || + maybe { consume_combinator(SubsequentSiblingCombinator) } || + maybe { consume_combinator(ColumnSiblingCombinator) } || + maybe { consume_combinator(DescendantCombinator) } + end end # = | ? '*' @@ -554,6 +578,16 @@ def one_or_more end end + def consume_combinator(combinator_class) + eat_whitespace = (combinator_class::TOKEN != WhitespaceToken) + + consume_whitespace if eat_whitespace + result = consume(*combinator_class::TOKEN) + consume_whitespace if eat_whitespace + + combinator_class.new(value: result) + end + def consume(*values) result = values.map do |value| diff --git a/test/selectors_test.rb b/test/selectors_test.rb index b7a54e6..a8dd5f7 100644 --- a/test/selectors_test.rb +++ b/test/selectors_test.rb @@ -128,15 +128,23 @@ class SelectorsTest < Minitest::Spec end it "parses a complex selector" do - actual = parse_selectors("section>table") + actual = parse_selectors("a b > c + d ~ e || f") assert_pattern do actual => [ Selectors::ComplexSelector[ child_nodes: [ - Selectors::TypeSelector[value: { name: { value: "section" } }], - Selectors::Combinator[value: { value: ">" }], - Selectors::TypeSelector[value: { name: { value: "table" } }] + Selectors::TypeSelector[value: { name: { value: "a" } }], + Selectors::DescendantCombinator, + Selectors::TypeSelector[value: { name: { value: "b" } }], + Selectors::ChildCombinator, + Selectors::TypeSelector[value: { name: { value: "c" } }], + Selectors::NextSiblingCombinator, + Selectors::TypeSelector[value: { name: { value: "d" } }], + Selectors::SubsequentSiblingCombinator, + Selectors::TypeSelector[value: { name: { value: "e" } }], + Selectors::ColumnSiblingCombinator, + Selectors::TypeSelector[value: { name: { value: "f" } }], ] ] ] @@ -185,6 +193,7 @@ class SelectorsTest < Minitest::Spec Selectors::ComplexSelector[ child_nodes: [ Selectors::TypeSelector[value: { name: { value: "section" } }], + Selectors::Combinator[value: { value: " " }], Selectors::TypeSelector[value: { name: { value: "table" } }], ] ] @@ -202,6 +211,7 @@ class SelectorsTest < Minitest::Spec Selectors::TypeSelector[value: { name: { value: "section" } }], Selectors::Combinator[value: { value: ">" }], Selectors::TypeSelector[value: { name: { value: "table" } }], + Selectors::Combinator[value: { value: " " }], Selectors::TypeSelector[value: { name: { value: "tr" } }] ] ] @@ -249,6 +259,13 @@ class SelectorsTest < Minitest::Spec ".outer section.foo > table.bar tr", ) end + + it "handles all the combinators" do + assert_selector_format( + "a b > c + d ~ e || f", + "a b > c + d ~ e || f", + ) + end end private