Skip to content

Commit

Permalink
Properly handle escaped @else (#1014)
Browse files Browse the repository at this point in the history
Fixes #1011.

This also fixes a bug where `@else` was parsed case-insensitively
(unlike all other Sass at-rules, which must be lowercase).
  • Loading branch information
jathak authored May 28, 2020
1 parent f233bcc commit fba0ea3
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
52 changes: 32 additions & 20 deletions lib/src/parse/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/src/parse/scss.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions lib/src/parse/selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions lib/src/parse/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit fba0ea3

Please sign in to comment.