Merge pull request #93 from sass/fixes

More bug fixes.
This commit is contained in:
Natalie Weizenbaum 2017-01-08 21:03:36 -08:00 committed by GitHub
commit 28c7362016
6 changed files with 182 additions and 39 deletions

View File

@ -1,3 +1,15 @@
## 1.0.0-alpha.8
* Disallow invalid function names.
* Support terse mixin syntax in the indented syntax.
* Fix `@at-root` query parsing.
* Support special functions in `@-moz-document`.
* Support `...` after a digit.
## 1.0.0-alpha.7
* Fix `function-exists()`, `variable-exists()`, and `mixin-exists()` to use the

View File

@ -26,6 +26,7 @@ class AtRootQueryParser extends Parser {
atRules.add(identifier().toLowerCase());
whitespace();
} while (lookingAtIdentifier());
scanner.expectChar($rparen);
scanner.expectDone();
return new AtRootQuery(include, atRules);

View File

@ -262,8 +262,8 @@ abstract class Parser {
/// Consumes a `url()` token if possible, and returns `null` otherwise.
String tryUrl() {
// NOTE: this logic is largely duplicated in ScssParser._tryUrlContents.
// Most changes here should be mirrored there.
// NOTE: this logic is largely duplicated in ScssParser._urlContents and
// ScssParser._tryUrlContents. Most changes here should be mirrored there.
var start = scanner.state;
if (!scanIdentifier("url", ignoreCase: true)) return null;
@ -434,19 +434,22 @@ abstract class Parser {
/// Returns whether the scanner is immediately before a plain CSS identifier.
///
/// If [forward] is passed, this looks that many characters forward instead.
///
/// This is based on [the CSS algorithm][], but it assumes all backslashes
/// start escapes.
///
/// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier
bool lookingAtIdentifier() {
bool lookingAtIdentifier([int forward]) {
// See also [ScssParser._lookingAtInterpolatedIdentifier].
var first = scanner.peekChar();
forward ??= 0;
var first = scanner.peekChar(forward);
if (first == null) return false;
if (isNameStart(first) || first == $backslash) return true;
if (first != $dash) return false;
var second = scanner.peekChar(1);
var second = scanner.peekChar(forward + 1);
return second != null &&
(isNameStart(second) || second == $dash || second == $backslash);
}

View File

@ -62,7 +62,7 @@ abstract class StylesheetParser extends Parser {
Stylesheet parse() {
return wrapSpanFormatException(() {
var start = scanner.state;
var statements = this.statements(_topLevelStatement);
var statements = this.statements(() => _statement(root: true));
scanner.expectDone();
return new Stylesheet(statements, scanner.spanFrom(start));
});
@ -76,12 +76,30 @@ abstract class StylesheetParser extends Parser {
});
}
/// Consumes a statement that's allowed at the top level of the stylesheet.
Statement _topLevelStatement() {
if (scanner.peekChar() == $at) {
return _atRule(_topLevelStatement, root: true);
} else {
return _styleRule();
/// Consumes a statement that's allowed at the top level of the stylesheet or
/// within nested style and at rules.
///
/// If [root] is `true`, this parses at-rules that are allowed only at the
/// root of the stylesheet.
Statement _statement({bool root: false}) {
switch (scanner.peekChar()) {
case $at:
return _atRule(() => _statement(), root: root);
case $plus:
if (!indented || !lookingAtIdentifier(1)) return _styleRule();
var start = scanner.state;
scanner.readChar();
return _includeRule(start);
case $equal:
if (!indented) return _styleRule();
var start = scanner.state;
scanner.readChar();
return _mixinRule(start);
default:
return root ? _styleRule() : _declarationOrStyleRule();
}
}
@ -122,16 +140,10 @@ abstract class StylesheetParser extends Parser {
StyleRule _styleRule() {
var start = scanner.state;
var selector = _almostAnyValue();
var children = this.children(_ruleChild);
var children = this.children(_statement);
return new StyleRule(selector, children, scanner.spanFrom(start));
}
/// Consumes a statement that's allowed within a style rule.
Statement _ruleChild() {
if (scanner.peekChar() == $at) return _atRule(_ruleChild);
return _declarationOrStyleRule();
}
/// Consumes a [Declaration] or a [StyleRule].
///
/// When parsing the contents of a style rule, it can be difficult to tell
@ -168,7 +180,7 @@ abstract class StylesheetParser extends Parser {
buffer.addInterpolation(_almostAnyValue());
var selectorSpan = scanner.spanFrom(start);
var children = this.children(_ruleChild);
var children = this.children(_statement);
if (indented && children.isEmpty) {
warn("This selector doesn't have any properties and won't be rendered.",
selectorSpan,
@ -372,6 +384,8 @@ abstract class StylesheetParser extends Parser {
return _mediaRule(start);
case "mixin":
return _mixinRule(start);
case "-moz-document":
return _mozDocumentRule(start);
case "return":
return _disallowedAtRule(start);
case "supports":
@ -458,10 +472,10 @@ abstract class StylesheetParser extends Parser {
if (scanner.peekChar() == $lparen) {
var query = _queryExpression();
whitespace();
return new AtRootRule(children(_ruleChild), scanner.spanFrom(start),
return new AtRootRule(children(_statement), scanner.spanFrom(start),
query: query);
} else if (lookingAtChildren()) {
return new AtRootRule(children(_ruleChild), scanner.spanFrom(start));
return new AtRootRule(children(_statement), scanner.spanFrom(start));
} else {
var child = _styleRule();
return new AtRootRule([child], scanner.spanFrom(start));
@ -557,6 +571,17 @@ abstract class StylesheetParser extends Parser {
scanner.string);
}
switch (unvendor(name)) {
case "calc":
case "element":
case "expression":
case "url":
scanner.error("Invalid function name.",
position: start.position,
length: scanner.position - start.position);
break;
}
whitespace();
var children = this.children(_functionAtRule);
@ -738,7 +763,7 @@ abstract class StylesheetParser extends Parser {
List<Statement> children;
if (lookingAtChildren()) {
_inContentBlock = true;
children = this.children(_ruleChild);
children = this.children(_statement);
_inContentBlock = false;
} else {
expectStatementSeparator();
@ -753,7 +778,7 @@ abstract class StylesheetParser extends Parser {
/// [start] should point before the `@`.
MediaRule _mediaRule(LineScannerState start) {
var query = _mediaQueryList();
var children = this.children(_ruleChild);
var children = this.children(_statement);
return new MediaRule(query, children, scanner.spanFrom(start));
}
@ -782,7 +807,7 @@ abstract class StylesheetParser extends Parser {
whitespace();
_inMixin = true;
_mixinHasContent = false;
var children = this.children(_ruleChild);
var children = this.children(_statement);
var hadContent = _mixinHasContent;
_inMixin = false;
_mixinHasContent = null;
@ -791,6 +816,57 @@ abstract class StylesheetParser extends Parser {
hasContent: hadContent);
}
/// Consumes a `@moz-document` rule.
///
/// Gecko's `@-moz-document` diverges from [the specificiation][] allows the
/// `url-prefix` and `domain` functions to omit quotation marks, contrary to
/// the standard.
///
/// [the specificiation]: http://www.w3.org/TR/css3-conditional/
AtRule _mozDocumentRule(LineScannerState start) {
var valueStart = scanner.state;
var buffer = new InterpolationBuffer();
while (true) {
if (scanner.peekChar() == $hash) {
buffer.add(singleInterpolation());
} else {
var identifierStart = scanner.state;
var identifier = this.identifier();
switch (identifier) {
case "url":
case "url-prefix":
case "domain":
buffer.addInterpolation(
_urlContents(identifierStart, name: identifier));
break;
case "regexp":
buffer.write("regexp(");
scanner.expectChar($lparen);
buffer.addInterpolation(interpolatedString().asInterpolation());
scanner.expectChar($rparen);
buffer.writeCharCode($rparen);
break;
default:
scanner.error("Invalid function name.",
position: identifierStart.position, length: identifier.length);
}
}
whitespace();
if (!scanner.scanChar($comma)) break;
buffer.writeCharCode($comma);
buffer.write(rawText(whitespace));
}
var value = buffer.interpolation(scanner.spanFrom(valueStart));
var children = this.children(_statement);
return new AtRule("-moz-document", scanner.spanFrom(start),
value: value, children: children);
}
/// Consumes a `@return` rule.
///
/// [start] should point before the `@`.
@ -807,7 +883,7 @@ abstract class StylesheetParser extends Parser {
var condition = _supportsCondition();
whitespace();
return new SupportsRule(
condition, children(_ruleChild), scanner.spanFrom(start));
condition, children(_statement), scanner.spanFrom(start));
}
/// Consumes a `@warn` rule.
@ -840,7 +916,7 @@ abstract class StylesheetParser extends Parser {
var next = scanner.peekChar();
if (next != $exclamation && !atEndOfStatement()) value = _almostAnyValue();
var children = lookingAtChildren() ? this.children(_ruleChild) : null;
var children = lookingAtChildren() ? this.children(_statement) : null;
if (children == null) expectStatementSeparator();
return new AtRule(name, scanner.spanFrom(start),
@ -1696,13 +1772,17 @@ abstract class StylesheetParser extends Parser {
}
if (scanner.peekChar() == $dot) {
scanner.readChar();
if (!isDigit(scanner.peekChar())) scanner.error("Expected digit.");
var decimal = 0.1;
while (isDigit(scanner.peekChar())) {
number += asDecimal(scanner.readChar()) * decimal;
decimal /= 10;
if (!isDigit(scanner.peekChar(1))) {
if (first == $dot) {
scanner.error("Expected digit.", position: scanner.position + 1);
}
} else {
scanner.readChar();
var decimal = 0.1;
while (isDigit(scanner.peekChar())) {
number += asDecimal(scanner.readChar()) * decimal;
decimal /= 10;
}
}
}
@ -1925,10 +2005,56 @@ abstract class StylesheetParser extends Parser {
/// Consumes the contents of a `url()` token (after the name).
///
/// [start] is the position before the beginning of the name.
/// [start] is the position before the beginning of the name. [name] is the
/// function's name; it defaults to `"url"`.
Interpolation _urlContents(LineScannerState start, {String name}) {
// NOTE: this logic is largely duplicated in [_tryUrlContents] and
// Parser.tryUrl. Most changes here should be mirrored there.
var start = scanner.state;
scanner.expectChar($lparen);
whitespaceWithoutComments();
// Match Ruby Sass's behavior: parse a raw URL() if possible, and if not
// backtrack and re-parse as a function expression.
var buffer = new InterpolationBuffer()..write("${name ?? 'url'}(");
while (true) {
var next = scanner.peekChar();
if (next == null) {
break;
} else if (next == $percent ||
next == $ampersand ||
(next >= $asterisk && next <= $tilde) ||
next >= 0x0080) {
buffer.writeCharCode(scanner.readChar());
} else if (next == $backslash) {
buffer.write(escape());
} else if (next == $hash) {
if (scanner.peekChar(1) == $lbrace) {
buffer.add(singleInterpolation());
} else {
buffer.writeCharCode(scanner.readChar());
}
} else if (isWhitespace(next)) {
whitespaceWithoutComments();
scanner.expectChar($rparen);
buffer.writeCharCode($rparen);
break;
} else if (next == $rparen) {
buffer.writeCharCode(scanner.readChar());
break;
} else {
scanner.expectChar($rparen);
}
}
return buffer.interpolation(scanner.spanFrom(start));
}
/// Like [_urlContents], but returns `null` if the URL fails to parse.
Interpolation _tryUrlContents(LineScannerState start) {
// NOTE: this logic is largely duplicated in Parser.tryUrl. Most changes
// here should be mirrored there.
// NOTE: this logic is largely duplicated in [_urlContents] and
// Parser.tryUrl. Most changes here should be mirrored there.
var start = scanner.state;
if (!scanner.scanChar($lparen)) return null;

View File

@ -195,7 +195,8 @@ class _PerformVisitor
Value visitAtRootRule(AtRootRule node) {
var query = node.query == null
? AtRootQuery.defaultQuery
: new AtRootQuery.parse(_performInterpolation(node.query));
: _adjustParseError(node.query.span,
() => new AtRootQuery.parse(_performInterpolation(node.query)));
var parent = _parent;
var included = <CssParentNode>[];

View File

@ -1,5 +1,5 @@
name: sass
version: 1.0.0-alpha.7
version: 1.0.0-dev
description: A Sass implementation in Dart.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/sass/dart-sass