From 4d23acd11948294f6d3e5c878c28eb728d67998c Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Mon, 11 Feb 2019 17:17:10 -0800 Subject: [PATCH 1/6] Run tests against sass-spec's feature.use branch --- tool/travis/sass-spec-ref.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/travis/sass-spec-ref.sh b/tool/travis/sass-spec-ref.sh index ae296f7f..e58c7eb2 100755 --- a/tool/travis/sass-spec-ref.sh +++ b/tool/travis/sass-spec-ref.sh @@ -7,7 +7,7 @@ # run. If we're running specs for a pull request which refers to a sass-spec # pull request, we'll run against the latter rather than sass-spec master. -default=master +default=feature.use if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then >&2 echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST." From d757d9956988564bc1c53e8306c448f5c9529fe3 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 17 Jan 2019 20:42:54 -0500 Subject: [PATCH 2/6] Add support for parsing @use --- lib/src/ast/sass.dart | 1 + lib/src/ast/sass/expression/function.dart | 16 +- lib/src/ast/sass/expression/variable.dart | 13 +- lib/src/ast/sass/statement/include_rule.dart | 11 +- lib/src/ast/sass/statement/use_rule.dart | 30 ++++ .../sass/statement/variable_declaration.dart | 16 +- lib/src/parse/css.dart | 3 +- lib/src/parse/parser.dart | 21 ++- lib/src/parse/stylesheet.dart | 142 ++++++++++++++++-- lib/src/visitor/async_evaluate.dart | 8 +- lib/src/visitor/evaluate.dart | 10 +- lib/src/visitor/find_imports.dart | 4 + lib/src/visitor/interface/statement.dart | 1 + 13 files changed, 246 insertions(+), 30 deletions(-) create mode 100644 lib/src/ast/sass/statement/use_rule.dart diff --git a/lib/src/ast/sass.dart b/lib/src/ast/sass.dart index f2b354d9..4e85726e 100644 --- a/lib/src/ast/sass.dart +++ b/lib/src/ast/sass.dart @@ -53,6 +53,7 @@ export 'sass/statement/silent_comment.dart'; export 'sass/statement/style_rule.dart'; export 'sass/statement/stylesheet.dart'; export 'sass/statement/supports_rule.dart'; +export 'sass/statement/use_rule.dart'; export 'sass/statement/variable_declaration.dart'; export 'sass/statement/warn_rule.dart'; export 'sass/statement/while_rule.dart'; diff --git a/lib/src/ast/sass/expression/function.dart b/lib/src/ast/sass/expression/function.dart index 0b938507..e684b457 100644 --- a/lib/src/ast/sass/expression/function.dart +++ b/lib/src/ast/sass/expression/function.dart @@ -4,7 +4,6 @@ import 'package:source_span/source_span.dart'; -import '../../../utils.dart'; import '../../../visitor/interface/expression.dart'; import '../expression.dart'; import '../argument_invocation.dart'; @@ -15,6 +14,10 @@ import '../interpolation.dart'; /// /// This may be a plain CSS function or a Sass function. class FunctionExpression implements Expression, CallableInvocation { + /// The namespace of the function being invoked, or `null` if it's invoked + /// without a namespace. + final String namespace; + /// The name of the function being invoked. /// /// If this is interpolated, the function will be interpreted as plain CSS, @@ -24,12 +27,17 @@ class FunctionExpression implements Expression, CallableInvocation { /// The arguments to pass to the function. final ArgumentInvocation arguments; - FileSpan get span => spanForList([name, arguments]); + final FileSpan span; - FunctionExpression(this.name, this.arguments); + FunctionExpression(this.name, this.arguments, this.span, {this.namespace}); T accept(ExpressionVisitor visitor) => visitor.visitFunctionExpression(this); - String toString() => "$name$arguments"; + String toString() { + var buffer = StringBuffer(); + if (namespace != null) buffer.write("$namespace."); + buffer.write("$name$arguments"); + return buffer.toString(); + } } diff --git a/lib/src/ast/sass/expression/variable.dart b/lib/src/ast/sass/expression/variable.dart index 26b7ec4f..f289c014 100644 --- a/lib/src/ast/sass/expression/variable.dart +++ b/lib/src/ast/sass/expression/variable.dart @@ -9,15 +9,24 @@ import '../expression.dart'; /// A Sass variable. class VariableExpression implements Expression { + /// The namespace of the variable being referenced, or `null` if it's + /// referenced without a namespace. + final String namespace; + /// The name of this variable. final String name; final FileSpan span; - VariableExpression(this.name, this.span); + VariableExpression(this.name, this.span, {this.namespace}); T accept(ExpressionVisitor visitor) => visitor.visitVariableExpression(this); - String toString() => "\$$name"; + String toString() { + var buffer = StringBuffer("\$"); + if (namespace != null) buffer.write("$namespace."); + buffer.write(name); + return buffer.toString(); + } } diff --git a/lib/src/ast/sass/statement/include_rule.dart b/lib/src/ast/sass/statement/include_rule.dart index 0c9f9516..fdd87ebc 100644 --- a/lib/src/ast/sass/statement/include_rule.dart +++ b/lib/src/ast/sass/statement/include_rule.dart @@ -12,6 +12,10 @@ import 'content_block.dart'; /// A mixin invocation. class IncludeRule implements Statement, CallableInvocation { + /// The namespace of the mixin being invoked, or `null` if it's invoked + /// without a namespace. + final String namespace; + /// The name of the mixin being invoked. final String name; @@ -24,12 +28,15 @@ class IncludeRule implements Statement, CallableInvocation { final FileSpan span; - IncludeRule(this.name, this.arguments, this.span, {this.content}); + IncludeRule(this.name, this.arguments, this.span, + {this.namespace, this.content}); T accept(StatementVisitor visitor) => visitor.visitIncludeRule(this); String toString() { - var buffer = StringBuffer("@include $name"); + var buffer = StringBuffer("@include "); + if (namespace != null) buffer.write("$namespace."); + buffer.write(name); if (!arguments.isEmpty) buffer.write("($arguments)"); buffer.write(content == null ? ";" : " $content"); return buffer.toString(); diff --git a/lib/src/ast/sass/statement/use_rule.dart b/lib/src/ast/sass/statement/use_rule.dart new file mode 100644 index 00000000..97532283 --- /dev/null +++ b/lib/src/ast/sass/statement/use_rule.dart @@ -0,0 +1,30 @@ +// Copyright 2016 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:source_span/source_span.dart'; + +import '../../../visitor/interface/statement.dart'; +import '../expression/string.dart'; +import '../statement.dart'; + +/// A `@use` rule. +class UseRule implements Statement { + /// The URI of the module to use. + /// + /// If this is relative, it's relative to the containing file. + final Uri url; + + /// The namespace for members of the used module, or `null` if the members + /// can be accessed without a namespace. + final String namespace; + + final FileSpan span; + + UseRule(this.url, this.namespace, this.span); + + T accept(StatementVisitor visitor) => visitor.visitUseRule(this); + + String toString() => "@use ${StringExpression.quoteText(url.toString())} as " + "${namespace ?? "*"};"; +} diff --git a/lib/src/ast/sass/statement/variable_declaration.dart b/lib/src/ast/sass/statement/variable_declaration.dart index 17925e50..012c09e3 100644 --- a/lib/src/ast/sass/statement/variable_declaration.dart +++ b/lib/src/ast/sass/statement/variable_declaration.dart @@ -15,6 +15,10 @@ import 'silent_comment.dart'; /// /// This defines or sets a variable. class VariableDeclaration implements Statement { + /// The namespace of the variable being set, or `null` if it's defined or set + /// without a namespace. + final String namespace; + /// The name of the variable. final String name; @@ -37,7 +41,10 @@ class VariableDeclaration implements Statement { final FileSpan span; VariableDeclaration(this.name, this.expression, this.span, - {bool guarded = false, bool global = false, SilentComment comment}) + {this.namespace, + bool guarded = false, + bool global = false, + SilentComment comment}) : isGuarded = guarded, isGlobal = global, comment = comment; @@ -53,5 +60,10 @@ class VariableDeclaration implements Statement { T accept(StatementVisitor visitor) => visitor.visitVariableDeclaration(this); - String toString() => "\$$name: $expression;"; + String toString() { + var buffer = StringBuffer("\$"); + if (namespace != null) buffer.write("$namespace."); + buffer.write("$name: $expression;"); + return buffer.toString(); + } } diff --git a/lib/src/parse/css.dart b/lib/src/parse/css.dart index 454b8bd9..8df5e38c 100644 --- a/lib/src/parse/css.dart +++ b/lib/src/parse/css.dart @@ -139,6 +139,7 @@ class CssParser extends ScssParser { // as plain CSS, rather than calling a user-defined function. Interpolation([StringExpression(identifier)], identifier.span), ArgumentInvocation( - arguments, const {}, scanner.spanFrom(beforeArguments))); + arguments, const {}, scanner.spanFrom(beforeArguments)), + scanner.spanFrom(start)); } } diff --git a/lib/src/parse/parser.dart b/lib/src/parse/parser.dart index 009c6960..e6fe5618 100644 --- a/lib/src/parse/parser.dart +++ b/lib/src/parse/parser.dart @@ -17,7 +17,7 @@ import '../utils.dart'; /// This provides utility methods and common token parsing. Unless specified /// otherwise, a parse method throws a [SassFormatException] if it fails to /// parse. -abstract class Parser { +class Parser { /// The scanner that scans through the text being parsed. final SpanScanner scanner; @@ -25,10 +25,25 @@ abstract class Parser { @protected final Logger logger; + /// Parses [text] as a CSS identifier and returns the result. + /// + /// Throws a [SassFormatException] if parsing fails. + static String parseIdentifier(String text, {Logger logger}) => + Parser(text, logger: logger)._parseIdentifier(); + + @protected Parser(String contents, {url, Logger logger}) : scanner = SpanScanner(contents, sourceUrl: url), logger = logger ?? const Logger.stderr(); + String _parseIdentifier() { + return wrapSpanFormatException(() { + var result = identifier(); + scanner.expectDone(); + return result; + }); + } + // ## Tokens /// Consumes whitespace, including any comments. @@ -114,8 +129,8 @@ abstract class Parser { @protected String identifier({bool unit = false}) { // NOTE: this logic is largely duplicated in - // StylesheetParser._interpolatedIdentifier. Most changes here should be - // mirrored there. + // StylesheetParser._interpolatedIdentifier and isIdentifier in utils.dart. + // Most changes here should be mirrored there. var text = StringBuffer(); while (scanner.scanChar($dash)) { diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index d6d88def..cdbf9deb 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -13,6 +13,7 @@ import 'package:tuple/tuple.dart'; import '../ast/sass.dart'; import '../color_names.dart'; +import '../exception.dart'; import '../interpolation_buffer.dart'; import '../logger.dart'; import '../util/character.dart'; @@ -21,6 +22,14 @@ import '../value.dart'; import '../value/color.dart'; import 'parser.dart'; +/// Whether to parse `@use` rules. +/// +/// This is set to `false` on Dart Sass's master branch and `true` on the +/// `feature.use` branch. It allows us to avoid having separate development +/// tracks as much as possible without shipping `@use` support until we're +/// ready. +const _parseUse = true; + /// The base class for both the SCSS and indented syntax parsers. /// /// Having a base class that's separate from both parsers allows us to make @@ -33,6 +42,10 @@ import 'parser.dart'; /// private, except where they have to be public for subclasses to refer to /// them. abstract class StylesheetParser extends Parser { + /// Whether we've consumed a rule other than `@charset`, `@forward`, or + /// `@use`. + var _isUseAllowed = true; + /// Whether the parser is currently parsing the contents of a mixin /// declaration. var _inMixin = false; @@ -131,18 +144,21 @@ abstract class StylesheetParser extends Parser { case $plus: if (!indented || !lookingAtIdentifier(1)) return _styleRule(); + _isUseAllowed = false; var start = scanner.state; scanner.readChar(); return _includeRule(start); case $equal: if (!indented) return _styleRule(); + _isUseAllowed = false; var start = scanner.state; scanner.readChar(); whitespace(); return _mixinRule(start); default: + _isUseAllowed = false; return _inStyleRule || _inUnknownAtRule || _inMixin || _inContentBlock ? _declarationOrStyleRule() : _styleRule(); @@ -155,7 +171,13 @@ abstract class StylesheetParser extends Parser { var precedingComment = lastSilentComment; lastSilentComment = null; var start = scanner.state; + + String namespace; var name = variableName(); + if (scanner.scanChar($dot)) { + namespace = name; + name = identifier(); + } if (plainCss) { error("Sass variables aren't allowed in plain CSS.", @@ -176,6 +198,11 @@ abstract class StylesheetParser extends Parser { if (flag == 'default') { guarded = true; } else if (flag == 'global') { + if (namespace != null) { + error("!global isn't allowed for variables in other modules.", + scanner.spanFrom(flagStart)); + } + global = true; } else { error("Invalid flag name.", scanner.spanFrom(flagStart)); @@ -186,7 +213,10 @@ abstract class StylesheetParser extends Parser { expectStatementSeparator("variable declaration"); return VariableDeclaration(name, value, scanner.spanFrom(start), - guarded: guarded, global: global, comment: precedingComment); + namespace: namespace, + guarded: guarded, + global: global, + comment: precedingComment); } /// Consumes a style rule. @@ -455,10 +485,18 @@ abstract class StylesheetParser extends Parser { var name = interpolatedIdentifier(); whitespace(); + // We want to set [_isUseAllowed] to `false` *unless* we're parsing + // `@charset`, `@forward`, or `@use`. To avoid double-comparing the rule + // name, we always set it to `false` and then set it back to its previous + // value if we're parsing an allowed rule. + var wasUseAllowed = _isUseAllowed; + _isUseAllowed = false; + switch (name.asPlain) { case "at-root": return _atRootRule(start); case "charset": + _isUseAllowed = wasUseAllowed; if (!root) _disallowedAtRule(start); string(); return null; @@ -494,6 +532,11 @@ abstract class StylesheetParser extends Parser { return _disallowedAtRule(start); case "supports": return supportsRule(start); + case "use": + _isUseAllowed = wasUseAllowed; + if (!root || !_isUseAllowed) _disallowedAtRule(start); + if (!_parseUse) _disallowedAtRule(start); + return _useRule(start); case "warn": return _warnRule(start); case "while": @@ -933,7 +976,13 @@ abstract class StylesheetParser extends Parser { /// /// [start] should point before the `@`. IncludeRule _includeRule(LineScannerState start) { + String namespace; var name = identifier(); + if (scanner.scanChar($dot)) { + namespace = name; + name = identifier(); + } + whitespace(); var arguments = scanner.peekChar() == $lparen ? _argumentInvocation(mixin: true) @@ -965,7 +1014,8 @@ abstract class StylesheetParser extends Parser { var span = scanner.spanFrom(start, start).expand((content ?? arguments).span); - return IncludeRule(name, arguments, span, content: content); + return IncludeRule(name, arguments, span, + namespace: namespace, content: content); } /// Consumes a `@media` rule. @@ -1115,6 +1165,39 @@ relase. For details, see http://bit.ly/moz-document. (children, span) => SupportsRule(condition, children, span)); } + /// Consumes a `@use` rule. + /// + /// [start] should point before the `@`. + UseRule _useRule(LineScannerState start) { + var urlString = string(); + Uri url; + try { + url = Uri.parse(urlString); + } on FormatException catch (innerError) { + error("Invalid URL: ${innerError.message}", scanner.spanFrom(start)); + } + whitespace(); + + String namespace; + if (scanIdentifier("as")) { + whitespace(); + namespace = scanner.scanChar($asterisk) ? null : identifier(); + } else { + var basename = url.pathSegments.isEmpty ? "" : url.pathSegments.last; + var dot = basename.indexOf("."); + namespace = basename.substring(0, dot == -1 ? basename.length : dot); + + try { + namespace = Parser.parseIdentifier(namespace, logger: logger); + } on SassFormatException { + error('Invalid Sass identifier "$namespace"', scanner.spanFrom(start)); + } + } + expectStatementSeparator("@use rule"); + + return UseRule(url, namespace, scanner.spanFrom(start)); + } + /// Consumes a `@warn` rule. /// /// [start] should point before the `@`. @@ -2143,11 +2226,22 @@ relase. For details, see http://bit.ly/moz-document. /// Consumes a variable expression. VariableExpression _variable() { var start = scanner.state; - var name = variableName(); - if (!plainCss) return VariableExpression(name, scanner.spanFrom(start)); - error( - "Sass variables aren't allowed in plain CSS.", scanner.spanFrom(start)); + String namespace; + var name = variableName(); + if (scanner.peekChar() == $dot && scanner.peekChar(1) != $dot) { + scanner.readChar(); + namespace = name; + name = identifier(); + } + + if (plainCss) { + error("Sass variables aren't allowed in plain CSS.", + scanner.spanFrom(start)); + } + + return VariableExpression(name, scanner.spanFrom(start), + namespace: namespace); } /// Consumes a selector expression. @@ -2254,9 +2348,31 @@ relase. For details, see http://bit.ly/moz-document. if (specialFunction != null) return specialFunction; } - return scanner.peekChar() == $lparen - ? FunctionExpression(identifier, _argumentInvocation()) - : StringExpression(identifier); + switch (scanner.peekChar()) { + case $dot: + if (scanner.peekChar(1) == $dot) return StringExpression(identifier); + + var namespace = identifier.asPlain; + scanner.readChar(); + var beforeName = scanner.state; + var name = + Interpolation([this.identifier()], scanner.spanFrom(beforeName)); + + if (namespace == null) { + error("Interpolation isn't allowed in namespaces.", identifier.span); + } + + return FunctionExpression( + name, _argumentInvocation(), scanner.spanFrom(start), + namespace: namespace); + + case $lparen: + return FunctionExpression( + identifier, _argumentInvocation(), scanner.spanFrom(start)); + + default: + return StringExpression(identifier); + } } /// If [name] is the name of a function with special syntax, consumes it. @@ -2510,8 +2626,8 @@ relase. For details, see http://bit.ly/moz-document. var contents = _tryUrlContents(start); if (contents != null) return StringExpression(contents); - return FunctionExpression( - Interpolation(["url"], scanner.spanFrom(start)), _argumentInvocation()); + return FunctionExpression(Interpolation(["url"], scanner.spanFrom(start)), + _argumentInvocation(), scanner.spanFrom(start)); } /// Consumes tokens up to "{", "}", ";", or "!". @@ -2620,8 +2736,8 @@ relase. For details, see http://bit.ly/moz-document. /// /// Unlike [declarationValue], this allows interpolation. StringExpression _interpolatedDeclarationValue({bool allowEmpty = false}) { - // NOTE: this logic is largely duplicated in Parser.declarationValue. Most - // changes here should be mirrored there. + // NOTE: this logic is largely duplicated in Parser.declarationValue and + // isIdentifier in utils.dart. Most changes here should be mirrored there. var start = scanner.state; var buffer = InterpolationBuffer(); diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index c87cd5b4..eb5b4023 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -290,7 +290,9 @@ class _EvaluateVisitor deprecation: true); var expression = FunctionExpression( - Interpolation([function.text], _callableNode.span), invocation); + Interpolation([function.text], _callableNode.span), + invocation, + _callableNode.span); return await expression.accept(this); } @@ -1130,6 +1132,10 @@ class _EvaluateVisitor return null; } + Future visitUseRule(UseRule node) async { + return null; + } + Future visitWarnRule(WarnRule node) async { var value = await _addExceptionSpanAsync(node, () => node.expression.accept(this)); diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index d2276c0a..38f43a50 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/synchronize.dart for details. // -// Checksum: 262c642b0cda0a5ed4984e763e9970422405efaa +// Checksum: 08c3aaa09f3be71dd315bf36665e249983ce3d53 // // ignore_for_file: unused_import @@ -297,7 +297,9 @@ class _EvaluateVisitor deprecation: true); var expression = FunctionExpression( - Interpolation([function.text], _callableNode.span), invocation); + Interpolation([function.text], _callableNode.span), + invocation, + _callableNode.span); return expression.accept(this); } @@ -1125,6 +1127,10 @@ class _EvaluateVisitor return null; } + Value visitUseRule(UseRule node) { + return null; + } + Value visitWarnRule(WarnRule node) { var value = _addExceptionSpan(node, () => node.expression.accept(this)); _logger.warn( diff --git a/lib/src/visitor/find_imports.dart b/lib/src/visitor/find_imports.dart index 4aa7fa5f..b163cbdc 100644 --- a/lib/src/visitor/find_imports.dart +++ b/lib/src/visitor/find_imports.dart @@ -28,6 +28,10 @@ class _FindImportsVisitor extends RecursiveStatementVisitor { void visitInterpolation(Interpolation interpolation) {} void visitSupportsCondition(SupportsCondition condition) {} + void visitUseRule(UseRule node) { + _imports.add(DynamicImport(node.url.toString(), node.span)); + } + void visitImportRule(ImportRule node) { for (var import in node.imports) { if (import is DynamicImport) _imports.add(import); diff --git a/lib/src/visitor/interface/statement.dart b/lib/src/visitor/interface/statement.dart index dfcd6a5f..64d050f2 100644 --- a/lib/src/visitor/interface/statement.dart +++ b/lib/src/visitor/interface/statement.dart @@ -30,6 +30,7 @@ abstract class StatementVisitor { T visitStyleRule(StyleRule node); T visitStylesheet(Stylesheet node); T visitSupportsRule(SupportsRule node); + T visitUseRule(UseRule node); T visitVariableDeclaration(VariableDeclaration node); T visitWarnRule(WarnRule node); T visitWhileRule(WhileRule node); From 6354cdd02a71b441f2172b7aecaef99002fcde65 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 18 Jan 2019 04:02:40 -0500 Subject: [PATCH 3/6] Don't parse private module member references --- .../sass/statement/variable_declaration.dart | 7 ++++- lib/src/parse/stylesheet.dart | 27 ++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/src/ast/sass/statement/variable_declaration.dart b/lib/src/ast/sass/statement/variable_declaration.dart index 012c09e3..b9b69dfb 100644 --- a/lib/src/ast/sass/statement/variable_declaration.dart +++ b/lib/src/ast/sass/statement/variable_declaration.dart @@ -47,7 +47,12 @@ class VariableDeclaration implements Statement { SilentComment comment}) : isGuarded = guarded, isGlobal = global, - comment = comment; + comment = comment { + if (namespace != null && global) { + throw ArgumentError( + "Other modules' members can't be defined with !global."); + } + } /// Parses a variable declaration from [contents]. /// diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index cdbf9deb..36874d8c 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -176,7 +176,7 @@ abstract class StylesheetParser extends Parser { var name = variableName(); if (scanner.scanChar($dot)) { namespace = name; - name = identifier(); + name = _publicIdentifier(); } if (plainCss) { @@ -192,8 +192,8 @@ abstract class StylesheetParser extends Parser { var guarded = false; var global = false; + var flagStart = scanner.state; while (scanner.scanChar($exclamation)) { - var flagStart = scanner.state; var flag = identifier(); if (flag == 'default') { guarded = true; @@ -209,6 +209,7 @@ abstract class StylesheetParser extends Parser { } whitespace(); + flagStart = scanner.state; } expectStatementSeparator("variable declaration"); @@ -980,7 +981,7 @@ abstract class StylesheetParser extends Parser { var name = identifier(); if (scanner.scanChar($dot)) { namespace = name; - name = identifier(); + name = _publicIdentifier(); } whitespace(); @@ -2232,7 +2233,7 @@ relase. For details, see http://bit.ly/moz-document. if (scanner.peekChar() == $dot && scanner.peekChar(1) != $dot) { scanner.readChar(); namespace = name; - name = identifier(); + name = _publicIdentifier(); } if (plainCss) { @@ -2355,8 +2356,8 @@ relase. For details, see http://bit.ly/moz-document. var namespace = identifier.asPlain; scanner.readChar(); var beforeName = scanner.state; - var name = - Interpolation([this.identifier()], scanner.spanFrom(beforeName)); + var name = Interpolation( + [this._publicIdentifier()], scanner.spanFrom(beforeName)); if (namespace == null) { error("Interpolation isn't allowed in namespaces.", identifier.span); @@ -3204,6 +3205,20 @@ relase. For details, see http://bit.ly/moz-document. return result; } + /// Like [identifier], but rejects identifiers that begin with `_` or `-`. + String _publicIdentifier() { + var start = scanner.state; + var result = identifier(); + + var first = result.codeUnitAt(0); + if (first == $dash || first == $underscore) { + error("Private members can't be accessed from outside their modules.", + scanner.spanFrom(start)); + } + + return result; + } + // ## Abstract Methods /// Whether this is parsing the indented syntax. From 4b10c22bd4453363742cf794992167dcc7a6085d Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 13 Feb 2019 00:02:42 -0800 Subject: [PATCH 4/6] Add more explanatory error messages --- lib/src/parse/stylesheet.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 36874d8c..723f1567 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -535,8 +535,7 @@ abstract class StylesheetParser extends Parser { return supportsRule(start); case "use": _isUseAllowed = wasUseAllowed; - if (!root || !_isUseAllowed) _disallowedAtRule(start); - if (!_parseUse) _disallowedAtRule(start); + if (!root) _disallowedAtRule(start); return _useRule(start); case "warn": return _warnRule(start); @@ -1196,7 +1195,17 @@ relase. For details, see http://bit.ly/moz-document. } expectStatementSeparator("@use rule"); - return UseRule(url, namespace, scanner.spanFrom(start)); + var span = scanner.spanFrom(start); + if (!_parseUse) { + error( + "@use is coming soon, but it's not supported in this version of " + "Dart Sass.", + span); + } else if (!_isUseAllowed) { + error("@use rules must be written before any other rules.", span); + } + + return UseRule(url, namespace, span); } /// Consumes a `@warn` rule. From 776e2bfcc2e5737d2a29e2138404eb3f8e694fef Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 13 Feb 2019 00:36:36 -0800 Subject: [PATCH 5/6] Disable @use parsing --- lib/src/parse/stylesheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 723f1567..1ef7ef38 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -28,7 +28,7 @@ import 'parser.dart'; /// `feature.use` branch. It allows us to avoid having separate development /// tracks as much as possible without shipping `@use` support until we're /// ready. -const _parseUse = true; +const _parseUse = false; /// The base class for both the SCSS and indented syntax parsers. /// From e3a5eed4dac8552336d27a42b11c97125c738b51 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 13 Feb 2019 13:59:44 -0800 Subject: [PATCH 6/6] Revert "Run tests against sass-spec's feature.use branch" This reverts commit 4d23acd11948294f6d3e5c878c28eb728d67998c. --- tool/travis/sass-spec-ref.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/travis/sass-spec-ref.sh b/tool/travis/sass-spec-ref.sh index e58c7eb2..ae296f7f 100755 --- a/tool/travis/sass-spec-ref.sh +++ b/tool/travis/sass-spec-ref.sh @@ -7,7 +7,7 @@ # run. If we're running specs for a pull request which refers to a sass-spec # pull request, we'll run against the latter rather than sass-spec master. -default=feature.use +default=master if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then >&2 echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST."