summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2013-10-04 14:46:04 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2013-10-04 14:47:19 -0700
commit9d240dcf6227f4c96c81a5361ae7d81f66b1b353 (patch)
tree198d6e17762f9d9d56b51c966d79c41dbb50ca27
parentd8676734582230c8f83efc1f33f600e9207f6b4f (diff)
downloadpyscss-9d240dcf6227f4c96c81a5361ae7d81f66b1b353.tar.gz
Fix the behavior of & when the parent is compound.
-rw-r--r--scss/selector.py63
-rw-r--r--scss/tests/files/original-doctests/037-test-7.css2
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;
}