diff --git a/CHANGELOG.md b/CHANGELOG.md index a75067b70..d39e80c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.26.6 +* Fix a bug where escape sequences were improperly recognized in `@else` rules. + ### JavaScript API * Add `sass.NULL`, `sass.TRUE`, and `sass.FALSE` constants to match Node Sass's diff --git a/lib/src/parse/parser.dart b/lib/src/parse/parser.dart index d90f36ea2..013410446 100644 --- a/lib/src/parse/parser.dart +++ b/lib/src/parse/parser.dart @@ -517,24 +517,37 @@ class Parser { return true; } - /// Consumes the next character if it's equal to [letter], ignoring ASCII - /// case. + /// Consumes the next character or escape sequence if it matches [expected]. + /// + /// Matching will be case-insensitive unless [caseSensitive] is true. @protected - bool scanCharIgnoreCase(int letter) { - if (!equalsLetterIgnoreCase(letter, scanner.peekChar())) return false; - scanner.readChar(); - return true; + bool scanIdentChar(int char, {bool caseSensitive = false}) { + bool matches(int actual) => caseSensitive + ? actual == char + : characterEqualsIgnoreCase(char, actual); + + var next = scanner.peekChar(); + if (matches(next)) { + scanner.readChar(); + return true; + } else if (next == $backslash) { + var start = scanner.state; + if (matches(escapeCharacter())) return true; + scanner.state = start; + } + return false; } - /// Consumes the next character and asserts that it's equal to [letter], - /// ignoring ASCII case. + /// Consumes the next character or escape sequence and asserts it matches + /// [char]. + /// + /// Matching will be case-insensitive unless [caseSensitive] is true. @protected - void expectCharIgnoreCase(int letter) { - var actual = scanner.readChar(); - if (equalsLetterIgnoreCase(letter, actual)) return; + void expectIdentChar(int letter, {bool caseSensitive = false}) { + if (scanIdentChar(letter, caseSensitive: caseSensitive)) return; scanner.error('Expected "${String.fromCharCode(letter)}".', - position: actual == null ? scanner.position : scanner.position - 1); + position: scanner.position); } // ## Utilities @@ -599,13 +612,12 @@ class Parser { /// Consumes an identifier if its name exactly matches [text]. @protected - bool scanIdentifier(String text) { + bool scanIdentifier(String text, {bool caseSensitive = false}) { if (!lookingAtIdentifier()) return false; var start = scanner.state; - for (var i = 0; i < text.length; i++) { - var next = text.codeUnitAt(i); - if (scanCharIgnoreCase(next)) continue; + for (var letter in text.codeUnits) { + if (scanIdentChar(letter, caseSensitive: caseSensitive)) continue; scanner.state = start; return false; } @@ -617,13 +629,13 @@ class Parser { /// Consumes an identifier and asserts that its name exactly matches [text]. @protected - void expectIdentifier(String text, {String name}) { + void expectIdentifier(String text, + {String name, bool caseSensitive = false}) { name ??= '"$text"'; var start = scanner.position; - for (var i = 0; i < text.length; i++) { - var next = text.codeUnitAt(i); - if (scanCharIgnoreCase(next)) continue; + for (var letter in text.codeUnits) { + if (scanIdentChar(letter, caseSensitive: caseSensitive)) continue; scanner.error("Expected $name.", position: start); } diff --git a/lib/src/parse/scss.dart b/lib/src/parse/scss.dart index edff92238..fa9facb2c 100644 --- a/lib/src/parse/scss.dart +++ b/lib/src/parse/scss.dart @@ -43,8 +43,8 @@ class ScssParser extends StylesheetParser { whitespace(); var beforeAt = scanner.state; if (scanner.scanChar($at)) { - if (scanIdentifier('else')) return true; - if (scanIdentifier('elseif')) { + if (scanIdentifier('else', caseSensitive: true)) return true; + if (scanIdentifier('elseif', caseSensitive: true)) { logger.warn( '@elseif is deprecated and will not be supported in future Sass ' 'versions.\n' diff --git a/lib/src/parse/selector.dart b/lib/src/parse/selector.dart index 3dacdc7f7..3ef290083 100644 --- a/lib/src/parse/selector.dart +++ b/lib/src/parse/selector.dart @@ -363,9 +363,9 @@ class SelectorParser extends Parser { buffer.writeCharCode(scanner.readChar()); } whitespace(); - if (!scanCharIgnoreCase($n)) return buffer.toString(); + if (!scanIdentChar($n)) return buffer.toString(); } else { - expectCharIgnoreCase($n); + expectIdentChar($n); } buffer.writeCharCode($n); whitespace(); diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 8afe4493a..d3cd55a0d 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -2437,7 +2437,7 @@ relase. For details, see http://bit.ly/moz-document. /// Consumes a unicode range expression. StringExpression _unicodeRange() { var start = scanner.state; - expectCharIgnoreCase($u); + expectIdentChar($u); scanner.expectChar($plus); var i = 0; @@ -2749,11 +2749,11 @@ relase. For details, see http://bit.ly/moz-document. case $m: case $M: scanner.readChar(); - if (scanCharIgnoreCase($i)) { - if (!scanCharIgnoreCase($n)) return false; + if (scanIdentChar($i)) { + if (!scanIdentChar($n)) return false; buffer.write("min("); - } else if (scanCharIgnoreCase($a)) { - if (!scanCharIgnoreCase($x)) return false; + } else if (scanIdentChar($a)) { + if (!scanIdentChar($x)) return false; buffer.write("max("); } else { return false;