Skip to content

Commit

Permalink
Add support for :has(), :host(), and :host-context().
Browse files Browse the repository at this point in the history
See #1126
  • Loading branch information
nex3 committed Jul 12, 2014
1 parent 2e5c22a commit 68e9035
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/sass/scss/static_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def attrib_name!
return ns, name
end

SELECTOR_PSEUDO_CLASSES = %w[not matches current any].to_set
SELECTOR_PSEUDO_CLASSES = %w[not matches current any has host host-context].to_set

PREFIXED_SELECTOR_PSEUDO_CLASSES = %w[nth-child nth-last-child].to_set

Expand Down
15 changes: 15 additions & 0 deletions lib/sass/selector/pseudo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def with_selector(new_selector)
# more complex cases that likely aren't worth the pain.
next [] unless sel.name == name && sel.arg == arg
sel.selector.members
when 'has', 'host', 'host-context'
# We can't expand nested selectors here, because each layer adds an
# additional layer of semantics. For example, `:has(:has(img))`
# doesn't match `<div><img></div>` but `:has(img)` does.
sel
else
[]
end
Expand Down Expand Up @@ -146,6 +151,16 @@ def superselector?(their_sseq, parents = [])
their_seq = Sequence.new(parents + [their_sseq])
our_seq.superselector?(their_seq)
end
when 'has', 'host', 'host-context'
# Like :matches, :has (et al) can be a superselector of another
# selector if its constituent selectors are a superset of those of
# another :has in the other selector. However, the :matches other case
# doesn't work, because :has refers to nested elements.
(their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
next false unless their_sel.is_a?(Pseudo)
next false unless their_sel.name == name
selector.superselector?(their_sel.selector)
end
when 'not'
selector.members.all? do |our_seq|
their_sseq.members.any? do |their_sel|
Expand Down
15 changes: 15 additions & 0 deletions test/sass/extend_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ def test_extend_into_mergeable_pseudoclasses

assert_extends(':-moz-any(.foo)', '.x {@extend .foo}', ':-moz-any(.foo, .x)')
assert_extends(':current(.foo)', '.x {@extend .foo}', ':current(.foo, .x)')
assert_extends(':has(.foo)', '.x {@extend .foo}', ':has(.foo, .x)')
assert_extends(':host(.foo)', '.x {@extend .foo}', ':host(.foo, .x)')
assert_extends(':host-context(.foo)', '.x {@extend .foo}', ':host-context(.foo, .x)')
assert_extends(':nth-child(n of .foo)', '.x {@extend .foo}', ':nth-child(n of .foo, .x)')
assert_extends(
':nth-last-child(n of .foo)',
Expand All @@ -362,6 +365,9 @@ def test_complex_extend_into_pseudoclass
assert_extends(':not(.bar)', '.x .y {@extend .bar}', ':not(.bar, .x .y)')
assert_extends(':matches(.bar)', '.x .y {@extend .bar}', ':matches(.bar, .x .y)')
assert_extends(':current(.bar)', '.x .y {@extend .bar}', ':current(.bar, .x .y)')
assert_extends(':has(.bar)', '.x .y {@extend .bar}', ':has(.bar, .x .y)')
assert_extends(':host(.bar)', '.x .y {@extend .bar}', ':host(.bar, .x .y)')
assert_extends(':host-context(.bar)', '.x .y {@extend .bar}', ':host-context(.bar, .x .y)')
assert_extends(
':-moz-any(.bar)',
'.x .y {@extend .bar}',
Expand Down Expand Up @@ -405,6 +411,15 @@ def test_pseudoclasses_merge
':nth-last-child(n of .foo, .bar)')
end

def test_nesting_pseudoclasses_merge
assert_extends(':has(.foo)', ':has(.bar) {@extend .foo}', ':has(.foo, :has(.bar))')
assert_extends(':host(.foo)', ':host(.bar) {@extend .foo}', ':host(.foo, :host(.bar))')
assert_extends(
':host-context(.foo)',
':host-context(.bar) {@extend .foo}',
':host-context(.foo, :host-context(.bar))')
end

def test_not_unifies_with_unique_values
assert_unification('foo', ':not(bar) {@extend foo}', ':not(bar)')
assert_unification('#foo', ':not(#bar) {@extend #foo}', ':not(#bar)')
Expand Down
16 changes: 16 additions & 0 deletions test/sass/scss/css_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,8 @@ def test_summarized_selectors_with_element
assert_selector_parses('E:not(s)')
assert_selector_parses('E:not(s1, s2)')
assert_selector_parses('E:matches(s1, s2)')
assert_selector_parses('E:has(s1, s2)')
assert_selector_parses('E:has(> s1, > s2)')
assert_selector_parses('E.warning')
assert_selector_parses('E#myid')
assert_selector_parses('E[foo]')
Expand Down Expand Up @@ -761,6 +763,10 @@ def test_summarized_selectors_with_element
assert_selector_parses('E! > F')

assert_selector_parses('E /ns|foo/ F')

# From http://dev.w3.org/csswg/css-scoping-1/
assert_selector_parses('E:host(s)')
assert_selector_parses('E:host-context(s)')
end

# Taken from http://dev.w3.org/csswg/selectors4/#overview, but without element
Expand All @@ -769,6 +775,8 @@ def test_more_summarized_selectors
assert_selector_parses(':not(s)')
assert_selector_parses(':not(s1, s2)')
assert_selector_parses(':matches(s1, s2)')
assert_selector_parses(':has(s1, s2)')
assert_selector_parses(':has(> s1, > s2)')
assert_selector_parses('.warning')
assert_selector_parses('#myid')
assert_selector_parses('[foo]')
Expand Down Expand Up @@ -823,6 +831,10 @@ def test_more_summarized_selectors
assert_selector_parses(':nth-last-child(n of selector)')
assert_selector_parses(':nth-child(n)')
assert_selector_parses(':nth-last-child(n)')

# From http://dev.w3.org/csswg/css-scoping-1/
assert_selector_parses(':host(s)')
assert_selector_parses(':host-context(s)')
end

def test_attribute_selectors_with_identifiers
Expand Down Expand Up @@ -865,6 +877,10 @@ def test_selectors_containing_selectors
assert_selector_can_contain_selectors(':nth-child(n of <sel>)')
assert_selector_can_contain_selectors(':nth-last-child(n of <sel>)')
assert_selector_can_contain_selectors(':-moz-any(<sel>)')
assert_selector_can_contain_selectors(':has(<sel>)')
assert_selector_can_contain_selectors(':has(+ <sel>)')
assert_selector_can_contain_selectors(':host(<sel>)')
assert_selector_can_contain_selectors(':host-context(<sel>)')
end

def assert_selector_can_contain_selectors(sel)
Expand Down
25 changes: 25 additions & 0 deletions test/sass/superselector_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,31 @@ def test_nth_match_can_be_subselector
end
end

def has_is_superselector_of_subset_host
assert_strict_superselector ':has(.foo, .bar, .baz)', ':has(.foo.bip, .baz.bang)'
end

def has_isnt_superselector_of_contained_selector
assert_strict_superselector ':has(.foo, .bar, .baz)', '.foo'
end

def host_is_superselector_of_subset_host
assert_strict_superselector ':host(.foo, .bar, .baz)', ':host(.foo.bip, .baz.bang)'
end

def host_isnt_superselector_of_contained_selector
assert_strict_superselector ':host(.foo, .bar, .baz)', '.foo'
end

def host_context_is_superselector_of_subset_host
assert_strict_superselector(
':host-context(.foo, .bar, .baz)', ':host-context(.foo.bip, .baz.bang)')
end

def host_context_isnt_superselector_of_contained_selector
assert_strict_superselector ':host-context(.foo, .bar, .baz)', '.foo'
end

private

def assert_superselector(superselector, subselector)
Expand Down

0 comments on commit 68e9035

Please sign in to comment.