From 63fe40446e8b7d72612ab15de4add2df4cc570ec Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 18 May 2022 15:07:17 -0700 Subject: [PATCH 1/3] Add support for arbitrary modifiers after @import See sass/sass#3285 --- CHANGELOG.md | 8 +- lib/src/ast/css/import.dart | 8 +- lib/src/ast/css/modifiable/import.dart | 11 +- lib/src/ast/sass/import/static.dart | 20 +-- lib/src/ast/sass/interpolation.dart | 23 +++ lib/src/ast/sass/supports_condition.dart | 7 +- .../ast/sass/supports_condition/anything.dart | 3 + .../sass/supports_condition/declaration.dart | 4 + .../ast/sass/supports_condition/function.dart | 3 + .../supports_condition/interpolation.dart | 3 + .../ast/sass/supports_condition/negation.dart | 12 ++ .../sass/supports_condition/operation.dart | 20 ++- lib/src/parse/css.dart | 4 +- lib/src/parse/stylesheet.dart | 134 ++++++++++++------ lib/src/visitor/async_evaluate.dart | 22 +-- lib/src/visitor/clone_css.dart | 3 +- lib/src/visitor/evaluate.dart | 24 +--- lib/src/visitor/recursive_ast.dart | 3 +- lib/src/visitor/serialize.dart | 12 +- pkg/sass_api/CHANGELOG.md | 7 + pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- 22 files changed, 208 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 837a3a0f1..9997f92c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ -## 1.51.1 +## 1.52.0 + +* Add support for arbitrary modifiers at the end of plain CSS imports, in + addition to the existing `supports()` and media queries. Sass now allows any + sequence of identifiers of functions after the URL of an import for forwards + compatibility with future additions to the CSS spec. * Fix an issue where source locations tracked through variable references could potentially become incorrect. + * Fix a bug where a loud comment in the source can break the source map when embedding the sources, when using the command-line interface or the legacy JS API. diff --git a/lib/src/ast/css/import.dart b/lib/src/ast/css/import.dart index 33db7c9b9..b8ad9e9e0 100644 --- a/lib/src/ast/css/import.dart +++ b/lib/src/ast/css/import.dart @@ -3,7 +3,6 @@ // https://opensource.org/licenses/MIT. import '../../visitor/interface/css.dart'; -import 'media_query.dart'; import 'node.dart'; import 'value.dart'; @@ -14,11 +13,8 @@ abstract class CssImport extends CssNode { /// This includes quotes. CssValue get url; - /// The supports condition attached to this import. - CssValue? get supports; - - /// The media query attached to this import. - List? get media; + /// The modifiers (such as media or supports queries) attached to this import. + CssValue? get modifiers; T accept(CssVisitor visitor) => visitor.visitCssImport(this); } diff --git a/lib/src/ast/css/modifiable/import.dart b/lib/src/ast/css/modifiable/import.dart index 9d705b72e..033424e78 100644 --- a/lib/src/ast/css/modifiable/import.dart +++ b/lib/src/ast/css/modifiable/import.dart @@ -6,7 +6,6 @@ import 'package:source_span/source_span.dart'; import '../../../visitor/interface/modifiable_css.dart'; import '../import.dart'; -import '../media_query.dart'; import '../value.dart'; import 'node.dart'; @@ -17,17 +16,11 @@ class ModifiableCssImport extends ModifiableCssNode implements CssImport { /// This includes quotes. final CssValue url; - /// The supports condition attached to this import. - final CssValue? supports; - - /// The media query attached to this import. - final List? media; + final CssValue? modifiers; final FileSpan span; - ModifiableCssImport(this.url, this.span, - {this.supports, Iterable? media}) - : media = media == null ? null : List.unmodifiable(media); + ModifiableCssImport(this.url, this.span, {this.modifiers}); T accept(ModifiableCssVisitor visitor) => visitor.visitCssImport(this); } diff --git a/lib/src/ast/sass/import/static.dart b/lib/src/ast/sass/import/static.dart index 6388a5cbd..69b1e3bb3 100644 --- a/lib/src/ast/sass/import/static.dart +++ b/lib/src/ast/sass/import/static.dart @@ -7,7 +7,6 @@ import 'package:source_span/source_span.dart'; import '../import.dart'; import '../interpolation.dart'; -import '../supports_condition.dart'; /// An import that produces a plain CSS `@import` rule. /// @@ -19,22 +18,13 @@ class StaticImport implements Import { /// This already contains quotes. final Interpolation url; - /// The supports condition attached to this import, or `null` if no condition - /// is attached. - final SupportsCondition? supports; - - /// The media query attached to this import, or `null` if no condition is - /// attached. - final Interpolation? media; + /// The modifiers (such as media or supports queries) attached to this import, + /// or `null` if none are attached. + final Interpolation? modifiers; final FileSpan span; - StaticImport(this.url, this.span, {this.supports, this.media}); + StaticImport(this.url, this.span, {this.modifiers}); - String toString() { - var buffer = StringBuffer(url); - if (supports != null) buffer.write(" supports($supports)"); - if (media != null) buffer.write(" $media"); - return buffer.toString(); - } + String toString() => "$url${modifiers == null ? '' : ' $modifiers'}"; } diff --git a/lib/src/ast/sass/interpolation.dart b/lib/src/ast/sass/interpolation.dart index dd3698348..cfb7b0d1f 100644 --- a/lib/src/ast/sass/interpolation.dart +++ b/lib/src/ast/sass/interpolation.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; +import '../../interpolation_buffer.dart'; import 'expression.dart'; import 'node.dart'; @@ -40,6 +41,28 @@ class Interpolation implements SassNode { return first is String ? first : ''; } + /// Creates a new [Interpolation] by concatenating a sequence of [String]s, + /// [Expression]s, or nested [Interpolation]s. + static Interpolation concat( + Iterable contents, + FileSpan span) { + var buffer = InterpolationBuffer(); + for (var element in contents) { + if (element is String) { + buffer.write(element); + } else if (element is Expression) { + buffer.add(element); + } else if (element is Interpolation) { + buffer.addInterpolation(element); + } else { + throw ArgumentError.value(contents, "contents", + "May only contains Strings, Expressions, or Interpolations."); + } + } + + return buffer.interpolation(span); + } + Interpolation(Iterable contents, this.span) : contents = List.unmodifiable(contents) { for (var i = 0; i < this.contents.length; i++) { diff --git a/lib/src/ast/sass/supports_condition.dart b/lib/src/ast/sass/supports_condition.dart index 53f96bb38..112ca9a30 100644 --- a/lib/src/ast/sass/supports_condition.dart +++ b/lib/src/ast/sass/supports_condition.dart @@ -4,10 +4,15 @@ import 'package:meta/meta.dart'; +import 'interpolation.dart'; import 'node.dart'; /// An abstract class for defining the condition a `@supports` rule selects. /// /// {@category AST} @sealed -abstract class SupportsCondition extends SassNode {} +abstract class SupportsCondition extends SassNode { + /// Returns an [Interpolation] that evaluates to the same value as this + /// [SupportsCondition]. + Interpolation toInterpolation(); +} diff --git a/lib/src/ast/sass/supports_condition/anything.dart b/lib/src/ast/sass/supports_condition/anything.dart index 4dbece4b2..48208619d 100644 --- a/lib/src/ast/sass/supports_condition/anything.dart +++ b/lib/src/ast/sass/supports_condition/anything.dart @@ -21,5 +21,8 @@ class SupportsAnything implements SupportsCondition { SupportsAnything(this.contents, this.span); + Interpolation toInterpolation() => + Interpolation.concat(['(', contents, ')'], span); + String toString() => "($contents)"; } diff --git a/lib/src/ast/sass/supports_condition/declaration.dart b/lib/src/ast/sass/supports_condition/declaration.dart index d29d717c9..c60a28bad 100644 --- a/lib/src/ast/sass/supports_condition/declaration.dart +++ b/lib/src/ast/sass/supports_condition/declaration.dart @@ -7,6 +7,7 @@ import 'package:source_span/source_span.dart'; import '../expression.dart'; import '../expression/string.dart'; +import '../interpolation.dart'; import '../supports_condition.dart'; /// A condition that selects for browsers where a given declaration is @@ -42,5 +43,8 @@ class SupportsDeclaration implements SupportsCondition { SupportsDeclaration(this.name, this.value, this.span); + Interpolation toInterpolation() => Interpolation.concat( + ['(', name, isCustomProperty ? ':' : ': ', value, ')'], span); + String toString() => "($name: $value)"; } diff --git a/lib/src/ast/sass/supports_condition/function.dart b/lib/src/ast/sass/supports_condition/function.dart index 73bdb9bda..1c239c9dc 100644 --- a/lib/src/ast/sass/supports_condition/function.dart +++ b/lib/src/ast/sass/supports_condition/function.dart @@ -23,5 +23,8 @@ class SupportsFunction implements SupportsCondition { SupportsFunction(this.name, this.arguments, this.span); + Interpolation toInterpolation() => + Interpolation.concat([name, '(', arguments, ')'], span); + String toString() => "$name($arguments)"; } diff --git a/lib/src/ast/sass/supports_condition/interpolation.dart b/lib/src/ast/sass/supports_condition/interpolation.dart index 9fbd85829..9f0e3a5aa 100644 --- a/lib/src/ast/sass/supports_condition/interpolation.dart +++ b/lib/src/ast/sass/supports_condition/interpolation.dart @@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../expression.dart'; +import '../interpolation.dart'; import '../supports_condition.dart'; /// An interpolated condition. @@ -20,5 +21,7 @@ class SupportsInterpolation implements SupportsCondition { SupportsInterpolation(this.expression, this.span); + Interpolation toInterpolation() => Interpolation([expression], span); + String toString() => "#{$expression}"; } diff --git a/lib/src/ast/sass/supports_condition/negation.dart b/lib/src/ast/sass/supports_condition/negation.dart index 4187f3793..5550a0446 100644 --- a/lib/src/ast/sass/supports_condition/negation.dart +++ b/lib/src/ast/sass/supports_condition/negation.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; +import '../interpolation.dart'; import '../supports_condition.dart'; import 'operation.dart'; @@ -20,6 +21,17 @@ class SupportsNegation implements SupportsCondition { SupportsNegation(this.condition, this.span); + Interpolation toInterpolation() { + var needsParens = + condition is SupportsNegation || condition is SupportsOperation; + return Interpolation.concat([ + "not ", + if (needsParens) "(", + condition.toInterpolation(), + if (needsParens) ")" + ], span); + } + String toString() { if (condition is SupportsNegation || condition is SupportsOperation) { return "not ($condition)"; diff --git a/lib/src/ast/sass/supports_condition/operation.dart b/lib/src/ast/sass/supports_condition/operation.dart index 3e4fc5113..13249dbad 100644 --- a/lib/src/ast/sass/supports_condition/operation.dart +++ b/lib/src/ast/sass/supports_condition/operation.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; +import '../interpolation.dart'; import '../supports_condition.dart'; import 'negation.dart'; @@ -34,10 +35,25 @@ class SupportsOperation implements SupportsCondition { } } + Interpolation toInterpolation() => Interpolation.concat([ + ..._parenthesizeInterpolation(left), + " $operator ", + ..._parenthesizeInterpolation(right) + ], span); + + /// Returns a list that can be passed to [Interpolation.concat], with + /// parentheses around [condition] if necessary. + List + _parenthesizeInterpolation(SupportsCondition condition) => condition + is SupportsNegation || + (condition is SupportsOperation && condition.operator == operator) + ? ["(", condition.toInterpolation(), ")"] + : [condition.toInterpolation()]; + String toString() => - "${_parenthesize(left)} $operator ${_parenthesize(right)}"; + "${_parenthesizeString(left)} $operator ${_parenthesizeString(right)}"; - String _parenthesize(SupportsCondition condition) => + String _parenthesizeString(SupportsCondition condition) => condition is SupportsNegation || (condition is SupportsOperation && condition.operator == operator) ? "($condition)" diff --git a/lib/src/parse/css.dart b/lib/src/parse/css.dart index ba09278e9..bffdaa7c9 100644 --- a/lib/src/parse/css.dart +++ b/lib/src/parse/css.dart @@ -94,11 +94,11 @@ class CssParser extends ScssParser { var urlSpan = scanner.spanFrom(urlStart); whitespace(); - var queries = tryImportQueries(); + var modifiers = tryImportModifiers(); expectStatementSeparator("@import rule"); return ImportRule([ StaticImport(Interpolation([url], urlSpan), scanner.spanFrom(urlStart), - supports: queries?.item1, media: queries?.item2) + modifiers: modifiers) ], scanner.spanFrom(start)); } diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index f106fc033..fd3a3095b 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -1086,20 +1086,20 @@ abstract class StylesheetParser extends Parser { if (next == $u || next == $U) { var url = dynamicUrl(); whitespace(); - var queries = tryImportQueries(); + var modifiers = tryImportModifiers(); return StaticImport(Interpolation([url], scanner.spanFrom(start)), scanner.spanFrom(start), - supports: queries?.item1, media: queries?.item2); + modifiers: modifiers); } var url = string(); var urlSpan = scanner.spanFrom(start); whitespace(); - var queries = tryImportQueries(); - if (isPlainImportUrl(url) || queries != null) { + var modifiers = tryImportModifiers(); + if (isPlainImportUrl(url) || modifiers != null) { return StaticImport( Interpolation([urlSpan.text], urlSpan), scanner.spanFrom(start), - supports: queries?.item1, media: queries?.item2); + modifiers: modifiers); } else { try { return DynamicImport(parseImportUrl(url), urlSpan); @@ -1135,54 +1135,100 @@ abstract class StylesheetParser extends Parser { return url.startsWith("http://") || url.startsWith("https://"); } - /// Consumes a supports condition and/or a media query after an `@import`. + /// Consumes a sequence of modifiers (such as media or supports queries) + /// after an import argument. /// - /// Returns `null` if neither type of query can be found. - Tuple2? tryImportQueries() { - SupportsCondition? supports; - if (scanIdentifier("supports")) { - scanner.expectChar($lparen); - var start = scanner.state; - if (scanIdentifier("not")) { - whitespace(); - supports = SupportsNegation( - _supportsConditionInParens(), scanner.spanFrom(start)); - } else if (scanner.peekChar() == $lparen) { - supports = _supportsCondition(); - } else { - if (_lookingAtInterpolatedIdentifier()) { - var identifier = interpolatedIdentifier(); - if (identifier.asPlain?.toLowerCase() == "not") { - error('"not" is not a valid identifier here.', identifier.span); - } + /// Returns `null` if there are no modifiers. + Interpolation? tryImportModifiers() { + // Exit before allocating anything if we're not looking at any modifiers, as + // is the most common case. + if (!_lookingAtInterpolatedIdentifier() && scanner.peekChar() != $lparen) { + return null; + } - if (scanner.scanChar($lparen)) { - var arguments = _interpolatedDeclarationValue( - allowEmpty: true, allowSemicolon: true); - scanner.expectChar($rparen); - supports = SupportsFunction( - identifier, arguments, scanner.spanFrom(start)); + var start = scanner.state; + var buffer = InterpolationBuffer(); + while (true) { + if (_lookingAtInterpolatedIdentifier()) { + if (!buffer.isEmpty) buffer.writeCharCode($space); + + var identifier = interpolatedIdentifier(); + buffer.addInterpolation(identifier); + + var name = identifier.asPlain?.toLowerCase(); + if (name != "and" && scanner.scanChar($lparen)) { + if (name == "supports") { + var query = _importSupportsQuery(); + if (query is! SupportsDeclaration) buffer.writeCharCode($lparen); + buffer.addInterpolation(query.toInterpolation()); + if (query is! SupportsDeclaration) buffer.writeCharCode($rparen); } else { - // Backtrack to parse a variable declaration - scanner.state = start; + buffer.writeCharCode($lparen); + buffer.addInterpolation(_interpolatedDeclarationValue( + allowEmpty: true, allowSemicolon: true)); + buffer.writeCharCode($rparen); + } + + scanner.expectChar($rparen); + whitespace(); + } else { + whitespace(); + if (scanner.scanChar($comma)) { + buffer.write(", "); + buffer.addInterpolation(_mediaQueryList()); + return buffer.interpolation(scanner.spanFrom(start)); } } - if (supports == null) { - var name = expression(); - scanner.expectChar($colon); - supports = _supportsDeclarationValue(name, start); - } + } else if (scanner.peekChar() == $lparen) { + if (!buffer.isEmpty) buffer.writeCharCode($space); + buffer.addInterpolation(_mediaQueryList()); + return buffer.interpolation(scanner.spanFrom(start)); + } else { + return buffer.interpolation(scanner.spanFrom(start)); } - scanner.expectChar($rparen); + } + } + + /// Consumes the contents of a `supports()` function after an `@import` rule + /// (but not the function name or parentheses). + SupportsCondition _importSupportsQuery() { + if (scanIdentifier("not")) { whitespace(); + var start = scanner.state; + return SupportsNegation( + _supportsConditionInParens(), scanner.spanFrom(start)); + } else if (scanner.peekChar() == $lparen) { + return _supportsCondition(); + } else { + var function = _tryImportSupportsFunction(); + if (function != null) return function; + + var start = scanner.state; + var name = expression(); + scanner.expectChar($colon); + return _supportsDeclarationValue(name, start); + } + } + + /// Consumes a function call within a `supports()` function after an + /// `@import` if available. + SupportsFunction? _tryImportSupportsFunction() { + if (!_lookingAtInterpolatedIdentifier()) return null; + + var start = scanner.state; + var name = interpolatedIdentifier(); + assert(name.asPlain != "not"); + + if (!scanner.scanChar($lparen)) { + scanner.state = start; + return null; } - var media = - _lookingAtInterpolatedIdentifier() || scanner.peekChar() == $lparen - ? _mediaQueryList() - : null; - if (supports == null && media == null) return null; - return Tuple2(supports, media); + var value = + _interpolatedDeclarationValue(allowEmpty: true, allowSemicolon: true); + scanner.expectChar($rparen); + + return SupportsFunction(name, value, scanner.spanFrom(start)); } /// Consumes an `@include` rule. diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index df184909a..9c0e7aad0 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -1663,20 +1663,10 @@ class _EvaluateVisitor // NOTE: this logic is largely duplicated in [visitCssImport]. Most changes // here should be mirrored there. - var url = await _interpolationToValue(import.url); - var supports = await import.supports.andThen((supports) async { - var arg = supports is SupportsDeclaration - ? "${await _evaluateToCss(supports.name)}:" - "${supports.isCustomProperty ? '' : ' '}" - "${await _evaluateToCss(supports.value)}" - : await supports.andThen(_visitSupportsCondition); - return CssValue("supports($arg)", supports.span); - }); - var rawMedia = import.media; - var mediaQuery = await rawMedia.andThen(_visitMediaQueries); - - var node = ModifiableCssImport(url, import.span, - supports: supports, media: mediaQuery); + var node = ModifiableCssImport( + await _interpolationToValue(import.url), import.span, + modifiers: await import.modifiers + .andThen>?>(_interpolationToValue)); if (_parent != _root) { _parent.addChild(node); @@ -2923,8 +2913,8 @@ class _EvaluateVisitor // NOTE: this logic is largely duplicated in [_visitStaticImport]. Most // changes here should be mirrored there. - var modifiableNode = ModifiableCssImport(node.url, node.span, - supports: node.supports, media: node.media); + var modifiableNode = + ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); if (_parent != _root) { _parent.addChild(modifiableNode); } else if (_endOfImports == _root.children.length) { diff --git a/lib/src/visitor/clone_css.dart b/lib/src/visitor/clone_css.dart index ba4bf7333..73b0f8b76 100644 --- a/lib/src/visitor/clone_css.dart +++ b/lib/src/visitor/clone_css.dart @@ -48,8 +48,7 @@ class _CloneCssVisitor implements CssVisitor { valueSpanForMap: node.valueSpanForMap); ModifiableCssImport visitCssImport(CssImport node) => - ModifiableCssImport(node.url, node.span, - supports: node.supports, media: node.media); + ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); ModifiableCssKeyframeBlock visitCssKeyframeBlock(CssKeyframeBlock node) => _visitChildren( diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 18663b7e4..a89b9a7f1 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/grind/synchronize.dart for details. // -// Checksum: 45277707f5ab21408f3abb8f249ed7115e0a3c0f +// Checksum: 68136a98fc33b94bd58d0658e325cf90bdfba006 // // ignore_for_file: unused_import @@ -1660,20 +1660,10 @@ class _EvaluateVisitor // NOTE: this logic is largely duplicated in [visitCssImport]. Most changes // here should be mirrored there. - var url = _interpolationToValue(import.url); - var supports = import.supports.andThen((supports) { - var arg = supports is SupportsDeclaration - ? "${_evaluateToCss(supports.name)}:" - "${supports.isCustomProperty ? '' : ' '}" - "${_evaluateToCss(supports.value)}" - : supports.andThen(_visitSupportsCondition); - return CssValue("supports($arg)", supports.span); - }); - var rawMedia = import.media; - var mediaQuery = rawMedia.andThen(_visitMediaQueries); - - var node = ModifiableCssImport(url, import.span, - supports: supports, media: mediaQuery); + var node = ModifiableCssImport( + _interpolationToValue(import.url), import.span, + modifiers: + import.modifiers.andThen?>(_interpolationToValue)); if (_parent != _root) { _parent.addChild(node); @@ -2903,8 +2893,8 @@ class _EvaluateVisitor // NOTE: this logic is largely duplicated in [_visitStaticImport]. Most // changes here should be mirrored there. - var modifiableNode = ModifiableCssImport(node.url, node.span, - supports: node.supports, media: node.media); + var modifiableNode = + ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); if (_parent != _root) { _parent.addChild(modifiableNode); } else if (_endOfImports == _root.children.length) { diff --git a/lib/src/visitor/recursive_ast.dart b/lib/src/visitor/recursive_ast.dart index 21e37ee61..fbc8589b5 100644 --- a/lib/src/visitor/recursive_ast.dart +++ b/lib/src/visitor/recursive_ast.dart @@ -93,8 +93,7 @@ abstract class RecursiveAstVisitor extends RecursiveStatementVisitor for (var import in node.imports) { if (import is StaticImport) { visitInterpolation(import.url); - import.supports.andThen(visitSupportsCondition); - import.media.andThen(visitInterpolation); + import.modifiers.andThen(visitInterpolation); } } } diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 669c624b0..1f1c0c9ec 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -237,16 +237,10 @@ class _SerializeVisitor _writeOptionalSpace(); _for(node.url, () => _writeImportUrl(node.url.value)); - var supports = node.supports; - if (supports != null) { + var modifiers = node.modifiers; + if (modifiers != null) { _writeOptionalSpace(); - _write(supports); - } - - var media = node.media; - if (media != null) { - _writeOptionalSpace(); - _writeBetween(media, _commaSeparator, _visitMediaQuery); + _buffer.write(modifiers); } }); } diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index a602c9374..27195ac04 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.0.0-beta.45 + +* **Breaking change:** Replace `StaticImport.supports` and `StaticImport.media` + with a unified `StaticImport.modifiers` field. Same for `CssImport`. + +* Add `SupportsCondition.toInterpolation()`. + ## 1.0.0-beta.44 * No user-visible changes. diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 697d971db..e7e03f8d8 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 1.0.0-beta.44 +version: 1.0.0-beta.45 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: - sass: 1.51.1 + sass: 1.52.0 dependency_overrides: sass: {path: ../..} diff --git a/pubspec.yaml b/pubspec.yaml index acd9134be..51c86bf5e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.51.1-dev +version: 1.52.0 description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass From ad31ae34efd6ed8c0430048d5ff0ba44fd280947 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Wed, 18 May 2022 16:05:03 -0700 Subject: [PATCH 2/3] Run `dart run dartdoc` instead of `dartdoc` --- .github/workflows/ci.yml | 4 ++-- pkg/sass_api/pubspec.yaml | 3 +++ pubspec.yaml | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c860153c..17fc581d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,11 +183,11 @@ jobs: - uses: dart-lang/setup-dart@v1 - run: dart pub get - name: dartdoc sass - run: dartdoc --quiet --no-generate-docs + run: dart run dartdoc --quiet --no-generate-docs --errors ambiguous-doc-reference,broken-link,deprecated --errors unknown-directive,unknown-macro,unresolved-doc-reference - name: dartdoc sass_api - run: cd pkg/sass_api && dartdoc --quiet --no-generate-docs + run: cd pkg/sass_api && dart run dartdoc --quiet --no-generate-docs --errors ambiguous-doc-reference,broken-link,deprecated --errors unknown-directive,unknown-macro,unresolved-doc-reference diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index e7e03f8d8..528c96904 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -12,5 +12,8 @@ environment: dependencies: sass: 1.52.0 +dev_dependencies: + dartdoc: ^5.0.0 + dependency_overrides: sass: {path: ../..} diff --git a/pubspec.yaml b/pubspec.yaml index 51c86bf5e..013ec6ff0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,11 +32,12 @@ dependencies: http: ^0.13.3 dev_dependencies: - analyzer: ^2.4.0 + analyzer: ^3.0.0 archive: ^3.1.2 cli_pkg: ^2.1.0 crypto: ^3.0.0 dart_style: ^2.0.0 + dartdoc: ^5.0.0 grinder: ^0.9.0 node_preamble: ^2.0.0 lints: ^1.0.0 From aba1aa8177d3c2ce22b501b55451618a7c172833 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 19 May 2022 15:52:28 -0700 Subject: [PATCH 3/3] Properly handle calc() in supports() --- lib/src/ast/sass.dart | 1 + lib/src/ast/sass/expression/supports.dart | 31 +++++++++++++++++++ lib/src/ast/sass/interpolation.dart | 2 +- lib/src/ast/sass/supports_condition.dart | 7 +---- .../ast/sass/supports_condition/anything.dart | 3 -- .../sass/supports_condition/declaration.dart | 4 --- .../ast/sass/supports_condition/function.dart | 3 -- .../supports_condition/interpolation.dart | 3 -- .../ast/sass/supports_condition/negation.dart | 12 ------- .../sass/supports_condition/operation.dart | 20 ++---------- lib/src/parse/stylesheet.dart | 2 +- lib/src/visitor/async_evaluate.dart | 5 +++ lib/src/visitor/evaluate.dart | 5 ++- lib/src/visitor/interface/expression.dart | 1 + lib/src/visitor/recursive_ast.dart | 4 +++ pkg/sass_api/CHANGELOG.md | 2 +- 16 files changed, 52 insertions(+), 53 deletions(-) create mode 100644 lib/src/ast/sass/expression/supports.dart diff --git a/lib/src/ast/sass.dart b/lib/src/ast/sass.dart index 949508e0f..aa5ebbb79 100644 --- a/lib/src/ast/sass.dart +++ b/lib/src/ast/sass.dart @@ -25,6 +25,7 @@ export 'sass/expression/number.dart'; export 'sass/expression/parenthesized.dart'; export 'sass/expression/selector.dart'; export 'sass/expression/string.dart'; +export 'sass/expression/supports.dart'; export 'sass/expression/unary_operation.dart'; export 'sass/expression/value.dart'; export 'sass/expression/variable.dart'; diff --git a/lib/src/ast/sass/expression/supports.dart b/lib/src/ast/sass/expression/supports.dart new file mode 100644 index 000000000..3aa28222c --- /dev/null +++ b/lib/src/ast/sass/expression/supports.dart @@ -0,0 +1,31 @@ +// Copyright 2022 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:meta/meta.dart'; +import 'package:source_span/source_span.dart'; + +import '../../../visitor/interface/expression.dart'; +import '../expression.dart'; +import '../supports_condition.dart'; + +/// An expression-level `@supports` condition. +/// +/// This appears only in the modifiers that come after a plain-CSS `@import`. It +/// doesn't include the function name wrapping the condition. +/// +/// {@category AST} +@sealed +class SupportsExpression implements Expression { + /// The condition itself. + final SupportsCondition condition; + + FileSpan get span => condition.span; + + SupportsExpression(this.condition); + + T accept(ExpressionVisitor visitor) => + visitor.visitSupportsExpression(this); + + String toString() => condition.toString(); +} diff --git a/lib/src/ast/sass/interpolation.dart b/lib/src/ast/sass/interpolation.dart index cfb7b0d1f..d3a55971a 100644 --- a/lib/src/ast/sass/interpolation.dart +++ b/lib/src/ast/sass/interpolation.dart @@ -57,7 +57,7 @@ class Interpolation implements SassNode { } else { throw ArgumentError.value(contents, "contents", "May only contains Strings, Expressions, or Interpolations."); - } + } } return buffer.interpolation(span); diff --git a/lib/src/ast/sass/supports_condition.dart b/lib/src/ast/sass/supports_condition.dart index 112ca9a30..53f96bb38 100644 --- a/lib/src/ast/sass/supports_condition.dart +++ b/lib/src/ast/sass/supports_condition.dart @@ -4,15 +4,10 @@ import 'package:meta/meta.dart'; -import 'interpolation.dart'; import 'node.dart'; /// An abstract class for defining the condition a `@supports` rule selects. /// /// {@category AST} @sealed -abstract class SupportsCondition extends SassNode { - /// Returns an [Interpolation] that evaluates to the same value as this - /// [SupportsCondition]. - Interpolation toInterpolation(); -} +abstract class SupportsCondition extends SassNode {} diff --git a/lib/src/ast/sass/supports_condition/anything.dart b/lib/src/ast/sass/supports_condition/anything.dart index 48208619d..4dbece4b2 100644 --- a/lib/src/ast/sass/supports_condition/anything.dart +++ b/lib/src/ast/sass/supports_condition/anything.dart @@ -21,8 +21,5 @@ class SupportsAnything implements SupportsCondition { SupportsAnything(this.contents, this.span); - Interpolation toInterpolation() => - Interpolation.concat(['(', contents, ')'], span); - String toString() => "($contents)"; } diff --git a/lib/src/ast/sass/supports_condition/declaration.dart b/lib/src/ast/sass/supports_condition/declaration.dart index c60a28bad..d29d717c9 100644 --- a/lib/src/ast/sass/supports_condition/declaration.dart +++ b/lib/src/ast/sass/supports_condition/declaration.dart @@ -7,7 +7,6 @@ import 'package:source_span/source_span.dart'; import '../expression.dart'; import '../expression/string.dart'; -import '../interpolation.dart'; import '../supports_condition.dart'; /// A condition that selects for browsers where a given declaration is @@ -43,8 +42,5 @@ class SupportsDeclaration implements SupportsCondition { SupportsDeclaration(this.name, this.value, this.span); - Interpolation toInterpolation() => Interpolation.concat( - ['(', name, isCustomProperty ? ':' : ': ', value, ')'], span); - String toString() => "($name: $value)"; } diff --git a/lib/src/ast/sass/supports_condition/function.dart b/lib/src/ast/sass/supports_condition/function.dart index 1c239c9dc..73bdb9bda 100644 --- a/lib/src/ast/sass/supports_condition/function.dart +++ b/lib/src/ast/sass/supports_condition/function.dart @@ -23,8 +23,5 @@ class SupportsFunction implements SupportsCondition { SupportsFunction(this.name, this.arguments, this.span); - Interpolation toInterpolation() => - Interpolation.concat([name, '(', arguments, ')'], span); - String toString() => "$name($arguments)"; } diff --git a/lib/src/ast/sass/supports_condition/interpolation.dart b/lib/src/ast/sass/supports_condition/interpolation.dart index 9f0e3a5aa..9fbd85829 100644 --- a/lib/src/ast/sass/supports_condition/interpolation.dart +++ b/lib/src/ast/sass/supports_condition/interpolation.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../expression.dart'; -import '../interpolation.dart'; import '../supports_condition.dart'; /// An interpolated condition. @@ -21,7 +20,5 @@ class SupportsInterpolation implements SupportsCondition { SupportsInterpolation(this.expression, this.span); - Interpolation toInterpolation() => Interpolation([expression], span); - String toString() => "#{$expression}"; } diff --git a/lib/src/ast/sass/supports_condition/negation.dart b/lib/src/ast/sass/supports_condition/negation.dart index 5550a0446..4187f3793 100644 --- a/lib/src/ast/sass/supports_condition/negation.dart +++ b/lib/src/ast/sass/supports_condition/negation.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; -import '../interpolation.dart'; import '../supports_condition.dart'; import 'operation.dart'; @@ -21,17 +20,6 @@ class SupportsNegation implements SupportsCondition { SupportsNegation(this.condition, this.span); - Interpolation toInterpolation() { - var needsParens = - condition is SupportsNegation || condition is SupportsOperation; - return Interpolation.concat([ - "not ", - if (needsParens) "(", - condition.toInterpolation(), - if (needsParens) ")" - ], span); - } - String toString() { if (condition is SupportsNegation || condition is SupportsOperation) { return "not ($condition)"; diff --git a/lib/src/ast/sass/supports_condition/operation.dart b/lib/src/ast/sass/supports_condition/operation.dart index 13249dbad..3e4fc5113 100644 --- a/lib/src/ast/sass/supports_condition/operation.dart +++ b/lib/src/ast/sass/supports_condition/operation.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; -import '../interpolation.dart'; import '../supports_condition.dart'; import 'negation.dart'; @@ -35,25 +34,10 @@ class SupportsOperation implements SupportsCondition { } } - Interpolation toInterpolation() => Interpolation.concat([ - ..._parenthesizeInterpolation(left), - " $operator ", - ..._parenthesizeInterpolation(right) - ], span); - - /// Returns a list that can be passed to [Interpolation.concat], with - /// parentheses around [condition] if necessary. - List - _parenthesizeInterpolation(SupportsCondition condition) => condition - is SupportsNegation || - (condition is SupportsOperation && condition.operator == operator) - ? ["(", condition.toInterpolation(), ")"] - : [condition.toInterpolation()]; - String toString() => - "${_parenthesizeString(left)} $operator ${_parenthesizeString(right)}"; + "${_parenthesize(left)} $operator ${_parenthesize(right)}"; - String _parenthesizeString(SupportsCondition condition) => + String _parenthesize(SupportsCondition condition) => condition is SupportsNegation || (condition is SupportsOperation && condition.operator == operator) ? "($condition)" diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index fd3a3095b..291bcd09b 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -1160,7 +1160,7 @@ abstract class StylesheetParser extends Parser { if (name == "supports") { var query = _importSupportsQuery(); if (query is! SupportsDeclaration) buffer.writeCharCode($lparen); - buffer.addInterpolation(query.toInterpolation()); + buffer.add(SupportsExpression(query)); if (query is! SupportsDeclaration) buffer.writeCharCode($rparen); } else { buffer.writeCharCode($lparen); diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 9c0e7aad0..b50f5480a 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -2843,6 +2843,11 @@ class _EvaluateVisitor return result; } + Future visitSupportsExpression( + SupportsExpression expression) async => + SassString(await _visitSupportsCondition(expression.condition), + quotes: false); + // ## Plain CSS // These methods are used when evaluating CSS syntax trees from `@import`ed diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index a89b9a7f1..d405aa8ae 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/grind/synchronize.dart for details. // -// Checksum: 68136a98fc33b94bd58d0658e325cf90bdfba006 +// Checksum: fdd5d16c0ec34a4e0e4e2d5bdbe3d764e788a43f // // ignore_for_file: unused_import @@ -2823,6 +2823,9 @@ class _EvaluateVisitor return result; } + SassString visitSupportsExpression(SupportsExpression expression) => + SassString(_visitSupportsCondition(expression.condition), quotes: false); + // ## Plain CSS // These methods are used when evaluating CSS syntax trees from `@import`ed diff --git a/lib/src/visitor/interface/expression.dart b/lib/src/visitor/interface/expression.dart index f9b74aba5..0a642ec64 100644 --- a/lib/src/visitor/interface/expression.dart +++ b/lib/src/visitor/interface/expression.dart @@ -24,6 +24,7 @@ abstract class ExpressionVisitor { T visitParenthesizedExpression(ParenthesizedExpression node); T visitSelectorExpression(SelectorExpression node); T visitStringExpression(StringExpression node); + T visitSupportsExpression(SupportsExpression node); T visitUnaryOperationExpression(UnaryOperationExpression node); T visitValueExpression(ValueExpression node); T visitVariableExpression(VariableExpression node); diff --git a/lib/src/visitor/recursive_ast.dart b/lib/src/visitor/recursive_ast.dart index fbc8589b5..c4ce85ede 100644 --- a/lib/src/visitor/recursive_ast.dart +++ b/lib/src/visitor/recursive_ast.dart @@ -207,6 +207,10 @@ abstract class RecursiveAstVisitor extends RecursiveStatementVisitor visitInterpolation(node.text); } + void visitSupportsExpression(SupportsExpression node) { + visitSupportsCondition(node.condition); + } + void visitUnaryOperationExpression(UnaryOperationExpression node) { node.operand.accept(this); } diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 27195ac04..b67329b84 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -3,7 +3,7 @@ * **Breaking change:** Replace `StaticImport.supports` and `StaticImport.media` with a unified `StaticImport.modifiers` field. Same for `CssImport`. -* Add `SupportsCondition.toInterpolation()`. +* Add `SupportsExpression`. ## 1.0.0-beta.44