From fa0d2fb55aed9e72c9d65dab0db55a779777ff33 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Thu, 19 May 2022 16:53:30 -0700 Subject: [PATCH] Add support for arbitrary modifiers after @import (#1695) See sass/sass#3285 --- .github/workflows/ci.yml | 4 +- CHANGELOG.md | 8 +- lib/src/ast/css/import.dart | 8 +- lib/src/ast/css/modifiable/import.dart | 11 +- lib/src/ast/sass.dart | 1 + lib/src/ast/sass/expression/supports.dart | 31 +++++ lib/src/ast/sass/import/static.dart | 20 +--- lib/src/ast/sass/interpolation.dart | 23 ++++ lib/src/parse/css.dart | 4 +- lib/src/parse/stylesheet.dart | 134 +++++++++++++++------- lib/src/visitor/async_evaluate.dart | 27 ++--- lib/src/visitor/clone_css.dart | 3 +- lib/src/visitor/evaluate.dart | 27 ++--- lib/src/visitor/interface/expression.dart | 1 + lib/src/visitor/recursive_ast.dart | 7 +- lib/src/visitor/serialize.dart | 12 +- pkg/sass_api/CHANGELOG.md | 7 ++ pkg/sass_api/pubspec.yaml | 7 +- pubspec.yaml | 5 +- 19 files changed, 211 insertions(+), 129 deletions(-) create mode 100644 lib/src/ast/sass/expression/supports.dart 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/CHANGELOG.md b/CHANGELOG.md index 0e23c0d34..b33341346 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.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/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..d3a55971a 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/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..291bcd09b 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.add(SupportsExpression(query)); + 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..b50f5480a 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); @@ -2853,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 @@ -2923,8 +2918,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..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: 45277707f5ab21408f3abb8f249ed7115e0a3c0f +// Checksum: fdd5d16c0ec34a4e0e4e2d5bdbe3d764e788a43f // // 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); @@ -2833,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 @@ -2903,8 +2896,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/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 21e37ee61..c4ce85ede 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); } } } @@ -208,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/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..b67329b84 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 `SupportsExpression`. + ## 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..528c96904 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,10 @@ environment: sdk: '>=2.12.0 <3.0.0' dependencies: - sass: 1.51.1 + sass: 1.52.0 + +dev_dependencies: + dartdoc: ^5.0.0 dependency_overrides: sass: {path: ../..} diff --git a/pubspec.yaml b/pubspec.yaml index acd9134be..013ec6ff0 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 @@ -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