diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-04 14:46:04 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-04 14:47:19 -0700 |
commit | 9d240dcf6227f4c96c81a5361ae7d81f66b1b353 (patch) | |
tree | 198d6e17762f9d9d56b51c966d79c41dbb50ca27 | |
parent | d8676734582230c8f83efc1f33f600e9207f6b4f (diff) | |
download | pyscss-9d240dcf6227f4c96c81a5361ae7d81f66b1b353.tar.gz |
Fix the behavior of & when the parent is compound.
-rw-r--r-- | scss/selector.py | 63 | ||||
-rw-r--r-- | scss/tests/files/original-doctests/037-test-7.css | 2 |
2 files changed, 53 insertions, 12 deletions
diff --git a/scss/selector.py b/scss/selector.py index 5fa16f0..25d32fe 100644 --- a/scss/selector.py +++ b/scss/selector.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import re # Super dumb little selector parser. @@ -55,15 +57,34 @@ r''' TOKEN_TYPE_ORDER = { '#': 2, '.': 3, - '[': 4, - # Note that pseudo-selectors MUST come last, because pseudo-element - # selectors target a different element! - ':': 5, - '%': 6, + '[': 3, + ':': 3, + '%': 4, } TOKEN_SORT_KEY = lambda token: TOKEN_TYPE_ORDER.get(token[0], 0) +def _is_combinator_subset_of(specific, general, is_first=True): + """Return whether `specific` matches a non-strict subset of what `general` + matches. + """ + if is_first and general == ' ': + # First selector always has a space to mean "descendent of root", which + # still holds if any other selector appears above it + return True + + if specific == general: + return True + + if specific == '>' and general == ' ': + return True + + if specific == '+' and general == '~': + return True + + return False + + class SimpleSelector(object): """A simple selector, by CSS 2.1 terminology: a combination of element name, class selectors, id selectors, and other criteria that all apply to a @@ -134,6 +155,11 @@ class SimpleSelector(object): set(self.tokens) <= set(other.tokens)) def replace_parent(self, parent_simples): + """If ``&`` (or the legacy xCSS equivalent ``self``) appears in this + selector, replace it with the given iterable of parent selectors. + + Returns a tuple of simple selectors. + """ assert parent_simples ancestors = parent_simples[:-1] @@ -148,14 +174,29 @@ class SimpleSelector(object): else: new_tokens.append(token) - if did_replace: - # This simple selector was merged into the direct parent - merged_simple = type(self)(self.combinator, new_tokens) - return ancestors + (merged_simple,) - else: - # This simple selector is completely separate + if not did_replace: + # This simple selector doesn't contain a parent reference so just + # stick it on the end return parent_simples + (self,) + # This simple selector was merged into the direct parent. + merged_self = type(self)(parent.combinator, new_tokens) + selector = ancestors + (merged_self,) + # Our combinator goes on the first ancestor, i.e., substituting "foo + # bar baz" into "+ &.quux" produces "+ foo bar baz.quux". This means a + # potential conflict with the first ancestor's combinator! + root = selector[0] + if not _is_combinator_subset_of(self.combinator, root.combinator): + raise ValueError( + "Can't sub parent {0!r} into {1!r}: " + "combinators {2!r} and {3!r} conflict!" + .format( + parent_simples, self, self.combinator, root.combinator)) + + root = type(self)(self.combinator, root.tokens) + selector = (root,) + selector[1:] + return tuple(selector) + # TODO just use set ops for these, once the constructor removes dupes def merge_with(self, other): new_tokens = self.tokens + tuple(token for token in other.tokens if token not in set(self.tokens)) diff --git a/scss/tests/files/original-doctests/037-test-7.css b/scss/tests/files/original-doctests/037-test-7.css index 7ae523e..1f74b57 100644 --- a/scss/tests/files/original-doctests/037-test-7.css +++ b/scss/tests/files/original-doctests/037-test-7.css @@ -1,6 +1,6 @@ a, #fake-links .link { color: blue; } -a:hover, #fake-links .link:hover { +a:hover, #fake-links :hover.link { text-decoration: underline; } |