diff --git a/scss/cssdefs.py b/scss/cssdefs.py index d94206b8..46b6877a 100644 --- a/scss/cssdefs.py +++ b/scss/cssdefs.py @@ -428,7 +428,7 @@ def determine_encoding(buf): # ------------------------------------------------------------------------------ -# Bits and pieces of grammar, mostly as regexen +# Bits and pieces of the official CSS grammar # These are the only pseudo-elements allowed to be specified with a single # colon, for backwards compatibility @@ -443,8 +443,7 @@ def determine_encoding(buf): # or a backslash followed by one to six hex digits and a single optional # whitespace. Escaped newlines become nothing. # Ref: http://dev.w3.org/csswg/css-syntax-3/#consume-an-escaped-code-point -unescape_rx = re.compile( - r"\\([0-9a-fA-F]{1,6})[\n\t ]?|\\(.)|\\\n", re.DOTALL) +escape_rx = re.compile(r"(?s)\\([0-9a-fA-F]{1,6})[\n\t ]?|\\(.)|\\\n") def _unescape_one(match): @@ -460,9 +459,12 @@ def unescape(string): """Given a raw CSS string (i.e. taken directly from CSS source with no processing), eliminate all backslash escapes. """ - return unescape_rx.sub(_unescape_one, string) + return escape_rx.sub(_unescape_one, string) +# ------------------------------------------------------------------------------ +# Ad-hoc regexes specific to pyscss + _expr_glob_re = re.compile(r''' \#\{(.*?)\} # Global Interpolation only ''', re.VERBOSE) diff --git a/scss/rule.py b/scss/rule.py index 712f4ba8..b8bf8c96 100644 --- a/scss/rule.py +++ b/scss/rule.py @@ -2,6 +2,7 @@ from __future__ import print_function import logging +import re from scss.namespace import Namespace @@ -239,17 +240,17 @@ def parse(cls, prop, has_contents=False): # Minor parsing if prop.startswith('@'): - if prop.lower().startswith('@else if '): - directive = '@else if' - argument = prop[9:] - else: - chunks = prop.split(None, 1) - if len(chunks) == 2: - directive, argument = chunks - else: - directive, argument = prop, None - directive = directive.lower() - + # This pattern MUST NOT BE ABLE TO FAIL! + # This is slightly more lax than the CSS syntax technically allows, + # e.g. identifiers aren't supposed to begin with three hyphens. + # But we don't care, and will just spit it back out anyway. + m = re.match( + u'@(else if|[-_a-zA-Z0-9\U00000080-\U0010FFFF]*)\\b', + prop, re.I) + directive = m.group(0).lower() + argument = prop[len(directive):].strip() + if not argument: + argument = None return BlockAtRuleHeader(directive, argument, num_lines) elif prop.split(None, 1)[0].endswith(':'): # Syntax is ": [prop]" -- if the optional prop exists, it diff --git a/scss/tests/files/bugs/if-with-parentheses.css b/scss/tests/files/bugs/if-with-parentheses.css new file mode 100644 index 00000000..c3420021 --- /dev/null +++ b/scss/tests/files/bugs/if-with-parentheses.css @@ -0,0 +1,3 @@ +a:hover { + text-decoration: underline; +} diff --git a/scss/tests/files/bugs/if-with-parentheses.scss b/scss/tests/files/bugs/if-with-parentheses.scss new file mode 100644 index 00000000..44bf82e1 --- /dev/null +++ b/scss/tests/files/bugs/if-with-parentheses.scss @@ -0,0 +1,7 @@ +a { + @if(true) { + &:hover { + text-decoration: underline; + } + } +}