diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ee23575b..b4bb2f801 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,8 +14,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dart-lang/setup-dart@v1 - - run: dart format --fix . + - uses: ./.github/util/initialize + with: {github-token: "${{ github.token }}"} + - run: dart format . + - run: dart fix --apply - run: git diff --exit-code static_analysis: diff --git a/bin/sass.dart b/bin/sass.dart index 78ac31370..dc0b079c8 100644 --- a/bin/sass.dart +++ b/bin/sass.dart @@ -49,20 +49,29 @@ Future main(List args) async { options.futureDeprecations; options.fatalDeprecations; - var graph = StylesheetGraph(ImportCache( + var graph = StylesheetGraph( + ImportCache( importers: [...options.pkgImporters, FilesystemImporter.noLoadPath], - loadPaths: options.loadPaths)); + loadPaths: options.loadPaths, + ), + ); if (options.watch) { await watch(options, graph); return; } - await compileStylesheets(options, graph, options.sourcesToDestinations, - ifModified: options.update); + await compileStylesheets( + options, + graph, + options.sourcesToDestinations, + ifModified: options.update, + ); } on UsageException catch (error) { print("${error.message}\n"); - print("Usage: sass [output.css]\n" - " sass : : \n"); + print( + "Usage: sass [output.css]\n" + " sass : : \n", + ); print(ExecutableOptions.usage); exitCode = 64; } catch (error, stackTrace) { @@ -75,7 +84,8 @@ Future main(List args) async { buffer.writeln(); buffer.writeln(); buffer.write( - Trace.from(getTrace(error) ?? stackTrace).terse.toString().trimRight()); + Trace.from(getTrace(error) ?? stackTrace).terse.toString().trimRight(), + ); printError(buffer); exitCode = 255; } @@ -92,8 +102,9 @@ Future _loadVersion() async { return version; } - var libDir = - p.fromUri(await Isolate.resolvePackageUri(Uri.parse('package:sass/'))); + var libDir = p.fromUri( + await Isolate.resolvePackageUri(Uri.parse('package:sass/')), + ); var pubspec = readFile(p.join(libDir, '..', 'pubspec.yaml')); return pubspec .split("\n") diff --git a/lib/sass.dart b/lib/sass.dart index 19c6f8f87..a4e4219eb 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -2,9 +2,6 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -/// We strongly recommend importing this library with the prefix `sass`. -library sass; - import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; import 'package:source_maps/source_maps.dart'; @@ -101,36 +98,41 @@ export 'src/evaluation_context.dart' show warn; /// Throws a [SassException] if conversion fails. /// /// {@category Compile} -CompileResult compileToResult(String path, - {bool color = false, - Logger? logger, - Iterable? importers, - Iterable? loadPaths, - PackageConfig? packageConfig, - Iterable? functions, - OutputStyle? style, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) => - c.compile(path, - logger: logger, - importCache: ImportCache( - importers: importers, - loadPaths: loadPaths, - packageConfig: packageConfig), - functions: functions, - style: style, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap, - charset: charset, - silenceDeprecations: silenceDeprecations, - fatalDeprecations: fatalDeprecations, - futureDeprecations: futureDeprecations); +CompileResult compileToResult( + String path, { + bool color = false, + Logger? logger, + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + Iterable? functions, + OutputStyle? style, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) => + c.compile( + path, + logger: logger, + importCache: ImportCache( + importers: importers, + loadPaths: loadPaths, + packageConfig: packageConfig, + ), + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset, + silenceDeprecations: silenceDeprecations, + fatalDeprecations: fatalDeprecations, + futureDeprecations: futureDeprecations, + ); /// Compiles [source] to CSS and returns a [CompileResult] containing the CSS /// and additional metadata about the compilation.. @@ -198,78 +200,88 @@ CompileResult compileToResult(String path, /// Throws a [SassException] if conversion fails. /// /// {@category Compile} -CompileResult compileStringToResult(String source, - {Syntax? syntax, - bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - Importer? importer, - Object? url, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) => - c.compileString(source, - syntax: syntax, - logger: logger, - importCache: ImportCache( - importers: importers, - packageConfig: packageConfig, - loadPaths: loadPaths), - functions: functions, - style: style, - importer: importer, - url: url, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap, - charset: charset, - silenceDeprecations: silenceDeprecations, - fatalDeprecations: fatalDeprecations, - futureDeprecations: futureDeprecations); +CompileResult compileStringToResult( + String source, { + Syntax? syntax, + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + Importer? importer, + Object? url, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) => + c.compileString( + source, + syntax: syntax, + logger: logger, + importCache: ImportCache( + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, + ), + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset, + silenceDeprecations: silenceDeprecations, + fatalDeprecations: fatalDeprecations, + futureDeprecations: futureDeprecations, + ); /// Like [compileToResult], except it runs asynchronously. /// /// Running asynchronously allows this to take [AsyncImporter]s rather than /// synchronous [Importer]s. However, running asynchronously is also somewhat /// slower, so [compileToResult] should be preferred if possible. -Future compileToResultAsync(String path, - {bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) => - c.compileAsync(path, - logger: logger, - importCache: AsyncImportCache( - importers: importers, - loadPaths: loadPaths, - packageConfig: packageConfig), - functions: functions, - style: style, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap, - charset: charset, - silenceDeprecations: silenceDeprecations, - fatalDeprecations: fatalDeprecations, - futureDeprecations: futureDeprecations); +Future compileToResultAsync( + String path, { + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) => + c.compileAsync( + path, + logger: logger, + importCache: AsyncImportCache( + importers: importers, + loadPaths: loadPaths, + packageConfig: packageConfig, + ), + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset, + silenceDeprecations: silenceDeprecations, + fatalDeprecations: fatalDeprecations, + futureDeprecations: futureDeprecations, + ); /// Like [compileStringToResult], except it runs asynchronously. /// @@ -278,42 +290,47 @@ Future compileToResultAsync(String path, /// slower, so [compileStringToResult] should be preferred if possible. /// /// {@category Compile} -Future compileStringToResultAsync(String source, - {Syntax? syntax, - bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - AsyncImporter? importer, - Object? url, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) => - c.compileStringAsync(source, - syntax: syntax, - logger: logger, - importCache: AsyncImportCache( - importers: importers, - packageConfig: packageConfig, - loadPaths: loadPaths), - functions: functions, - style: style, - importer: importer, - url: url, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap, - charset: charset, - silenceDeprecations: silenceDeprecations, - fatalDeprecations: fatalDeprecations, - futureDeprecations: futureDeprecations); +Future compileStringToResultAsync( + String source, { + Syntax? syntax, + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + AsyncImporter? importer, + Object? url, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) => + c.compileStringAsync( + source, + syntax: syntax, + logger: logger, + importCache: AsyncImportCache( + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, + ), + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset, + silenceDeprecations: silenceDeprecations, + fatalDeprecations: fatalDeprecations, + futureDeprecations: futureDeprecations, + ); /// Like [compileToResult], but returns [CompileResult.css] rather than /// returning [CompileResult] directly. @@ -337,30 +354,34 @@ Future compileStringToResultAsync(String source, /// /// {@category Compile} @Deprecated("Use compileToResult() instead.") -String compile(String path, - {bool color = false, - Logger? logger, - Iterable? importers, - Iterable? loadPaths, - PackageConfig? packageConfig, - Iterable? functions, - OutputStyle? style, - bool quietDeps = false, - bool verbose = false, - @Deprecated("Use CompileResult.sourceMap from compileToResult() instead.") - void sourceMap(SingleMapping map)?, - bool charset = true}) { - var result = compileToResult(path, - logger: logger, - importers: importers, - loadPaths: loadPaths, - packageConfig: packageConfig, - functions: functions, - style: style, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap != null, - charset: charset); +String compile( + String path, { + bool color = false, + Logger? logger, + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + Iterable? functions, + OutputStyle? style, + bool quietDeps = false, + bool verbose = false, + @Deprecated("Use CompileResult.sourceMap from compileToResult() instead.") + void sourceMap(SingleMapping map)?, + bool charset = true, +}) { + var result = compileToResult( + path, + logger: logger, + importers: importers, + loadPaths: loadPaths, + packageConfig: packageConfig, + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap != null, + charset: charset, + ); result.sourceMap.andThen(sourceMap); return result.css; } @@ -387,38 +408,43 @@ String compile(String path, /// /// {@category Compile} @Deprecated("Use compileStringToResult() instead.") -String compileString(String source, - {Syntax? syntax, - bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - Importer? importer, - Object? url, - bool quietDeps = false, - bool verbose = false, - @Deprecated( - "Use CompileResult.sourceMap from compileStringToResult() instead.") - void sourceMap(SingleMapping map)?, - bool charset = true, - @Deprecated("Use syntax instead.") bool indented = false}) { - var result = compileStringToResult(source, - syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), - logger: logger, - importers: importers, - packageConfig: packageConfig, - loadPaths: loadPaths, - functions: functions, - style: style, - importer: importer, - url: url, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap != null, - charset: charset); +String compileString( + String source, { + Syntax? syntax, + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + Importer? importer, + Object? url, + bool quietDeps = false, + bool verbose = false, + @Deprecated( + "Use CompileResult.sourceMap from compileStringToResult() instead.", + ) + void sourceMap(SingleMapping map)?, + bool charset = true, + @Deprecated("Use syntax instead.") bool indented = false, +}) { + var result = compileStringToResult( + source, + syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), + logger: logger, + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap != null, + charset: charset, + ); result.sourceMap.andThen(sourceMap); return result.css; } @@ -431,29 +457,34 @@ String compileString(String source, /// /// {@category Compile} @Deprecated("Use compileToResultAsync() instead.") -Future compileAsync(String path, - {bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - bool quietDeps = false, - bool verbose = false, - @Deprecated( - "Use CompileResult.sourceMap from compileToResultAsync() instead.") - void sourceMap(SingleMapping map)?}) async { - var result = await compileToResultAsync(path, - logger: logger, - importers: importers, - loadPaths: loadPaths, - packageConfig: packageConfig, - functions: functions, - style: style, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap != null); +Future compileAsync( + String path, { + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + bool quietDeps = false, + bool verbose = false, + @Deprecated( + "Use CompileResult.sourceMap from compileToResultAsync() instead.", + ) + void sourceMap(SingleMapping map)?, +}) async { + var result = await compileToResultAsync( + path, + logger: logger, + importers: importers, + loadPaths: loadPaths, + packageConfig: packageConfig, + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap != null, + ); result.sourceMap.andThen(sourceMap); return result.css; } @@ -466,38 +497,43 @@ Future compileAsync(String path, /// /// {@category Compile} @Deprecated("Use compileStringToResultAsync() instead.") -Future compileStringAsync(String source, - {Syntax? syntax, - bool color = false, - Logger? logger, - Iterable? importers, - PackageConfig? packageConfig, - Iterable? loadPaths, - Iterable? functions, - OutputStyle? style, - AsyncImporter? importer, - Object? url, - bool quietDeps = false, - bool verbose = false, - @Deprecated( - "Use CompileResult.sourceMap from compileStringToResultAsync() instead.") - void sourceMap(SingleMapping map)?, - bool charset = true, - @Deprecated("Use syntax instead.") bool indented = false}) async { - var result = await compileStringToResultAsync(source, - syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), - logger: logger, - importers: importers, - packageConfig: packageConfig, - loadPaths: loadPaths, - functions: functions, - style: style, - importer: importer, - url: url, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap != null, - charset: charset); +Future compileStringAsync( + String source, { + Syntax? syntax, + bool color = false, + Logger? logger, + Iterable? importers, + PackageConfig? packageConfig, + Iterable? loadPaths, + Iterable? functions, + OutputStyle? style, + AsyncImporter? importer, + Object? url, + bool quietDeps = false, + bool verbose = false, + @Deprecated( + "Use CompileResult.sourceMap from compileStringToResultAsync() instead.", + ) + void sourceMap(SingleMapping map)?, + bool charset = true, + @Deprecated("Use syntax instead.") bool indented = false, +}) async { + var result = await compileStringToResultAsync( + source, + syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), + logger: logger, + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap != null, + charset: charset, + ); result.sourceMap.andThen(sourceMap); return result.css; } diff --git a/lib/src/ast/css/media_query.dart b/lib/src/ast/css/media_query.dart index bad97ada6..8e63d8920 100644 --- a/lib/src/ast/css/media_query.dart +++ b/lib/src/ast/css/media_query.dart @@ -42,10 +42,16 @@ final class CssMediaQuery { /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - static List parseList(String contents, - {Object? url, InterpolationMap? interpolationMap}) => - MediaQueryParser(contents, url: url, interpolationMap: interpolationMap) - .parse(); + static List parseList( + String contents, { + Object? url, + InterpolationMap? interpolationMap, + }) => + MediaQueryParser( + contents, + url: url, + interpolationMap: interpolationMap, + ).parse(); /// Creates a media query specifies a type and, optionally, conditions. /// @@ -67,8 +73,9 @@ final class CssMediaQuery { conditions = List.unmodifiable(conditions) { if (this.conditions.length > 1 && conjunction == null) { throw ArgumentError( - "If conditions is longer than one element, conjunction may not be " - "null."); + "If conditions is longer than one element, conjunction may not be " + "null.", + ); } } @@ -85,9 +92,12 @@ final class CssMediaQuery { var theirType = other.type?.toLowerCase(); if (ourType == null && theirType == null) { - return MediaQuerySuccessfulMergeResult._(CssMediaQuery.condition( - [...this.conditions, ...other.conditions], - conjunction: true)); + return MediaQuerySuccessfulMergeResult._( + CssMediaQuery.condition([ + ...this.conditions, + ...other.conditions, + ], conjunction: true), + ); } String? modifier; @@ -165,10 +175,13 @@ final class CssMediaQuery { conditions = [...this.conditions, ...other.conditions]; } - return MediaQuerySuccessfulMergeResult._(CssMediaQuery.type( + return MediaQuerySuccessfulMergeResult._( + CssMediaQuery.type( type == ourType ? this.type : other.type, modifier: modifier == ourModifier ? this.modifier : other.modifier, - conditions: conditions)); + conditions: conditions, + ), + ); } bool operator ==(Object other) => @@ -209,7 +222,7 @@ sealed class MediaQueryMergeResult { /// The subclass [MediaQueryMergeResult] that represents singleton enum values. enum _SingletonCssMediaQueryMergeResult implements MediaQueryMergeResult { empty, - unrepresentable; + unrepresentable, } /// A successful result of [CssMediaQuery.merge]. diff --git a/lib/src/ast/css/modifiable/at_rule.dart b/lib/src/ast/css/modifiable/at_rule.dart index a63579688..28c367271 100644 --- a/lib/src/ast/css/modifiable/at_rule.dart +++ b/lib/src/ast/css/modifiable/at_rule.dart @@ -17,9 +17,12 @@ final class ModifiableCssAtRule extends ModifiableCssParentNode final bool isChildless; final FileSpan span; - ModifiableCssAtRule(this.name, this.span, - {bool childless = false, this.value}) - : isChildless = childless; + ModifiableCssAtRule( + this.name, + this.span, { + bool childless = false, + this.value, + }) : isChildless = childless; T accept(ModifiableCssVisitor visitor) => visitor.visitCssAtRule(this); diff --git a/lib/src/ast/css/modifiable/declaration.dart b/lib/src/ast/css/modifiable/declaration.dart index 3a0279307..479403dfd 100644 --- a/lib/src/ast/css/modifiable/declaration.dart +++ b/lib/src/ast/css/modifiable/declaration.dart @@ -26,24 +26,29 @@ final class ModifiableCssDeclaration extends ModifiableCssNode bool get isCustomProperty => name.value.startsWith('--'); /// Returns a new CSS declaration with the given properties. - ModifiableCssDeclaration(this.name, this.value, this.span, - {required this.parsedAsCustomProperty, - Iterable? interleavedRules, - this.trace, - FileSpan? valueSpanForMap}) - : interleavedRules = interleavedRules == null + ModifiableCssDeclaration( + this.name, + this.value, + this.span, { + required this.parsedAsCustomProperty, + Iterable? interleavedRules, + this.trace, + FileSpan? valueSpanForMap, + }) : interleavedRules = interleavedRules == null ? const [] : List.unmodifiable(interleavedRules), valueSpanForMap = valueSpanForMap ?? value.span { if (parsedAsCustomProperty) { if (!isCustomProperty) { throw ArgumentError( - 'parsedAsCustomProperty must be false if name doesn\'t begin with ' - '"--".'); + 'parsedAsCustomProperty must be false if name doesn\'t begin with ' + '"--".', + ); } else if (value.value is! SassString) { throw ArgumentError( - 'If parsedAsCustomProperty is true, value must contain a SassString ' - '(was `$value` of type ${value.value.runtimeType}).'); + 'If parsedAsCustomProperty is true, value must contain a SassString ' + '(was `$value` of type ${value.value.runtimeType}).', + ); } } } diff --git a/lib/src/ast/css/modifiable/style_rule.dart b/lib/src/ast/css/modifiable/style_rule.dart index 6e242d36c..e375a3a96 100644 --- a/lib/src/ast/css/modifiable/style_rule.dart +++ b/lib/src/ast/css/modifiable/style_rule.dart @@ -26,9 +26,12 @@ final class ModifiableCssStyleRule extends ModifiableCssParentNode /// Creates a new [ModifiableCssStyleRule]. /// /// If [originalSelector] isn't passed, it defaults to [_selector.value]. - ModifiableCssStyleRule(this._selector, this.span, - {SelectorList? originalSelector, this.fromPlainCss = false}) - : originalSelector = originalSelector ?? _selector.value; + ModifiableCssStyleRule( + this._selector, + this.span, { + SelectorList? originalSelector, + this.fromPlainCss = false, + }) : originalSelector = originalSelector ?? _selector.value; T accept(ModifiableCssVisitor visitor) => visitor.visitCssStyleRule(this); @@ -36,7 +39,9 @@ final class ModifiableCssStyleRule extends ModifiableCssParentNode bool equalsIgnoringChildren(ModifiableCssNode other) => other is ModifiableCssStyleRule && other.selector == selector; - ModifiableCssStyleRule copyWithoutChildren() => - ModifiableCssStyleRule(_selector, span, - originalSelector: originalSelector); + ModifiableCssStyleRule copyWithoutChildren() => ModifiableCssStyleRule( + _selector, + span, + originalSelector: originalSelector, + ); } diff --git a/lib/src/ast/css/node.dart b/lib/src/ast/css/node.dart index 04b6b839d..f50f8c9f5 100644 --- a/lib/src/ast/css/node.dart +++ b/lib/src/ast/css/node.dart @@ -32,7 +32,8 @@ abstract class CssNode implements AstNode { /// invisible even though they're omitted in compressed mode. @internal bool get isInvisible => accept( - const _IsInvisibleVisitor(includeBogus: true, includeComments: false)); + const _IsInvisibleVisitor(includeBogus: true, includeComments: false), + ); // Whether this node would be invisible even if style rule selectors within it // didn't have bogus combinators. @@ -41,12 +42,14 @@ abstract class CssNode implements AstNode { /// invisible even though they're omitted in compressed mode. @internal bool get isInvisibleOtherThanBogusCombinators => accept( - const _IsInvisibleVisitor(includeBogus: false, includeComments: false)); + const _IsInvisibleVisitor(includeBogus: false, includeComments: false), + ); // Whether this node will be invisible when loud comments are stripped. @internal bool get isInvisibleHidingComments => accept( - const _IsInvisibleVisitor(includeBogus: true, includeComments: true)); + const _IsInvisibleVisitor(includeBogus: true, includeComments: true), + ); String toString() => serialize(this, inspect: true).$1; } @@ -75,8 +78,10 @@ class _IsInvisibleVisitor with EveryCssVisitor { /// Whether to consider comments invisible. final bool includeComments; - const _IsInvisibleVisitor( - {required this.includeBogus, required this.includeComments}); + const _IsInvisibleVisitor({ + required this.includeBogus, + required this.includeComments, + }); // An unknown at-rule is never invisible. Because we don't know the semantics // of unknown rules, we can't guarantee that (for example) `@foo {}` isn't diff --git a/lib/src/ast/sass/argument_list.dart b/lib/src/ast/sass/argument_list.dart index 84a53b67e..6d72bdcd3 100644 --- a/lib/src/ast/sass/argument_list.dart +++ b/lib/src/ast/sass/argument_list.dart @@ -32,9 +32,12 @@ final class ArgumentList implements SassNode { bool get isEmpty => positional.isEmpty && named.isEmpty && rest == null; ArgumentList( - Iterable positional, Map named, this.span, - {this.rest, this.keywordRest}) - : positional = List.unmodifiable(positional), + Iterable positional, + Map named, + this.span, { + this.rest, + this.keywordRest, + }) : positional = List.unmodifiable(positional), named = Map.unmodifiable(named) { assert(rest != null || keywordRest == null); } @@ -53,7 +56,7 @@ final class ArgumentList implements SassNode { "\$$name: ${_parenthesizeArgument(value)}", if (rest case var rest?) "${_parenthesizeArgument(rest)}...", if (keywordRest case var keywordRest?) - "${_parenthesizeArgument(keywordRest)}..." + "${_parenthesizeArgument(keywordRest)}...", ]; return "(${components.join(', ')})"; } @@ -63,9 +66,9 @@ final class ArgumentList implements SassNode { ListExpression( separator: ListSeparator.comma, hasBrackets: false, - contents: [_, _, ...] + contents: [_, _, ...], ) => "($argument)", - _ => argument.toString() + _ => argument.toString(), }; } diff --git a/lib/src/ast/sass/at_root_query.dart b/lib/src/ast/sass/at_root_query.dart index 0ed522c39..4a1f9b998 100644 --- a/lib/src/ast/sass/at_root_query.dart +++ b/lib/src/ast/sass/at_root_query.dart @@ -56,8 +56,11 @@ final class AtRootQuery { /// original location of the selector in the source file. /// /// Throws a [SassFormatException] if parsing fails. - factory AtRootQuery.parse(String contents, - {Object? url, InterpolationMap? interpolationMap}) => + factory AtRootQuery.parse( + String contents, { + Object? url, + InterpolationMap? interpolationMap, + }) => AtRootQueryParser(contents, url: url).parse(); /// Returns whether `this` excludes [node]. @@ -71,7 +74,7 @@ final class AtRootQuery { CssMediaRule() => excludesName("media"), CssSupportsRule() => excludesName("supports"), CssAtRule() => excludesName(node.name.value.toLowerCase()), - _ => false + _ => false, }; } diff --git a/lib/src/ast/sass/configured_variable.dart b/lib/src/ast/sass/configured_variable.dart index c78066734..575141dbf 100644 --- a/lib/src/ast/sass/configured_variable.dart +++ b/lib/src/ast/sass/configured_variable.dart @@ -28,9 +28,12 @@ final class ConfiguredVariable implements SassNode, SassDeclaration { FileSpan get nameSpan => span.initialIdentifier(includeLeading: 1); - ConfiguredVariable(this.name, this.expression, this.span, - {bool guarded = false}) - : isGuarded = guarded; + ConfiguredVariable( + this.name, + this.expression, + this.span, { + bool guarded = false, + }) : isGuarded = guarded; String toString() => "\$$name: $expression${isGuarded ? ' !default' : ''}"; } diff --git a/lib/src/ast/sass/expression/binary_operation.dart b/lib/src/ast/sass/expression/binary_operation.dart index 4e9cda229..9aeb05a33 100644 --- a/lib/src/ast/sass/expression/binary_operation.dart +++ b/lib/src/ast/sass/expression/binary_operation.dart @@ -81,7 +81,7 @@ final class BinaryOperationExpression extends Expression { BinaryOperationExpression(operator: BinaryOperator(:var precedence)) => precedence < operator.precedence, ListExpression(hasBrackets: false, contents: [_, _, ...]) => true, - _ => false + _ => false, }; if (leftNeedsParens) buffer.writeCharCode($lparen); buffer.write(left); @@ -97,7 +97,7 @@ final class BinaryOperationExpression extends Expression { operator.precedence <= this.operator.precedence && !(operator == this.operator && operator.isAssociative), ListExpression(hasBrackets: false, contents: [_, _, ...]) => true, - _ => false + _ => false, }; if (rightNeedsParens) buffer.writeCharCode($lparen); buffer.write(right); @@ -172,9 +172,12 @@ enum BinaryOperator { /// [associative property]: https://en.wikipedia.org/wiki/Associative_property final bool isAssociative; - const BinaryOperator(this.name, this.operator, this.precedence, - {bool associative = false}) - : isAssociative = associative; + const BinaryOperator( + this.name, + this.operator, + this.precedence, { + bool associative = false, + }) : isAssociative = associative; String toString() => name; } diff --git a/lib/src/ast/sass/expression/function.dart b/lib/src/ast/sass/expression/function.dart index 78b7c734a..2706804a0 100644 --- a/lib/src/ast/sass/expression/function.dart +++ b/lib/src/ast/sass/expression/function.dart @@ -45,9 +45,12 @@ final class FunctionExpression extends Expression FileSpan? get namespaceSpan => namespace == null ? null : span.initialIdentifier(); - FunctionExpression(this.originalName, this.arguments, this.span, - {this.namespace}) - : name = originalName.replaceAll('_', '-'); + FunctionExpression( + this.originalName, + this.arguments, + this.span, { + this.namespace, + }) : name = originalName.replaceAll('_', '-'); T accept(ExpressionVisitor visitor) => visitor.visitFunctionExpression(this); diff --git a/lib/src/ast/sass/expression/if.dart b/lib/src/ast/sass/expression/if.dart index 1cec007e0..88eeb1cb1 100644 --- a/lib/src/ast/sass/expression/if.dart +++ b/lib/src/ast/sass/expression/if.dart @@ -16,8 +16,9 @@ import '../../../visitor/interface/expression.dart'; /// {@category AST} final class IfExpression extends Expression implements CallableInvocation { /// The declaration of `if()`, as though it were a normal function. - static final declaration = - ParameterList.parse(r"@function if($condition, $if-true, $if-false) {"); + static final declaration = ParameterList.parse( + r"@function if($condition, $if-true, $if-false) {", + ); /// The arguments passed to `if()`. final ArgumentList arguments; diff --git a/lib/src/ast/sass/expression/list.dart b/lib/src/ast/sass/expression/list.dart index 67d26880e..2bfcea228 100644 --- a/lib/src/ast/sass/expression/list.dart +++ b/lib/src/ast/sass/expression/list.dart @@ -25,9 +25,12 @@ final class ListExpression extends Expression { final FileSpan span; - ListExpression(Iterable contents, this.separator, this.span, - {bool brackets = false}) - : contents = List.unmodifiable(contents), + ListExpression( + Iterable contents, + this.separator, + this.span, { + bool brackets = false, + }) : contents = List.unmodifiable(contents), hasBrackets = brackets; T accept(ExpressionVisitor visitor) => @@ -42,10 +45,15 @@ final class ListExpression extends Expression { buffer.writeCharCode($lparen); } - buffer.write(contents - .map((element) => - _elementNeedsParens(element) ? "($element)" : element.toString()) - .join(separator == ListSeparator.comma ? ", " : " ")); + buffer.write( + contents + .map( + (element) => _elementNeedsParens(element) + ? "($element)" + : element.toString(), + ) + .join(separator == ListSeparator.comma ? ", " : " "), + ); if (hasBrackets) { buffer.writeCharCode($rbracket); @@ -64,15 +72,15 @@ final class ListExpression extends Expression { ListExpression( contents: [_, _, ...], hasBrackets: false, - separator: var childSeparator + separator: var childSeparator, ) => separator == ListSeparator.comma ? childSeparator == ListSeparator.comma : childSeparator != ListSeparator.undecided, UnaryOperationExpression( - operator: UnaryOperator.plus || UnaryOperator.minus + operator: UnaryOperator.plus || UnaryOperator.minus, ) => separator == ListSeparator.space, - _ => false + _ => false, }; } diff --git a/lib/src/ast/sass/expression/string.dart b/lib/src/ast/sass/expression/string.dart index f9f05d98e..38a4b2dfa 100644 --- a/lib/src/ast/sass/expression/string.dart +++ b/lib/src/ast/sass/expression/string.dart @@ -84,8 +84,12 @@ final class StringExpression extends Expression { /// /// This always adds an escape sequence before [quote]. If [static] is true, /// it also escapes any `#{` sequences in the string. - static void _quoteInnerText(String text, int quote, StringSink buffer, - {bool static = false}) { + static void _quoteInnerText( + String text, + int quote, + StringSink buffer, { + bool static = false, + }) { for (var i = 0; i < text.length; i++) { switch (text.codeUnitAt(i)) { case int(isNewline: true): diff --git a/lib/src/ast/sass/expression/unary_operation.dart b/lib/src/ast/sass/expression/unary_operation.dart index 913d1ef9e..f7736b59d 100644 --- a/lib/src/ast/sass/expression/unary_operation.dart +++ b/lib/src/ast/sass/expression/unary_operation.dart @@ -36,7 +36,7 @@ final class UnaryOperationExpression extends Expression { UnaryOperationExpression() || ListExpression(hasBrackets: false, contents: [_, _, ...]) => true, - _ => false + _ => false, }; if (needsParens) buffer.write($lparen); buffer.write(operand); diff --git a/lib/src/ast/sass/interpolation.dart b/lib/src/ast/sass/interpolation.dart index 345819d21..d1a599334 100644 --- a/lib/src/ast/sass/interpolation.dart +++ b/lib/src/ast/sass/interpolation.dart @@ -35,15 +35,20 @@ final class Interpolation implements SassNode { /// If this contains no interpolated expressions, returns its text contents. /// /// Otherwise, returns `null`. - String? get asPlain => - switch (contents) { [] => '', [String first] => first, _ => null }; + String? get asPlain => switch (contents) { + [] => '', + [String first] => first, + _ => null, + }; /// Returns the plain text before the interpolation, or the empty string. /// /// @nodoc @internal - String get initialPlain => - switch (contents) { [String first, ...] => first, _ => '' }; + String get initialPlain => switch (contents) { + [String first, ...] => first, + _ => '', + }; /// Returns the [FileSpan] covering the element of the interpolation at /// [index]. @@ -60,9 +65,9 @@ final class Interpolation implements SassNode { FileSpan spanForElement(int index) => switch (contents[index]) { String() => span.file.span( (index == 0 ? span.start : spans[index - 1]!.end).offset, - (index == spans.length ? span.end : spans[index + 1]!.start) - .offset), - _ => spans[index]! + (index == spans.length ? span.end : spans[index + 1]!.start).offset, + ), + _ => spans[index]!, }; Interpolation.plain(String text, this.span) @@ -76,31 +81,48 @@ final class Interpolation implements SassNode { /// expression. /// /// The single [span] must cover the entire interpolation. - Interpolation(Iterable contents, - Iterable spans, this.span) - : contents = List.unmodifiable(contents), + Interpolation( + Iterable contents, + Iterable spans, + this.span, + ) : contents = List.unmodifiable(contents), spans = List.unmodifiable(spans) { if (spans.length != contents.length) { throw ArgumentError.value( - this.spans, "spans", "Must be the same length as contents."); + this.spans, + "spans", + "Must be the same length as contents.", + ); } for (var i = 0; i < this.contents.length; i++) { var isString = this.contents[i] is String; if (!isString && this.contents[i] is! Expression) { - throw ArgumentError.value(this.contents, "contents", - "May only contain Strings or Expressions."); + throw ArgumentError.value( + this.contents, + "contents", + "May only contain Strings or Expressions.", + ); } else if (isString) { if (i != 0 && this.contents[i - 1] is String) { throw ArgumentError.value( - this.contents, "contents", "May not contain adjacent Strings."); + this.contents, + "contents", + "May not contain adjacent Strings.", + ); } else if (i < spans.length && this.spans[i] != null) { - throw ArgumentError.value(this.spans, "spans", - "May not have a value for string elements (at index $i)."); + throw ArgumentError.value( + this.spans, + "spans", + "May not have a value for string elements (at index $i).", + ); } } else if (i >= spans.length || this.spans[i] == null) { - throw ArgumentError.value(this.spans, "spans", - "Must not have a value for expression elements (at index $i)."); + throw ArgumentError.value( + this.spans, + "spans", + "Must not have a value for expression elements (at index $i).", + ); } } } diff --git a/lib/src/ast/sass/parameter_list.dart b/lib/src/ast/sass/parameter_list.dart index ea5e3fae7..bdd01848a 100644 --- a/lib/src/ast/sass/parameter_list.dart +++ b/lib/src/ast/sass/parameter_list.dart @@ -81,16 +81,18 @@ final class ParameterList implements SassNode { if (i < positional) { if (names.contains(parameter.name)) { throw SassScriptException( - "Argument ${_originalParameterName(parameter.name)} was passed " - "both by position and by name."); + "Argument ${_originalParameterName(parameter.name)} was passed " + "both by position and by name.", + ); } } else if (names.contains(parameter.name)) { namedUsed++; } else if (parameter.defaultValue == null) { throw MultiSpanSassScriptException( - "Missing argument ${_originalParameterName(parameter.name)}.", - "invocation", - {spanWithName: "declaration"}); + "Missing argument ${_originalParameterName(parameter.name)}.", + "invocation", + {spanWithName: "declaration"}, + ); } } @@ -98,23 +100,25 @@ final class ParameterList implements SassNode { if (positional > parameters.length) { throw MultiSpanSassScriptException( - "Only ${parameters.length} " - "${names.isEmpty ? '' : 'positional '}" - "${pluralize('argument', parameters.length)} allowed, but " - "$positional ${pluralize('was', positional, plural: 'were')} " - "passed.", - "invocation", - {spanWithName: "declaration"}); + "Only ${parameters.length} " + "${names.isEmpty ? '' : 'positional '}" + "${pluralize('argument', parameters.length)} allowed, but " + "$positional ${pluralize('was', positional, plural: 'were')} " + "passed.", + "invocation", + {spanWithName: "declaration"}, + ); } if (namedUsed < names.length) { var unknownNames = Set.of(names) ..removeAll(parameters.map((parameter) => parameter.name)); throw MultiSpanSassScriptException( - "No ${pluralize('parameter', unknownNames.length)} named " - "${toSentence(unknownNames.map((name) => "\$$name"), 'or')}.", - "invocation", - {spanWithName: "declaration"}); + "No ${pluralize('parameter', unknownNames.length)} named " + "${toSentence(unknownNames.map((name) => "\$$name"), 'or')}.", + "invocation", + {spanWithName: "declaration"}, + ); } } @@ -157,6 +161,6 @@ final class ParameterList implements SassNode { String toString() => [ for (var arg in parameters) '\$$arg', - if (restParameter != null) '\$$restParameter...' + if (restParameter != null) '\$$restParameter...', ].join(', '); } diff --git a/lib/src/ast/sass/statement/callable_declaration.dart b/lib/src/ast/sass/statement/callable_declaration.dart index 1c2154065..a043ba5a6 100644 --- a/lib/src/ast/sass/statement/callable_declaration.dart +++ b/lib/src/ast/sass/statement/callable_declaration.dart @@ -29,9 +29,12 @@ abstract base class CallableDeclaration final FileSpan span; - CallableDeclaration(this.originalName, this.parameters, - Iterable children, this.span, - {this.comment}) - : name = originalName.replaceAll('_', '-'), + CallableDeclaration( + this.originalName, + this.parameters, + Iterable children, + this.span, { + this.comment, + }) : name = originalName.replaceAll('_', '-'), super(List.unmodifiable(children)); } diff --git a/lib/src/ast/sass/statement/content_block.dart b/lib/src/ast/sass/statement/content_block.dart index d29a8290b..e42faa366 100644 --- a/lib/src/ast/sass/statement/content_block.dart +++ b/lib/src/ast/sass/statement/content_block.dart @@ -14,8 +14,10 @@ import 'callable_declaration.dart'; /// {@category AST} final class ContentBlock extends CallableDeclaration { ContentBlock( - ParameterList parameters, Iterable children, FileSpan span) - : super("@content", parameters, children, span); + ParameterList parameters, + Iterable children, + FileSpan span, + ) : super("@content", parameters, children, span); T accept(StatementVisitor visitor) => visitor.visitContentBlock(this); diff --git a/lib/src/ast/sass/statement/declaration.dart b/lib/src/ast/sass/statement/declaration.dart index 852bc3145..2ba768b26 100644 --- a/lib/src/ast/sass/statement/declaration.dart +++ b/lib/src/ast/sass/statement/declaration.dart @@ -46,9 +46,12 @@ final class Declaration extends ParentStatement { /// Creates a declaration with children. /// /// For these declarations, a value is optional. - Declaration.nested(this.name, Iterable children, this.span, - {this.value}) - : super(List.unmodifiable(children)); + Declaration.nested( + this.name, + Iterable children, + this.span, { + this.value, + }) : super(List.unmodifiable(children)); T accept(StatementVisitor visitor) => visitor.visitDeclaration(this); diff --git a/lib/src/ast/sass/statement/each_rule.dart b/lib/src/ast/sass/statement/each_rule.dart index 68500ef56..7ed1459f8 100644 --- a/lib/src/ast/sass/statement/each_rule.dart +++ b/lib/src/ast/sass/statement/each_rule.dart @@ -23,9 +23,12 @@ final class EachRule extends ParentStatement> { final FileSpan span; - EachRule(Iterable variables, this.list, Iterable children, - this.span) - : variables = List.unmodifiable(variables), + EachRule( + Iterable variables, + this.list, + Iterable children, + this.span, + ) : variables = List.unmodifiable(variables), super(List.unmodifiable(children)); T accept(StatementVisitor visitor) => visitor.visitEachRule(this); diff --git a/lib/src/ast/sass/statement/for_rule.dart b/lib/src/ast/sass/statement/for_rule.dart index 008f4d1f2..54689d836 100644 --- a/lib/src/ast/sass/statement/for_rule.dart +++ b/lib/src/ast/sass/statement/for_rule.dart @@ -29,10 +29,14 @@ final class ForRule extends ParentStatement> { final FileSpan span; - ForRule(this.variable, this.from, this.to, Iterable children, - this.span, - {bool exclusive = true}) - : isExclusive = exclusive, + ForRule( + this.variable, + this.from, + this.to, + Iterable children, + this.span, { + bool exclusive = true, + }) : isExclusive = exclusive, super(List.unmodifiable(children)); T accept(StatementVisitor visitor) => visitor.visitForRule(this); diff --git a/lib/src/ast/sass/statement/forward_rule.dart b/lib/src/ast/sass/statement/forward_rule.dart index 7a680e935..3c4234fe7 100644 --- a/lib/src/ast/sass/statement/forward_rule.dart +++ b/lib/src/ast/sass/statement/forward_rule.dart @@ -77,9 +77,12 @@ final class ForwardRule extends Statement implements SassDependency { FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted(); /// Creates a `@forward` rule that allows all members to be accessed. - ForwardRule(this.url, this.span, - {this.prefix, Iterable? configuration}) - : shownMixinsAndFunctions = null, + ForwardRule( + this.url, + this.span, { + this.prefix, + Iterable? configuration, + }) : shownMixinsAndFunctions = null, shownVariables = null, hiddenMixinsAndFunctions = null, hiddenVariables = null, @@ -88,11 +91,16 @@ final class ForwardRule extends Statement implements SassDependency { /// Creates a `@forward` rule that allows only members included in /// [shownMixinsAndFunctions] and [shownVariables] to be accessed. - ForwardRule.show(this.url, Iterable shownMixinsAndFunctions, - Iterable shownVariables, this.span, - {this.prefix, Iterable? configuration}) - : shownMixinsAndFunctions = - UnmodifiableSetView(Set.of(shownMixinsAndFunctions)), + ForwardRule.show( + this.url, + Iterable shownMixinsAndFunctions, + Iterable shownVariables, + this.span, { + this.prefix, + Iterable? configuration, + }) : shownMixinsAndFunctions = UnmodifiableSetView( + Set.of(shownMixinsAndFunctions), + ), shownVariables = UnmodifiableSetView(Set.of(shownVariables)), hiddenMixinsAndFunctions = null, hiddenVariables = null, @@ -101,13 +109,18 @@ final class ForwardRule extends Statement implements SassDependency { /// Creates a `@forward` rule that allows only members not included in /// [hiddenMixinsAndFunctions] and [hiddenVariables] to be accessed. - ForwardRule.hide(this.url, Iterable hiddenMixinsAndFunctions, - Iterable hiddenVariables, this.span, - {this.prefix, Iterable? configuration}) - : shownMixinsAndFunctions = null, + ForwardRule.hide( + this.url, + Iterable hiddenMixinsAndFunctions, + Iterable hiddenVariables, + this.span, { + this.prefix, + Iterable? configuration, + }) : shownMixinsAndFunctions = null, shownVariables = null, - hiddenMixinsAndFunctions = - UnmodifiableSetView(Set.of(hiddenMixinsAndFunctions)), + hiddenMixinsAndFunctions = UnmodifiableSetView( + Set.of(hiddenMixinsAndFunctions), + ), hiddenVariables = UnmodifiableSetView(Set.of(hiddenVariables)), configuration = configuration == null ? const [] : List.unmodifiable(configuration); @@ -115,8 +128,9 @@ final class ForwardRule extends Statement implements SassDependency { T accept(StatementVisitor visitor) => visitor.visitForwardRule(this); String toString() { - var buffer = - StringBuffer("@forward ${StringExpression.quoteText(url.toString())}"); + var buffer = StringBuffer( + "@forward ${StringExpression.quoteText(url.toString())}", + ); var shownMixinsAndFunctions = this.shownMixinsAndFunctions; var hiddenMixinsAndFunctions = this.hiddenMixinsAndFunctions; @@ -144,6 +158,8 @@ final class ForwardRule extends Statement implements SassDependency { /// Returns a combined list of names of the given members. String _memberList( - Iterable mixinsAndFunctions, Iterable variables) => + Iterable mixinsAndFunctions, + Iterable variables, + ) => [...mixinsAndFunctions, for (var name in variables) "\$$name"].join(", "); } diff --git a/lib/src/ast/sass/statement/function_rule.dart b/lib/src/ast/sass/statement/function_rule.dart index 15741908f..0e8336856 100644 --- a/lib/src/ast/sass/statement/function_rule.dart +++ b/lib/src/ast/sass/statement/function_rule.dart @@ -18,8 +18,13 @@ final class FunctionRule extends CallableDeclaration implements SassDeclaration { FileSpan get nameSpan => span.withoutInitialAtRule().initialIdentifier(); - FunctionRule(super.name, super.parameters, super.children, super.span, - {super.comment}); + FunctionRule( + super.name, + super.parameters, + super.children, + super.span, { + super.comment, + }); T accept(StatementVisitor visitor) => visitor.visitFunctionRule(this); diff --git a/lib/src/ast/sass/statement/if_rule.dart b/lib/src/ast/sass/statement/if_rule.dart index 22b5a03c3..ca9b34efc 100644 --- a/lib/src/ast/sass/statement/if_rule.dart +++ b/lib/src/ast/sass/statement/if_rule.dart @@ -42,9 +42,11 @@ final class IfRule extends Statement { String toString() { var result = clauses - .mapIndexed((index, clause) => - "@${index == 0 ? 'if' : 'else if'} ${clause.expression} " - "{${clause.children.join(' ')}}") + .mapIndexed( + (index, clause) => + "@${index == 0 ? 'if' : 'else if'} ${clause.expression} " + "{${clause.children.join(' ')}}", + ) .join(' '); var lastClause = this.lastClause; @@ -70,12 +72,15 @@ sealed class IfRuleClause { : this._(List.unmodifiable(children)); IfRuleClause._(this.children) - : hasDeclarations = children.any((child) => switch (child) { - VariableDeclaration() || FunctionRule() || MixinRule() => true, - ImportRule(:var imports) => - imports.any((import) => import is DynamicImport), - _ => false - }); + : hasDeclarations = children.any( + (child) => switch (child) { + VariableDeclaration() || FunctionRule() || MixinRule() => true, + ImportRule(:var imports) => imports.any( + (import) => import is DynamicImport, + ), + _ => false, + }, + ); } /// An `@if` or `@else if` clause in an `@if` rule. diff --git a/lib/src/ast/sass/statement/include_rule.dart b/lib/src/ast/sass/statement/include_rule.dart index 5331a728e..5838f65f4 100644 --- a/lib/src/ast/sass/statement/include_rule.dart +++ b/lib/src/ast/sass/statement/include_rule.dart @@ -59,9 +59,13 @@ final class IncludeRule extends Statement return startSpan.initialIdentifier(); } - IncludeRule(this.originalName, this.arguments, this.span, - {this.namespace, this.content}) - : name = originalName.replaceAll('_', '-'); + IncludeRule( + this.originalName, + this.arguments, + this.span, { + this.namespace, + this.content, + }) : name = originalName.replaceAll('_', '-'); T accept(StatementVisitor visitor) => visitor.visitIncludeRule(this); diff --git a/lib/src/ast/sass/statement/mixin_rule.dart b/lib/src/ast/sass/statement/mixin_rule.dart index a4b5e4d2d..220b24dac 100644 --- a/lib/src/ast/sass/statement/mixin_rule.dart +++ b/lib/src/ast/sass/statement/mixin_rule.dart @@ -28,8 +28,13 @@ final class MixinRule extends CallableDeclaration implements SassDeclaration { return startSpan.initialIdentifier(); } - MixinRule(super.name, super.parameters, super.children, super.span, - {super.comment}); + MixinRule( + super.name, + super.parameters, + super.children, + super.span, { + super.comment, + }); T accept(StatementVisitor visitor) => visitor.visitMixinRule(this); diff --git a/lib/src/ast/sass/statement/parent.dart b/lib/src/ast/sass/statement/parent.dart index 4067a2a18..eb5db88f8 100644 --- a/lib/src/ast/sass/statement/parent.dart +++ b/lib/src/ast/sass/statement/parent.dart @@ -30,14 +30,14 @@ abstract base class ParentStatement?> final bool hasDeclarations; ParentStatement(this.children) - : hasDeclarations = children?.any((child) => switch (child) { - VariableDeclaration() || - FunctionRule() || - MixinRule() => - true, - ImportRule(:var imports) => - imports.any((import) => import is DynamicImport), - _ => false, - }) ?? + : hasDeclarations = children?.any( + (child) => switch (child) { + VariableDeclaration() || FunctionRule() || MixinRule() => true, + ImportRule(:var imports) => imports.any( + (import) => import is DynamicImport, + ), + _ => false, + }, + ) ?? false; } diff --git a/lib/src/ast/sass/statement/stylesheet.dart b/lib/src/ast/sass/statement/stylesheet.dart index a030a71aa..2076abd5f 100644 --- a/lib/src/ast/sass/statement/stylesheet.dart +++ b/lib/src/ast/sass/statement/stylesheet.dart @@ -65,10 +65,13 @@ final class Stylesheet extends ParentStatement> { /// /// @nodoc @internal - Stylesheet.internal(Iterable children, this.span, - List parseTimeWarnings, - {this.plainCss = false, Map? globalVariables}) - : parseTimeWarnings = UnmodifiableListView(parseTimeWarnings), + Stylesheet.internal( + Iterable children, + this.span, + List parseTimeWarnings, { + this.plainCss = false, + Map? globalVariables, + }) : parseTimeWarnings = UnmodifiableListView(parseTimeWarnings), globalVariables = globalVariables == null ? const {} : Map.unmodifiable(globalVariables), @@ -113,7 +116,10 @@ final class Stylesheet extends ParentStatement> { if (url == null || url.toString() == 'stdin') rethrow; throw throwWithTrace( - error.withLoadedUrls(Set.unmodifiable({url})), error, stackTrace); + error.withLoadedUrls(Set.unmodifiable({url})), + error, + stackTrace, + ); } } diff --git a/lib/src/ast/sass/statement/use_rule.dart b/lib/src/ast/sass/statement/use_rule.dart index 70005f8fe..14414856d 100644 --- a/lib/src/ast/sass/statement/use_rule.dart +++ b/lib/src/ast/sass/statement/use_rule.dart @@ -34,15 +34,21 @@ final class UseRule extends Statement implements SassDependency { FileSpan get urlSpan => span.withoutInitialAtRule().initialQuoted(); - UseRule(this.url, this.namespace, this.span, - {Iterable? configuration}) - : configuration = configuration == null + UseRule( + this.url, + this.namespace, + this.span, { + Iterable? configuration, + }) : configuration = configuration == null ? const [] : List.unmodifiable(configuration) { for (var variable in this.configuration) { if (variable.isGuarded) { - throw ArgumentError.value(variable, "configured variable", - "can't be guarded in a @use rule."); + throw ArgumentError.value( + variable, + "configured variable", + "can't be guarded in a @use rule.", + ); } } } @@ -61,8 +67,9 @@ final class UseRule extends Statement implements SassDependency { T accept(StatementVisitor visitor) => visitor.visitUseRule(this); String toString() { - var buffer = - StringBuffer("@use ${StringExpression.quoteText(url.toString())}"); + var buffer = StringBuffer( + "@use ${StringExpression.quoteText(url.toString())}", + ); var basename = url.pathSegments.isEmpty ? "" : url.pathSegments.last; var dot = basename.indexOf("."); diff --git a/lib/src/ast/sass/statement/variable_declaration.dart b/lib/src/ast/sass/statement/variable_declaration.dart index 56f75b12c..d3b6a533b 100644 --- a/lib/src/ast/sass/statement/variable_declaration.dart +++ b/lib/src/ast/sass/statement/variable_declaration.dart @@ -62,13 +62,20 @@ final class VariableDeclaration extends Statement implements SassDeclaration { FileSpan? get namespaceSpan => namespace == null ? null : span.initialIdentifier(); - VariableDeclaration(this.name, this.expression, this.span, - {this.namespace, bool guarded = false, bool global = false, this.comment}) - : isGuarded = guarded, + VariableDeclaration( + this.name, + this.expression, + this.span, { + this.namespace, + bool guarded = false, + bool global = false, + this.comment, + }) : isGuarded = guarded, isGlobal = global { if (namespace != null && global) { throw ArgumentError( - "Other modules' members can't be defined with !global."); + "Other modules' members can't be defined with !global.", + ); } } diff --git a/lib/src/ast/sass/supports_condition/declaration.dart b/lib/src/ast/sass/supports_condition/declaration.dart index f81d37f2c..76ec453c0 100644 --- a/lib/src/ast/sass/supports_condition/declaration.dart +++ b/lib/src/ast/sass/supports_condition/declaration.dart @@ -38,7 +38,7 @@ final class SupportsDeclaration implements SupportsCondition { bool get isCustomProperty => switch (name) { StringExpression(hasQuotes: false, :var text) => text.initialPlain.startsWith('--'), - _ => false + _ => false, }; SupportsDeclaration(this.name, this.value, this.span); diff --git a/lib/src/ast/sass/supports_condition/operation.dart b/lib/src/ast/sass/supports_condition/operation.dart index 71fed25c9..72d680534 100644 --- a/lib/src/ast/sass/supports_condition/operation.dart +++ b/lib/src/ast/sass/supports_condition/operation.dart @@ -32,7 +32,10 @@ final class SupportsOperation implements SupportsCondition { var lowerOperator = operator.toLowerCase(); if (lowerOperator != "and" && lowerOperator != "or") { throw ArgumentError.value( - operator, 'operator', 'may only be "and" or "or".'); + operator, + 'operator', + 'may only be "and" or "or".', + ); } } diff --git a/lib/src/ast/selector.dart b/lib/src/ast/selector.dart index fcabcdc25..64420d5c7 100644 --- a/lib/src/ast/selector.dart +++ b/lib/src/ast/selector.dart @@ -90,12 +90,13 @@ abstract base class Selector implements AstNode { void assertNotBogus({String? name}) { if (!isBogus) return; warnForDeprecation( - (name == null ? '' : '\$$name: ') + - '$this is not valid CSS.\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - Deprecation.bogusCombinators); + (name == null ? '' : '\$$name: ') + + '$this is not valid CSS.\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + Deprecation.bogusCombinators, + ); } /// Calls the appropriate visit method on [visitor]. @@ -149,9 +150,11 @@ class _IsBogusVisitor with AnySelectorVisitor { return complex.leadingCombinators.length > (includeLeadingCombinator ? 0 : 1) || complex.components.last.combinators.isNotEmpty || - complex.components.any((component) => - component.combinators.length > 1 || - component.selector.accept(this)); + complex.components.any( + (component) => + component.combinators.length > 1 || + component.selector.accept(this), + ); } } @@ -172,8 +175,10 @@ class _IsUselessVisitor with AnySelectorVisitor { bool visitComplexSelector(ComplexSelector complex) => complex.leadingCombinators.length > 1 || - complex.components.any((component) => - component.combinators.length > 1 || component.selector.accept(this)); + complex.components.any( + (component) => + component.combinators.length > 1 || component.selector.accept(this), + ); bool visitPseudoSelector(PseudoSelector pseudo) => pseudo.isBogus; } diff --git a/lib/src/ast/selector/attribute.dart b/lib/src/ast/selector/attribute.dart index dcdedf54a..6fd1439f8 100644 --- a/lib/src/ast/selector/attribute.dart +++ b/lib/src/ast/selector/attribute.dart @@ -51,9 +51,13 @@ final class AttributeSelector extends SimpleSelector { /// Creates an attribute selector that matches an element with a property /// named [name], whose value matches [value] based on the semantics of [op]. - AttributeSelector.withOperator(this.name, this.op, this.value, FileSpan span, - {this.modifier}) - : super(span); + AttributeSelector.withOperator( + this.name, + this.op, + this.value, + FileSpan span, { + this.modifier, + }) : super(span); T accept(SelectorVisitor visitor) => visitor.visitAttributeSelector(this); diff --git a/lib/src/ast/selector/complex.dart b/lib/src/ast/selector/complex.dart index bc0e057fa..b118f38a8 100644 --- a/lib/src/ast/selector/complex.dart +++ b/lib/src/ast/selector/complex.dart @@ -51,7 +51,9 @@ final class ComplexSelector extends Selector { /// "sufficiently high"; it's extremely unlikely that any single selector /// sequence will contain 1000 simple selectors. late final int specificity = components.fold( - 0, (sum, component) => sum + component.selector.specificity); + 0, + (sum, component) => sum + component.selector.specificity, + ); /// If this compound selector is composed of a single compound selector with /// no combinators, returns it. @@ -64,18 +66,21 @@ final class ComplexSelector extends Selector { if (leadingCombinators.isNotEmpty) return null; return switch (components) { [ComplexSelectorComponent(:var selector, combinators: [])] => selector, - _ => null + _ => null, }; } - ComplexSelector(Iterable> leadingCombinators, - Iterable components, super.span, - {this.lineBreak = false}) - : leadingCombinators = List.unmodifiable(leadingCombinators), + ComplexSelector( + Iterable> leadingCombinators, + Iterable components, + super.span, { + this.lineBreak = false, + }) : leadingCombinators = List.unmodifiable(leadingCombinators), components = List.unmodifiable(components) { if (this.leadingCombinators.isEmpty && this.components.isEmpty) { throw ArgumentError( - "leadingCombinators and components may not both be empty."); + "leadingCombinators and components may not both be empty.", + ); } } @@ -86,10 +91,16 @@ final class ComplexSelector extends Selector { /// selector. /// /// Throws a [SassFormatException] if parsing fails. - factory ComplexSelector.parse(String contents, - {Object? url, bool allowParent = true}) => - SelectorParser(contents, url: url, allowParent: allowParent) - .parseComplexSelector(); + factory ComplexSelector.parse( + String contents, { + Object? url, + bool allowParent = true, + }) => + SelectorParser( + contents, + url: url, + allowParent: allowParent, + ).parseComplexSelector(); T accept(SelectorVisitor visitor) => visitor.visitComplexSelector(this); @@ -111,16 +122,23 @@ final class ComplexSelector extends Selector { /// @nodoc @internal ComplexSelector withAdditionalCombinators( - List> combinators, - {bool forceLineBreak = false}) { + List> combinators, { + bool forceLineBreak = false, + }) { if (combinators.isEmpty) return this; return switch (components) { - [...var initial, var last] => ComplexSelector(leadingCombinators, - [...initial, last.withAdditionalCombinators(combinators)], span, - lineBreak: lineBreak || forceLineBreak), + [...var initial, var last] => ComplexSelector( + leadingCombinators, + [...initial, last.withAdditionalCombinators(combinators)], + span, + lineBreak: lineBreak || forceLineBreak, + ), [] => ComplexSelector( - [...leadingCombinators, ...combinators], const [], span, - lineBreak: lineBreak || forceLineBreak) + [...leadingCombinators, ...combinators], + const [], + span, + lineBreak: lineBreak || forceLineBreak, + ), }; } @@ -134,10 +152,16 @@ final class ComplexSelector extends Selector { /// @nodoc @internal ComplexSelector withAdditionalComponent( - ComplexSelectorComponent component, FileSpan span, - {bool forceLineBreak = false}) => - ComplexSelector(leadingCombinators, [...components, component], span, - lineBreak: lineBreak || forceLineBreak); + ComplexSelectorComponent component, + FileSpan span, { + bool forceLineBreak = false, + }) => + ComplexSelector( + leadingCombinators, + [...components, component], + span, + lineBreak: lineBreak || forceLineBreak, + ); /// Returns a copy of `this` with [child]'s combinators added to the end. /// @@ -151,28 +175,36 @@ final class ComplexSelector extends Selector { /// /// @nodoc @internal - ComplexSelector concatenate(ComplexSelector child, FileSpan span, - {bool forceLineBreak = false}) { + ComplexSelector concatenate( + ComplexSelector child, + FileSpan span, { + bool forceLineBreak = false, + }) { if (child.leadingCombinators.isEmpty) { return ComplexSelector( - leadingCombinators, [...components, ...child.components], span, - lineBreak: lineBreak || child.lineBreak || forceLineBreak); + leadingCombinators, + [...components, ...child.components], + span, + lineBreak: lineBreak || child.lineBreak || forceLineBreak, + ); } else if (components case [...var initial, var last]) { return ComplexSelector( - leadingCombinators, - [ - ...initial, - last.withAdditionalCombinators(child.leadingCombinators), - ...child.components - ], - span, - lineBreak: lineBreak || child.lineBreak || forceLineBreak); + leadingCombinators, + [ + ...initial, + last.withAdditionalCombinators(child.leadingCombinators), + ...child.components, + ], + span, + lineBreak: lineBreak || child.lineBreak || forceLineBreak, + ); } else { return ComplexSelector( - [...leadingCombinators, ...child.leadingCombinators], - child.components, - span, - lineBreak: lineBreak || child.lineBreak || forceLineBreak); + [...leadingCombinators, ...child.leadingCombinators], + child.components, + span, + lineBreak: lineBreak || child.lineBreak || forceLineBreak, + ); } } diff --git a/lib/src/ast/selector/complex_component.dart b/lib/src/ast/selector/complex_component.dart index 70f6d8e42..fa9d85deb 100644 --- a/lib/src/ast/selector/complex_component.dart +++ b/lib/src/ast/selector/complex_component.dart @@ -28,8 +28,10 @@ final class ComplexSelectorComponent { final FileSpan span; ComplexSelectorComponent( - this.selector, Iterable> combinators, this.span) - : combinators = List.unmodifiable(combinators); + this.selector, + Iterable> combinators, + this.span, + ) : combinators = List.unmodifiable(combinators); /// Returns a copy of `this` with [combinators] added to the end of /// [this.combinators]. @@ -37,11 +39,17 @@ final class ComplexSelectorComponent { /// @nodoc @internal ComplexSelectorComponent withAdditionalCombinators( - List> combinators) => + List> combinators, + ) => combinators.isEmpty ? this : ComplexSelectorComponent( - selector, [...this.combinators, ...combinators], span); + selector, + [ + ...this.combinators, + ...combinators, + ], + span); int get hashCode => selector.hashCode ^ listHash(combinators); diff --git a/lib/src/ast/selector/compound.dart b/lib/src/ast/selector/compound.dart index 94cadc6df..fcdf270ae 100644 --- a/lib/src/ast/selector/compound.dart +++ b/lib/src/ast/selector/compound.dart @@ -28,8 +28,10 @@ final class CompoundSelector extends Selector { /// Specificity is represented in base 1000. The spec says this should be /// "sufficiently high"; it's extremely unlikely that any single selector /// sequence will contain 1000 simple selectors. - late final int specificity = - components.fold(0, (sum, component) => sum + component.specificity); + late final int specificity = components.fold( + 0, + (sum, component) => sum + component.specificity, + ); /// If this compound selector is composed of a single simple selector, returns /// it. @@ -50,8 +52,9 @@ final class CompoundSelector extends Selector { /// /// #nodoc @internal - late final bool hasComplicatedSuperselectorSemantics = components - .any((component) => component.hasComplicatedSuperselectorSemantics); + late final bool hasComplicatedSuperselectorSemantics = components.any( + (component) => component.hasComplicatedSuperselectorSemantics, + ); CompoundSelector(Iterable components, super.span) : components = List.unmodifiable(components) { @@ -67,10 +70,16 @@ final class CompoundSelector extends Selector { /// selector. /// /// Throws a [SassFormatException] if parsing fails. - factory CompoundSelector.parse(String contents, - {Object? url, bool allowParent = true}) => - SelectorParser(contents, url: url, allowParent: allowParent) - .parseCompoundSelector(); + factory CompoundSelector.parse( + String contents, { + Object? url, + bool allowParent = true, + }) => + SelectorParser( + contents, + url: url, + allowParent: allowParent, + ).parseCompoundSelector(); T accept(SelectorVisitor visitor) => visitor.visitCompoundSelector(this); diff --git a/lib/src/ast/selector/list.dart b/lib/src/ast/selector/list.dart index 96f3c4f27..3f745b512 100644 --- a/lib/src/ast/selector/list.dart +++ b/lib/src/ast/selector/list.dart @@ -34,17 +34,20 @@ final class SelectorList extends Selector { /// /// This has the same format as a list returned by `selector-parse()`. SassList get asSassList { - return SassList(components.map((complex) { - return SassList([ - for (var combinator in complex.leadingCombinators) - SassString(combinator.toString(), quotes: false), - for (var component in complex.components) ...[ - SassString(component.selector.toString(), quotes: false), - for (var combinator in component.combinators) - SassString(combinator.toString(), quotes: false) - ] - ], ListSeparator.space); - }), ListSeparator.comma); + return SassList( + components.map((complex) { + return SassList([ + for (var combinator in complex.leadingCombinators) + SassString(combinator.toString(), quotes: false), + for (var component in complex.components) ...[ + SassString(component.selector.toString(), quotes: false), + for (var combinator in component.combinators) + SassString(combinator.toString(), quotes: false), + ], + ], ListSeparator.space); + }), + ListSeparator.comma, + ); } SelectorList(Iterable components, super.span) @@ -65,17 +68,20 @@ final class SelectorList extends Selector { /// original location of the selector in the source file. /// /// Throws a [SassFormatException] if parsing fails. - factory SelectorList.parse(String contents, - {Object? url, - InterpolationMap? interpolationMap, - bool allowParent = true, - bool plainCss = false}) => - SelectorParser(contents, - url: url, - interpolationMap: interpolationMap, - allowParent: allowParent, - plainCss: plainCss) - .parse(); + factory SelectorList.parse( + String contents, { + Object? url, + InterpolationMap? interpolationMap, + bool allowParent = true, + bool plainCss = false, + }) => + SelectorParser( + contents, + url: url, + interpolationMap: interpolationMap, + allowParent: allowParent, + plainCss: plainCss, + ).parse(); T accept(SelectorVisitor visitor) => visitor.visitSelectorList(this); @@ -87,7 +93,7 @@ final class SelectorList extends Selector { var contents = [ for (var complex1 in components) for (var complex2 in other.components) - ...?unifyComplex([complex1, complex2], complex1.span) + ...?unifyComplex([complex1, complex2], complex1.span), ]; return contents.isEmpty ? null : SelectorList(contents, span); @@ -107,63 +113,86 @@ final class SelectorList extends Selector { /// so, this list is returned as-is if it doesn't contain any explicit /// [ParentSelector]s or if [preserveParentSelectors] is true. Otherwise, this /// throws a [SassScriptException]. - SelectorList nestWithin(SelectorList? parent, - {bool implicitParent = true, bool preserveParentSelectors = false}) { + SelectorList nestWithin( + SelectorList? parent, { + bool implicitParent = true, + bool preserveParentSelectors = false, + }) { if (parent == null) { if (preserveParentSelectors) return this; var parentSelector = accept(const _ParentSelectorVisitor()); if (parentSelector == null) return this; throw SassException( - 'Top-level selectors may not contain the parent selector "&".', - parentSelector.span); + 'Top-level selectors may not contain the parent selector "&".', + parentSelector.span, + ); } - return SelectorList(flattenVertically(components.map((complex) { - if (preserveParentSelectors || !_containsParentSelector(complex)) { - if (!implicitParent) return [complex]; - return parent.components.map((parentComplex) => - parentComplex.concatenate(complex, complex.span)); - } + return SelectorList( + flattenVertically( + components.map((complex) { + if (preserveParentSelectors || !_containsParentSelector(complex)) { + if (!implicitParent) return [complex]; + return parent.components.map( + (parentComplex) => + parentComplex.concatenate(complex, complex.span), + ); + } - var newComplexes = []; - for (var component in complex.components) { - var resolved = _nestWithinCompound(component, parent); - if (resolved == null) { - if (newComplexes.isEmpty) { - newComplexes.add(ComplexSelector( - complex.leadingCombinators, [component], complex.span, - lineBreak: false)); - } else { - for (var i = 0; i < newComplexes.length; i++) { - newComplexes[i] = newComplexes[i] - .withAdditionalComponent(component, complex.span); + var newComplexes = []; + for (var component in complex.components) { + var resolved = _nestWithinCompound(component, parent); + if (resolved == null) { + if (newComplexes.isEmpty) { + newComplexes.add( + ComplexSelector( + complex.leadingCombinators, + [component], + complex.span, + lineBreak: false, + ), + ); + } else { + for (var i = 0; i < newComplexes.length; i++) { + newComplexes[i] = newComplexes[i].withAdditionalComponent( + component, + complex.span, + ); + } + } + } else if (newComplexes.isEmpty) { + newComplexes.addAll( + complex.leadingCombinators.isEmpty + ? resolved + : resolved.map( + (resolvedComplex) => ComplexSelector( + resolvedComplex.leadingCombinators.isEmpty + ? complex.leadingCombinators + : [ + ...complex.leadingCombinators, + ...resolvedComplex.leadingCombinators, + ], + resolvedComplex.components, + complex.span, + lineBreak: resolvedComplex.lineBreak, + ), + ), + ); + } else { + var previousComplexes = newComplexes; + newComplexes = [ + for (var newComplex in previousComplexes) + for (var resolvedComplex in resolved) + newComplex.concatenate(resolvedComplex, newComplex.span), + ]; } } - } else if (newComplexes.isEmpty) { - newComplexes.addAll(complex.leadingCombinators.isEmpty - ? resolved - : resolved.map((resolvedComplex) => ComplexSelector( - resolvedComplex.leadingCombinators.isEmpty - ? complex.leadingCombinators - : [ - ...complex.leadingCombinators, - ...resolvedComplex.leadingCombinators - ], - resolvedComplex.components, - complex.span, - lineBreak: resolvedComplex.lineBreak))); - } else { - var previousComplexes = newComplexes; - newComplexes = [ - for (var newComplex in previousComplexes) - for (var resolvedComplex in resolved) - newComplex.concatenate(resolvedComplex, newComplex.span) - ]; - } - } - return newComplexes; - })), span); + return newComplexes; + }), + ), + span, + ); } /// Returns a new selector list based on [component] with all @@ -171,7 +200,9 @@ final class SelectorList extends Selector { /// /// Returns `null` if [component] doesn't contain any [ParentSelector]s. Iterable? _nestWithinCompound( - ComplexSelectorComponent component, SelectorList parent) { + ComplexSelectorComponent component, + SelectorList parent, + ) { var simples = component.selector.components; var containsSelectorPseudo = simples.any((simple) { if (simple is! PseudoSelector) return false; @@ -183,13 +214,16 @@ final class SelectorList extends Selector { } var resolvedSimples = containsSelectorPseudo - ? simples.map((simple) => switch (simple) { + ? simples.map( + (simple) => switch (simple) { PseudoSelector(:var selector?) when _containsParentSelector(selector) => simple.withSelector( - selector.nestWithin(parent, implicitParent: false)), - _ => simple - }) + selector.nestWithin(parent, implicitParent: false), + ), + _ => simple, + }, + ) : simples; var parentSelector = simples.first; @@ -198,10 +232,11 @@ final class SelectorList extends Selector { return [ ComplexSelector(const [], [ ComplexSelectorComponent( - CompoundSelector(resolvedSimples, component.selector.span), - component.combinators, - component.span) - ], component.span) + CompoundSelector(resolvedSimples, component.selector.span), + component.combinators, + component.span, + ), + ], component.span), ]; } else if (simples.length == 1 && parentSelector.suffix == null) { return parent @@ -210,9 +245,10 @@ final class SelectorList extends Selector { } } on SassException catch (error, stackTrace) { throwWithTrace( - error.withAdditionalSpan(parentSelector.span, "parent selector"), - error, - stackTrace); + error.withAdditionalSpan(parentSelector.span, "parent selector"), + error, + stackTrace, + ); } return parent.components.map((complex) { @@ -220,39 +256,46 @@ final class SelectorList extends Selector { var lastComponent = complex.components.last; if (lastComponent.combinators.isNotEmpty) { throw MultiSpanSassException( - 'Selector "$complex" can\'t be used as a parent in a compound ' - 'selector.', - lastComponent.span.trimRight(), - "outer selector", - {parentSelector.span: "parent selector"}); + 'Selector "$complex" can\'t be used as a parent in a compound ' + 'selector.', + lastComponent.span.trimRight(), + "outer selector", + {parentSelector.span: "parent selector"}, + ); } var suffix = parentSelector.suffix; var lastSimples = lastComponent.selector.components; var last = CompoundSelector( - suffix == null - ? [...lastSimples, ...resolvedSimples.skip(1)] - : [ - ...lastSimples.exceptLast, - lastSimples.last.addSuffix(suffix), - ...resolvedSimples.skip(1) - ], - component.selector.span); + suffix == null + ? [...lastSimples, ...resolvedSimples.skip(1)] + : [ + ...lastSimples.exceptLast, + lastSimples.last.addSuffix(suffix), + ...resolvedSimples.skip(1), + ], + component.selector.span, + ); return ComplexSelector( - complex.leadingCombinators, - [ - ...complex.components.exceptLast, - ComplexSelectorComponent( - last, component.combinators, component.span) - ], - component.span, - lineBreak: complex.lineBreak); + complex.leadingCombinators, + [ + ...complex.components.exceptLast, + ComplexSelectorComponent( + last, + component.combinators, + component.span, + ), + ], + component.span, + lineBreak: complex.lineBreak, + ); } on SassException catch (error, stackTrace) { throwWithTrace( - error.withAdditionalSpan(parentSelector.span, "parent selector"), - error, - stackTrace); + error.withAdditionalSpan(parentSelector.span, "parent selector"), + error, + stackTrace, + ); } }); } @@ -270,13 +313,16 @@ final class SelectorList extends Selector { /// @nodoc @internal SelectorList withAdditionalCombinators( - List> combinators) => + List> combinators, + ) => combinators.isEmpty ? this : SelectorList( components.map( - (complex) => complex.withAdditionalCombinators(combinators)), - span); + (complex) => complex.withAdditionalCombinators(combinators), + ), + span, + ); int get hashCode => listHash(components); diff --git a/lib/src/ast/selector/pseudo.dart b/lib/src/ast/selector/pseudo.dart index 930d49e08..f3f3b8ceb 100644 --- a/lib/src/ast/selector/pseudo.dart +++ b/lib/src/ast/selector/pseudo.dart @@ -108,9 +108,13 @@ final class PseudoSelector extends SimpleSelector { } }(); - PseudoSelector(this.name, FileSpan span, - {bool element = false, this.argument, this.selector}) - : isClass = !element && !_isFakePseudoElement(name), + PseudoSelector( + this.name, + FileSpan span, { + bool element = false, + this.argument, + this.selector, + }) : isClass = !element && !_isFakePseudoElement(name), isSyntacticClass = !element, normalizedName = unvendor(name), super(span); @@ -140,9 +144,13 @@ final class PseudoSelector extends SimpleSelector { /// Returns a new [PseudoSelector] based on this, but with the selector /// replaced with [selector]. - PseudoSelector withSelector(SelectorList selector) => - PseudoSelector(name, span, - element: isElement, argument: argument, selector: selector); + PseudoSelector withSelector(SelectorList selector) => PseudoSelector( + name, + span, + element: isElement, + argument: argument, + selector: selector, + ); /// @nodoc @internal @@ -155,9 +163,11 @@ final class PseudoSelector extends SimpleSelector { @internal List? unify(List compound) { if (name == 'host' || name == 'host-context') { - if (!compound.every((simple) => - simple is PseudoSelector && - (simple.isHost || simple.selector != null))) { + if (!compound.every( + (simple) => + simple is PseudoSelector && + (simple.isHost || simple.selector != null), + )) { return null; } } else if (compound case [var other] @@ -205,7 +215,9 @@ final class PseudoSelector extends SimpleSelector { // Fall back to the logic defined in functions.dart, which knows how to // compare selector pseudoclasses against raw selectors. - return CompoundSelector([this], span) + return CompoundSelector([ + this, + ], span) .isSuperselector(CompoundSelector([other], span)); } diff --git a/lib/src/ast/selector/simple.dart b/lib/src/ast/selector/simple.dart index 8440b6642..dabaa2b4f 100644 --- a/lib/src/ast/selector/simple.dart +++ b/lib/src/ast/selector/simple.dart @@ -18,7 +18,7 @@ final _subselectorPseudos = { 'where', 'any', 'nth-child', - 'nth-last-child' + 'nth-last-child', }; /// An abstract superclass for simple selectors. @@ -52,10 +52,16 @@ abstract base class SimpleSelector extends Selector { /// selector. /// /// Throws a [SassFormatException] if parsing fails. - factory SimpleSelector.parse(String contents, - {Object? url, bool allowParent = true}) => - SelectorParser(contents, url: url, allowParent: allowParent) - .parseSimpleSelector(); + factory SimpleSelector.parse( + String contents, { + Object? url, + bool allowParent = true, + }) => + SelectorParser( + contents, + url: url, + allowParent: allowParent, + ).parseSimpleSelector(); /// Returns a new [SimpleSelector] based on `this`, as though it had been /// written with [suffix] at the end. @@ -66,7 +72,11 @@ abstract base class SimpleSelector extends Selector { /// @nodoc @internal SimpleSelector addSuffix(String suffix) => throw MultiSpanSassException( - 'Selector "$this" can\'t have a suffix', span, "outer selector", {}); + 'Selector "$this" can\'t have a suffix', + span, + "outer selector", + {}, + ); /// Returns the components of a [CompoundSelector] that matches only elements /// matched by both this and [compound]. @@ -113,10 +123,13 @@ abstract base class SimpleSelector extends Selector { if (other is PseudoSelector && other.isClass) { var list = other.selector; if (list != null && _subselectorPseudos.contains(other.normalizedName)) { - return list.components.every((complex) => - complex.components.isNotEmpty && - complex.components.last.selector.components - .any((simple) => isSuperselector(simple))); + return list.components.every( + (complex) => + complex.components.isNotEmpty && + complex.components.last.selector.components.any( + (simple) => isSuperselector(simple), + ), + ); } } return false; diff --git a/lib/src/ast/selector/type.dart b/lib/src/ast/selector/type.dart index a5ada4b8f..1184889fa 100644 --- a/lib/src/ast/selector/type.dart +++ b/lib/src/ast/selector/type.dart @@ -27,7 +27,9 @@ final class TypeSelector extends SimpleSelector { /// @nodoc @internal TypeSelector addSuffix(String suffix) => TypeSelector( - QualifiedName(name.name + suffix, namespace: name.namespace), span); + QualifiedName(name.name + suffix, namespace: name.namespace), + span, + ); /// @nodoc @internal diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index dcbd7337e..d11e2555c 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -28,30 +28,33 @@ import 'visitor/serialize.dart'; /// /// If both `importCache` and `nodeImporter` are provided, the importers in /// `importCache` will be evaluated before `nodeImporter`. -Future compileAsync(String path, - {Syntax? syntax, - Logger? logger, - AsyncImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? functions, - OutputStyle? style, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) async { +Future compileAsync( + String path, { + Syntax? syntax, + Logger? logger, + AsyncImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? functions, + OutputStyle? style, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) async { DeprecationProcessingLogger deprecationLogger = - logger = DeprecationProcessingLogger(logger ?? Logger.stderr(), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - limitRepetition: !verbose) - ..validate(); + logger = DeprecationProcessingLogger( + logger ?? Logger.stderr(), + silenceDeprecations: {...?silenceDeprecations}, + fatalDeprecations: {...?fatalDeprecations}, + futureDeprecations: {...?futureDeprecations}, + limitRepetition: !verbose, + )..validate(); // If the syntax is different than the importer would default to, we have to // parse the file manually and we can't store it in the cache. @@ -60,28 +63,33 @@ Future compileAsync(String path, (syntax == null || syntax == Syntax.forPath(path))) { importCache ??= AsyncImportCache.none(); stylesheet = (await importCache.importCanonical( - FilesystemImporter.cwd, p.toUri(canonicalize(path)), - originalUrl: p.toUri(path)))!; + FilesystemImporter.cwd, + p.toUri(canonicalize(path)), + originalUrl: p.toUri(path), + ))!; } else { stylesheet = Stylesheet.parse( - readFile(path), syntax ?? Syntax.forPath(path), - url: p.toUri(path)); + readFile(path), + syntax ?? Syntax.forPath(path), + url: p.toUri(path), + ); } var result = await _compileStylesheet( - stylesheet, - logger, - importCache, - nodeImporter, - FilesystemImporter.cwd, - functions, - style, - useSpaces, - indentWidth, - lineFeed, - quietDeps, - sourceMap, - charset); + stylesheet, + logger, + importCache, + nodeImporter, + FilesystemImporter.cwd, + functions, + style, + useSpaces, + indentWidth, + lineFeed, + quietDeps, + sourceMap, + charset, + ); deprecationLogger.summarize(js: nodeImporter != null); return result; @@ -91,51 +99,55 @@ Future compileAsync(String path, /// support the node-sass compatible API. /// /// At most one of `importCache` and `nodeImporter` may be provided at once. -Future compileStringAsync(String source, - {Syntax? syntax, - Logger? logger, - AsyncImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? importers, - Iterable? loadPaths, - AsyncImporter? importer, - Iterable? functions, - OutputStyle? style, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - Object? url, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) async { +Future compileStringAsync( + String source, { + Syntax? syntax, + Logger? logger, + AsyncImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? importers, + Iterable? loadPaths, + AsyncImporter? importer, + Iterable? functions, + OutputStyle? style, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + Object? url, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) async { DeprecationProcessingLogger deprecationLogger = - logger = DeprecationProcessingLogger(logger ?? Logger.stderr(), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - limitRepetition: !verbose) - ..validate(); + logger = DeprecationProcessingLogger( + logger ?? Logger.stderr(), + silenceDeprecations: {...?silenceDeprecations}, + fatalDeprecations: {...?fatalDeprecations}, + futureDeprecations: {...?futureDeprecations}, + limitRepetition: !verbose, + )..validate(); var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url); var result = await _compileStylesheet( - stylesheet, - logger, - importCache, - nodeImporter, - importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), - functions, - style, - useSpaces, - indentWidth, - lineFeed, - quietDeps, - sourceMap, - charset); + stylesheet, + logger, + importCache, + nodeImporter, + importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), + functions, + style, + useSpaces, + indentWidth, + lineFeed, + quietDeps, + sourceMap, + charset, + ); deprecationLogger.summarize(js: nodeImporter != null); return result; @@ -145,53 +157,61 @@ Future compileStringAsync(String source, /// /// Arguments are handled as for [compileStringAsync]. Future _compileStylesheet( - Stylesheet stylesheet, - Logger? logger, - AsyncImportCache? importCache, - NodeImporter? nodeImporter, - AsyncImporter importer, - Iterable? functions, - OutputStyle? style, - bool useSpaces, - int? indentWidth, - LineFeed? lineFeed, - bool quietDeps, - bool sourceMap, - bool charset) async { + Stylesheet stylesheet, + Logger? logger, + AsyncImportCache? importCache, + NodeImporter? nodeImporter, + AsyncImporter importer, + Iterable? functions, + OutputStyle? style, + bool useSpaces, + int? indentWidth, + LineFeed? lineFeed, + bool quietDeps, + bool sourceMap, + bool charset, +) async { if (nodeImporter != null) { logger?.warnForDeprecation( - Deprecation.legacyJsApi, - 'The legacy JS API is deprecated and will be removed in ' - 'Dart Sass 2.0.0.\n\n' - 'More info: https://sass-lang.com/d/legacy-js-api'); + Deprecation.legacyJsApi, + 'The legacy JS API is deprecated and will be removed in ' + 'Dart Sass 2.0.0.\n\n' + 'More info: https://sass-lang.com/d/legacy-js-api', + ); } - var evaluateResult = await evaluateAsync(stylesheet, - importCache: importCache, - nodeImporter: nodeImporter, - importer: importer, - functions: functions, - logger: logger, - quietDeps: quietDeps, - sourceMap: sourceMap); + var evaluateResult = await evaluateAsync( + stylesheet, + importCache: importCache, + nodeImporter: nodeImporter, + importer: importer, + functions: functions, + logger: logger, + quietDeps: quietDeps, + sourceMap: sourceMap, + ); - var serializeResult = serialize(evaluateResult.stylesheet, - style: style, - useSpaces: useSpaces, - indentWidth: indentWidth, - lineFeed: lineFeed, - logger: logger, - sourceMap: sourceMap, - charset: charset); + var serializeResult = serialize( + evaluateResult.stylesheet, + style: style, + useSpaces: useSpaces, + indentWidth: indentWidth, + lineFeed: lineFeed, + logger: logger, + sourceMap: sourceMap, + charset: charset, + ); var resultSourceMap = serializeResult.sourceMap; if (resultSourceMap != null && importCache != null) { mapInPlace( - resultSourceMap.urls, - (url) => url == '' - ? Uri.dataFromString(stylesheet.span.file.getText(0), - encoding: utf8) - .toString() - : importCache.sourceMapUrl(Uri.parse(url)).toString()); + resultSourceMap.urls, + (url) => url == '' + ? Uri.dataFromString( + stylesheet.span.file.getText(0), + encoding: utf8, + ).toString() + : importCache.sourceMapUrl(Uri.parse(url)).toString(), + ); } return CompileResult(evaluateResult, serializeResult); diff --git a/lib/src/async_environment.dart b/lib/src/async_environment.dart index 371d68faf..b2b9f6521 100644 --- a/lib/src/async_environment.dart +++ b/lib/src/async_environment.dart @@ -163,23 +163,24 @@ final class AsyncEnvironment { _mixinIndices = {}; AsyncEnvironment._( - this._modules, - this._namespaceNodes, - this._globalModules, - this._importedModules, - this._forwardedModules, - this._nestedForwardedModules, - this._allModules, - this._variables, - this._variableNodes, - this._functions, - this._mixins, - this._content) - // Lazily fill in the indices rather than eagerly copying them from the - // existing environment in closure() because the copying took a lot of - // time and was rarely helpful. This saves a bunch of time on Susy's - // tests. - : _variableIndices = {}, + this._modules, + this._namespaceNodes, + this._globalModules, + this._importedModules, + this._forwardedModules, + this._nestedForwardedModules, + this._allModules, + this._variables, + this._variableNodes, + this._functions, + this._mixins, + this._content, + ) + // Lazily fill in the indices rather than eagerly copying them from the + // existing environment in closure() because the copying took a lot of + // time and was rarely helpful. This saves a bunch of time on Susy's + // tests. + : _variableIndices = {}, _functionIndices = {}, _mixinIndices = {}; @@ -189,18 +190,19 @@ final class AsyncEnvironment { /// However, any new declarations or assignments in scopes that are visible /// when the closure was created will be reflected. AsyncEnvironment closure() => AsyncEnvironment._( - _modules, - _namespaceNodes, - _globalModules, - _importedModules, - _forwardedModules, - _nestedForwardedModules, - _allModules, - _variables.toList(), - _variableNodes.toList(), - _functions.toList(), - _mixins.toList(), - _content); + _modules, + _namespaceNodes, + _globalModules, + _importedModules, + _forwardedModules, + _nestedForwardedModules, + _allModules, + _variables.toList(), + _variableNodes.toList(), + _functions.toList(), + _mixins.toList(), + _content, + ); /// Returns a new environment to use for an imported file. /// @@ -208,18 +210,19 @@ final class AsyncEnvironment { /// and mixins, but excludes most modules (except for global modules that /// result from importing a file with forwards). AsyncEnvironment forImport() => AsyncEnvironment._( - {}, - {}, - {}, - _importedModules, - null, - null, - [], - _variables.toList(), - _variableNodes.toList(), - _functions.toList(), - _mixins.toList(), - _content); + {}, + {}, + {}, + _importedModules, + null, + null, + [], + _variables.toList(), + _variableNodes.toList(), + _functions.toList(), + _mixins.toList(), + _content, + ); /// Adds [module] to the set of modules visible in this environment. /// @@ -239,16 +242,18 @@ final class AsyncEnvironment { if (_variables.first.keys.firstWhereOrNull(module.variables.containsKey) case var name?) { throw SassScriptException( - 'This module and the new module both define a variable named ' - '"\$$name".'); + 'This module and the new module both define a variable named ' + '"\$$name".', + ); } } else { if (_modules.containsKey(namespace)) { var span = _namespaceNodes[namespace]?.span; throw MultiSpanSassScriptException( - "There's already a module with namespace \"$namespace\".", - "new @use", - {if (span != null) span: "original @use"}); + "There's already a module with namespace \"$namespace\".", + "new @use", + {if (span != null) span: "original @use"}, + ); } _modules[namespace] = module; @@ -265,9 +270,19 @@ final class AsyncEnvironment { var view = ForwardedModuleView.ifNecessary(module, rule); for (var other in forwardedModules.keys) { _assertNoConflicts( - view.variables, other.variables, view, other, "variable"); + view.variables, + other.variables, + view, + other, + "variable", + ); _assertNoConflicts( - view.functions, other.functions, view, other, "function"); + view.functions, + other.functions, + view, + other, + "function", + ); _assertNoConflicts(view.mixins, other.mixins, view, other, "mixin"); } @@ -284,11 +299,12 @@ final class AsyncEnvironment { /// /// The [type] and [newModuleNodeWithSpan] are used for error reporting. void _assertNoConflicts( - Map newMembers, - Map oldMembers, - Module newModule, - Module oldModule, - String type) { + Map newMembers, + Map oldMembers, + Module newModule, + Module oldModule, + String type, + ) { Map smaller; Map larger; if (newMembers.length < oldMembers.length) { @@ -311,9 +327,10 @@ final class AsyncEnvironment { if (type == "variable") name = "\$$name"; var span = _forwardedModules?[oldModule]?.span; throw MultiSpanSassScriptException( - 'Two forwarded modules both define a $type named $name.', - "new @forward", - {if (span != null) span: "original @forward"}); + 'Two forwarded modules both define a $type named $name.', + "new @forward", + {if (span != null) span: "original @forward"}, + ); } } @@ -341,23 +358,25 @@ final class AsyncEnvironment { } var forwardedVariableNames = { - for (var module in forwarded.keys) ...module.variables.keys + for (var module in forwarded.keys) ...module.variables.keys, }; var forwardedFunctionNames = { - for (var module in forwarded.keys) ...module.functions.keys + for (var module in forwarded.keys) ...module.functions.keys, }; var forwardedMixinNames = { - for (var module in forwarded.keys) ...module.mixins.keys + for (var module in forwarded.keys) ...module.mixins.keys, }; if (atRoot) { // Hide members from modules that have already been imported or // forwarded that would otherwise conflict with the @imported members. for (var (module, node) in _importedModules.pairs.toList()) { - var shadowed = ShadowedModuleView.ifNecessary(module, - variables: forwardedVariableNames, - mixins: forwardedMixinNames, - functions: forwardedFunctionNames); + var shadowed = ShadowedModuleView.ifNecessary( + module, + variables: forwardedVariableNames, + mixins: forwardedMixinNames, + functions: forwardedFunctionNames, + ); if (shadowed != null) { _importedModules.remove(module); if (!shadowed.isEmpty) _importedModules[shadowed] = node; @@ -365,10 +384,12 @@ final class AsyncEnvironment { } for (var (module, node) in forwardedModules.pairs.toList()) { - var shadowed = ShadowedModuleView.ifNecessary(module, - variables: forwardedVariableNames, - mixins: forwardedMixinNames, - functions: forwardedFunctionNames); + var shadowed = ShadowedModuleView.ifNecessary( + module, + variables: forwardedVariableNames, + mixins: forwardedMixinNames, + functions: forwardedFunctionNames, + ); if (shadowed != null) { forwardedModules.remove(module); if (!shadowed.isEmpty) forwardedModules[shadowed] = node; @@ -378,8 +399,10 @@ final class AsyncEnvironment { _importedModules.addAll(forwarded); forwardedModules.addAll(forwarded); } else { - (_nestedForwardedModules ??= - List.generate(_variables.length - 1, (_) => [])) + (_nestedForwardedModules ??= List.generate( + _variables.length - 1, + (_) => [], + )) .last .addAll(forwarded.keys); } @@ -528,8 +551,13 @@ final class AsyncEnvironment { /// defined with the given namespace, if no variable with the given [name] is /// defined in module with the given namespace, or if no [namespace] is passed /// and multiple global modules define variables named [name]. - void setVariable(String name, Value value, AstNode nodeWithSpan, - {String? namespace, bool global = false}) { + void setVariable( + String name, + Value value, + AstNode nodeWithSpan, { + String? namespace, + bool global = false, + }) { if (namespace != null) { _getModule(namespace).setVariable(name, value, nodeWithSpan); return; @@ -547,8 +575,11 @@ final class AsyncEnvironment { // If this module doesn't already contain a variable named [name], try // setting it in a global module. if (!_variables.first.containsKey(name)) { - var moduleWithName = _fromOneModule(name, "variable", - (module) => module.variables.containsKey(name) ? module : null); + var moduleWithName = _fromOneModule( + name, + "variable", + (module) => module.variables.containsKey(name) ? module : null, + ); if (moduleWithName != null) { moduleWithName.setVariable(name, value, nodeWithSpan); return; @@ -577,7 +608,9 @@ final class AsyncEnvironment { var index = _lastVariableName == name ? _lastVariableIndex! : _variableIndices.putIfAbsent( - name, () => _variableIndex(name) ?? _variables.length - 1); + name, + () => _variableIndex(name) ?? _variables.length - 1, + ); if (!_inSemiGlobalScope && index == 0) { index = _variables.length - 1; _variableIndices[name] = index; @@ -702,8 +735,10 @@ final class AsyncEnvironment { } /// Sets [content] as [this.content] for the duration of [callback]. - Future withContent(UserDefinedCallable? content, - Future callback()) async { + Future withContent( + UserDefinedCallable? content, + Future callback(), + ) async { var oldContent = _content; _content = content; await callback(); @@ -726,8 +761,11 @@ final class AsyncEnvironment { /// /// If [when] is false, this doesn't create a new scope and instead just /// executes [callback] and returns its result. - Future scope(Future callback(), - {bool semiGlobal = false, bool when = true}) async { + Future scope( + Future callback(), { + bool semiGlobal = false, + bool when = true, + }) async { // We have to track semi-globalness even if `!when` so that // // div { @@ -792,12 +830,18 @@ final class AsyncEnvironment { /// that contains [css] and [preModuleComments] as its CSS, which can be /// extended using [extensionStore]. Module toModule( - CssStylesheet css, - Map> preModuleComments, - ExtensionStore extensionStore) { + CssStylesheet css, + Map> preModuleComments, + ExtensionStore extensionStore, + ) { assert(atRoot); - return _EnvironmentModule(this, css, preModuleComments, extensionStore, - forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); + return _EnvironmentModule( + this, + css, + preModuleComments, + extensionStore, + forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules)), + ); } /// Returns a module with the same members and upstream modules as `this`, but @@ -807,19 +851,23 @@ final class AsyncEnvironment { /// members into the current scope. It's the only situation in which a nested /// environment can become a module. Module toDummyModule() => _EnvironmentModule( - this, - CssStylesheet(const [], - SourceFile.decoded(const [], url: "").span(0)), - const {}, - ExtensionStore.empty, - forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); + this, + CssStylesheet( + const [], + SourceFile.decoded(const [], url: "").span(0), + ), + const {}, + ExtensionStore.empty, + forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules)), + ); /// Returns the module with the given [namespace], or throws a /// [SassScriptException] if none exists. Module _getModule(String namespace) { if (_modules[namespace] case var module?) return module; throw SassScriptException( - 'There is no module with the namespace "$namespace".'); + 'There is no module with the namespace "$namespace".', + ); } /// Returns the result of [callback] if it returns non-`null` for exactly one @@ -858,11 +906,13 @@ final class AsyncEnvironment { if (value != null) { throw MultiSpanSassScriptException( - 'This $type is available from multiple global modules.', - '$type use', { - for (var (module, node) in _globalModules.pairs) - if (callback(module) != null) node.span: 'includes $type' - }); + 'This $type is available from multiple global modules.', + '$type use', + { + for (var (module, node) in _globalModules.pairs) + if (callback(module) != null) node.span: 'includes $type', + }, + ); } value = valueInModule; @@ -899,36 +949,48 @@ final class _EnvironmentModule implements Module { final Map _modulesByVariable; factory _EnvironmentModule( - AsyncEnvironment environment, - CssStylesheet css, - Map> preModuleComments, - ExtensionStore extensionStore, - {Set? forwarded}) { + AsyncEnvironment environment, + CssStylesheet css, + Map> preModuleComments, + ExtensionStore extensionStore, { + Set? forwarded, + }) { forwarded ??= const {}; return _EnvironmentModule._( - environment, - css, - Map.unmodifiable({ - for (var (module, comments) in preModuleComments.pairs) - module: List.unmodifiable(comments) - }), - extensionStore, - _makeModulesByVariable(forwarded), - _memberMap(environment._variables.first, - forwarded.map((module) => module.variables)), - _memberMap(environment._variableNodes.first, - forwarded.map((module) => module.variableNodes)), - _memberMap(environment._functions.first, - forwarded.map((module) => module.functions)), - _memberMap(environment._mixins.first, - forwarded.map((module) => module.mixins)), - transitivelyContainsCss: css.children.isNotEmpty || - preModuleComments.isNotEmpty || - environment._allModules - .any((module) => module.transitivelyContainsCss), - transitivelyContainsExtensions: !extensionStore.isEmpty || - environment._allModules - .any((module) => module.transitivelyContainsExtensions)); + environment, + css, + Map.unmodifiable({ + for (var (module, comments) in preModuleComments.pairs) + module: List.unmodifiable(comments), + }), + extensionStore, + _makeModulesByVariable(forwarded), + _memberMap( + environment._variables.first, + forwarded.map((module) => module.variables), + ), + _memberMap( + environment._variableNodes.first, + forwarded.map((module) => module.variableNodes), + ), + _memberMap( + environment._functions.first, + forwarded.map((module) => module.functions), + ), + _memberMap( + environment._mixins.first, + forwarded.map((module) => module.mixins), + ), + transitivelyContainsCss: css.children.isNotEmpty || + preModuleComments.isNotEmpty || + environment._allModules.any( + (module) => module.transitivelyContainsCss, + ), + transitivelyContainsExtensions: !extensionStore.isEmpty || + environment._allModules.any( + (module) => module.transitivelyContainsExtensions, + ), + ); } /// Create [_modulesByVariable] for a set of forwarded modules. @@ -942,8 +1004,11 @@ final class _EnvironmentModule implements Module { for (var child in module._modulesByVariable.values) { setAll(modulesByVariable, child.variables.keys, child); } - setAll(modulesByVariable, module._environment._variables.first.keys, - module); + setAll( + modulesByVariable, + module._environment._variables.first.keys, + module, + ); } else { setAll(modulesByVariable, module.variables.keys, module); } @@ -954,14 +1019,16 @@ final class _EnvironmentModule implements Module { /// Returns a map that exposes the public members of [localMap] as well as all /// the members of [otherMaps]. static Map _memberMap( - Map localMap, Iterable> otherMaps) { + Map localMap, + Iterable> otherMaps, + ) { localMap = PublicMemberMapView(localMap); if (otherMaps.isEmpty) return localMap; var allMaps = [ for (var map in otherMaps) if (map.isNotEmpty) map, - localMap + localMap, ]; if (allMaps.length == 1) return localMap; @@ -969,18 +1036,18 @@ final class _EnvironmentModule implements Module { } _EnvironmentModule._( - this._environment, - this.css, - this.preModuleComments, - this.extensionStore, - this._modulesByVariable, - this.variables, - this.variableNodes, - this.functions, - this.mixins, - {required this.transitivelyContainsCss, - required this.transitivelyContainsExtensions}) - : upstream = _environment._allModules; + this._environment, + this.css, + this.preModuleComments, + this.extensionStore, + this._modulesByVariable, + this.variables, + this.variableNodes, + this.functions, + this.mixins, { + required this.transitivelyContainsCss, + required this.transitivelyContainsExtensions, + }) : upstream = _environment._allModules; void setVariable(String name, Value value, AstNode nodeWithSpan) { if (_modulesByVariable[name] case var module?) { @@ -1006,20 +1073,23 @@ final class _EnvironmentModule implements Module { Module cloneCss() { if (!transitivelyContainsCss) return this; - var (newStylesheet, newExtensionStore) = - cloneCssStylesheet(css, extensionStore); + var (newStylesheet, newExtensionStore) = cloneCssStylesheet( + css, + extensionStore, + ); return _EnvironmentModule._( - _environment, - newStylesheet, - preModuleComments, - newExtensionStore, - _modulesByVariable, - variables, - variableNodes, - functions, - mixins, - transitivelyContainsCss: transitivelyContainsCss, - transitivelyContainsExtensions: transitivelyContainsExtensions); + _environment, + newStylesheet, + preModuleComments, + newExtensionStore, + _modulesByVariable, + variables, + variableNodes, + functions, + mixins, + transitivelyContainsCss: transitivelyContainsCss, + transitivelyContainsExtensions: transitivelyContainsExtensions, + ); } String toString() => url == null ? "" : p.prettyUri(url); diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index b07735664..e1dc17651 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -90,11 +90,11 @@ final class AsyncImportCache { /// this is a shorthand for adding a [PackageImporter] to [importers]. /// /// [`PackageConfig`]: https://pub.dev/documentation/package_config/latest/package_config.package_config/PackageConfig-class.html - AsyncImportCache( - {Iterable? importers, - Iterable? loadPaths, - PackageConfig? packageConfig}) - : _importers = _toImporters(importers, loadPaths, packageConfig); + AsyncImportCache({ + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + }) : _importers = _toImporters(importers, loadPaths, packageConfig); /// Creates an import cache without any globally-available importers. AsyncImportCache.none() : _importers = const []; @@ -106,8 +106,11 @@ final class AsyncImportCache { /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. - static List _toImporters(Iterable? importers, - Iterable? loadPaths, PackageConfig? packageConfig) { + static List _toImporters( + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + ) { var sassPath = getEnvironmentVariable('SASS_PATH'); if (isBrowser) return [...?importers]; return [ @@ -117,7 +120,7 @@ final class AsyncImportCache { if (sassPath != null) for (var path in sassPath.split(isWindows ? ';' : ':')) FilesystemImporter(path), - if (packageConfig != null) PackageImporter(packageConfig) + if (packageConfig != null) PackageImporter(packageConfig), ]; } @@ -136,10 +139,12 @@ final class AsyncImportCache { /// If any importers understand [url], returns that importer as well as the /// canonicalized URL and the original URL (resolved relative to [baseUrl] if /// applicable). Otherwise, returns `null`. - Future canonicalize(Uri url, - {AsyncImporter? baseImporter, - Uri? baseUrl, - bool forImport = false}) async { + Future canonicalize( + Uri url, { + AsyncImporter? baseImporter, + Uri? baseUrl, + bool forImport = false, + }) async { if (isBrowser && (baseImporter == null || baseImporter is NoOpImporter) && _importers.isEmpty) { @@ -150,17 +155,25 @@ final class AsyncImportCache { if (baseImporter != null && url.scheme == '') { var resolvedUrl = baseUrl?.resolveUri(url) ?? url; var key = (baseImporter, resolvedUrl, forImport: forImport); - var relativeResult = - await putIfAbsentAsync(_perImporterCanonicalizeCache, key, () async { - var (result, cacheable) = - await _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport); - assert( + var relativeResult = await putIfAbsentAsync( + _perImporterCanonicalizeCache, + key, + () async { + var (result, cacheable) = await _canonicalize( + baseImporter, + resolvedUrl, + baseUrl, + forImport, + ); + assert( cacheable, "Relative loads should always be cacheable because they never " - "provide access to the containing URL."); - if (baseUrl != null) _nonCanonicalRelativeUrls[key] = url; - return result; - }); + "provide access to the containing URL.", + ); + if (baseUrl != null) _nonCanonicalRelativeUrls[key] = url; + return result; + }, + ); if (relativeResult != null) return relativeResult; } @@ -202,7 +215,7 @@ final class AsyncImportCache { _perImporterCanonicalizeCache[( _importers[j], url, - forImport: forImport + forImport: forImport, )] = null; } cacheable = false; @@ -222,15 +235,23 @@ final class AsyncImportCache { /// This returns both the result of the call to `canonicalize()` and whether /// that result is cacheable at all. Future<(AsyncCanonicalizeResult?, bool cacheable)> _canonicalize( - AsyncImporter importer, Uri url, Uri? baseUrl, bool forImport) async { + AsyncImporter importer, + Uri url, + Uri? baseUrl, + bool forImport, + ) async { var passContainingUrl = baseUrl != null && (url.scheme == '' || await importer.isNonCanonicalScheme(url.scheme)); - var canonicalizeContext = - CanonicalizeContext(passContainingUrl ? baseUrl : null, forImport); + var canonicalizeContext = CanonicalizeContext( + passContainingUrl ? baseUrl : null, + forImport, + ); var result = await withCanonicalizeContext( - canonicalizeContext, () => importer.canonicalize(url)); + canonicalizeContext, + () => importer.canonicalize(url), + ); var cacheable = !passContainingUrl || !canonicalizeContext.wasContainingUrlAccessed; @@ -258,15 +279,24 @@ final class AsyncImportCache { /// parsed stylesheet. Otherwise, returns `null`. /// /// Caches the result of the import and uses cached results if possible. - Future<(AsyncImporter, Stylesheet)?> import(Uri url, - {AsyncImporter? baseImporter, - Uri? baseUrl, - bool forImport = false}) async { - if (await canonicalize(url, - baseImporter: baseImporter, baseUrl: baseUrl, forImport: forImport) + Future<(AsyncImporter, Stylesheet)?> import( + Uri url, { + AsyncImporter? baseImporter, + Uri? baseUrl, + bool forImport = false, + }) async { + if (await canonicalize( + url, + baseImporter: baseImporter, + baseUrl: baseUrl, + forImport: forImport, + ) case (var importer, var canonicalUrl, :var originalUrl)) { - return (await importCanonical(importer, canonicalUrl, - originalUrl: originalUrl)) + return (await importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + )) .andThen((stylesheet) => (importer, stylesheet)); } else { return null; @@ -283,8 +313,11 @@ final class AsyncImportCache { /// importers may return for legacy reasons. /// /// Caches the result of the import and uses cached results if possible. - Future importCanonical(AsyncImporter importer, Uri canonicalUrl, - {Uri? originalUrl}) async { + Future importCanonical( + AsyncImporter importer, + Uri canonicalUrl, { + Uri? originalUrl, + }) async { return await putIfAbsentAsync(_importCache, canonicalUrl, () async { var loadTime = DateTime.now(); var result = await importer.load(canonicalUrl); @@ -292,12 +325,15 @@ final class AsyncImportCache { _loadTimes[canonicalUrl] = loadTime; _resultsCache[canonicalUrl] = result; - return Stylesheet.parse(result.contents, result.syntax, - // For backwards-compatibility, relative canonical URLs are resolved - // relative to [originalUrl]. - url: originalUrl == null - ? canonicalUrl - : originalUrl.resolveUri(canonicalUrl)); + return Stylesheet.parse( + result.contents, + result.syntax, + // For backwards-compatibility, relative canonical URLs are resolved + // relative to [originalUrl]. + url: originalUrl == null + ? canonicalUrl + : originalUrl.resolveUri(canonicalUrl), + ); }); } @@ -308,10 +344,11 @@ final class AsyncImportCache { // If multiple original URLs canonicalize to the same thing, choose the // shortest one. minBy( - _canonicalizeCache.values.nonNulls - .where((result) => result.$2 == canonicalUrl) - .map((result) => result.originalUrl), - (url) => url.path.length) + _canonicalizeCache.values.nonNulls + .where((result) => result.$2 == canonicalUrl) + .map((result) => result.originalUrl), + (url) => url.path.length, + ) // Use the canonicalized basename so that we display e.g. // package:example/_example.scss rather than package:example/example // in stack traces. diff --git a/lib/src/callable.dart b/lib/src/callable.dart index 1fda63247..c6d3b0321 100644 --- a/lib/src/callable.dart +++ b/lib/src/callable.dart @@ -70,8 +70,11 @@ export 'callable/user_defined.dart'; @sealed abstract interface class Callable implements AsyncCallable { @Deprecated('Use `Callable.function` instead.') - factory Callable(String name, String arguments, - Value callback(List arguments)) => + factory Callable( + String name, + String arguments, + Value callback(List arguments), + ) => Callable.function(name, arguments, callback); /// Creates a function with the given [name] and [arguments] that runs @@ -116,18 +119,25 @@ abstract interface class Callable implements AsyncCallable { /// Note that the argument list is always an instance of [SassArgumentList], /// which provides access to keyword arguments using /// [SassArgumentList.keywords]. - factory Callable.function(String name, String arguments, - Value callback(List arguments)) => + factory Callable.function( + String name, + String arguments, + Value callback(List arguments), + ) => BuiltInCallable.function(name, arguments, callback); /// Creates a callable with a single [signature] and a single [callback]. /// /// Throws a [SassFormatException] if parsing fails. factory Callable.fromSignature( - String signature, Value callback(List arguments), - {bool requireParens = true}) { - var (name, declaration) = - parseSignature(signature, requireParens: requireParens); + String signature, + Value callback(List arguments), { + bool requireParens = true, + }) { + var (name, declaration) = parseSignature( + signature, + requireParens: requireParens, + ); return BuiltInCallable.parsed(name, declaration, callback); } } diff --git a/lib/src/callable/async.dart b/lib/src/callable/async.dart index 433ca98b6..60a53a8a2 100644 --- a/lib/src/callable/async.dart +++ b/lib/src/callable/async.dart @@ -27,8 +27,11 @@ abstract interface class AsyncCallable { String get name; @Deprecated('Use `AsyncCallable.function` instead.') - factory AsyncCallable(String name, String arguments, - FutureOr callback(List arguments)) => + factory AsyncCallable( + String name, + String arguments, + FutureOr callback(List arguments), + ) => AsyncCallable.function(name, arguments, callback); /// Creates a callable with the given [name] and [arguments] that runs @@ -38,18 +41,25 @@ abstract interface class AsyncCallable { /// include parentheses. Throws a [SassFormatException] if parsing fails. /// /// See [Callable.new] for more details. - factory AsyncCallable.function(String name, String arguments, - FutureOr callback(List arguments)) => + factory AsyncCallable.function( + String name, + String arguments, + FutureOr callback(List arguments), + ) => AsyncBuiltInCallable.function(name, arguments, callback); /// Creates a callable with a single [signature] and a single [callback]. /// /// Throws a [SassFormatException] if parsing fails. factory AsyncCallable.fromSignature( - String signature, FutureOr callback(List arguments), - {bool requireParens = true}) { - var (name, declaration) = - parseSignature(signature, requireParens: requireParens); + String signature, + FutureOr callback(List arguments), { + bool requireParens = true, + }) { + var (name, declaration) = parseSignature( + signature, + requireParens: requireParens, + ); return AsyncBuiltInCallable.parsed(name, declaration, callback); } } diff --git a/lib/src/callable/async_built_in.dart b/lib/src/callable/async_built_in.dart index 3736f47a9..e4e7d50bd 100644 --- a/lib/src/callable/async_built_in.dart +++ b/lib/src/callable/async_built_in.dart @@ -41,12 +41,16 @@ class AsyncBuiltInCallable implements AsyncCallable { /// /// If passed, [url] is the URL of the module in which the function is /// defined. - AsyncBuiltInCallable.function(String name, String parameters, - FutureOr callback(List parameters), {Object? url}) - : this.parsed( - name, - ParameterList.parse('@function $name($parameters) {', url: url), - callback); + AsyncBuiltInCallable.function( + String name, + String parameters, + FutureOr callback(List parameters), { + Object? url, + }) : this.parsed( + name, + ParameterList.parse('@function $name($parameters) {', url: url), + callback, + ); /// Creates a mixin with a single [parameters] declaration and a single /// [callback]. @@ -56,24 +60,33 @@ class AsyncBuiltInCallable implements AsyncCallable { /// /// If passed, [url] is the URL of the module in which the mixin is /// defined. - AsyncBuiltInCallable.mixin(String name, String parameters, - FutureOr callback(List parameters), - {Object? url, bool acceptsContent = false}) - : this.parsed( - name, ParameterList.parse('@mixin $name($parameters) {', url: url), - (arguments) async { - await callback(arguments); - // We could encode the fact that functions return values and mixins - // don't in the type system, but that would get very messy very - // quickly so it's easier to just return Sass's `null` for mixins and - // simply ignore it at the call site. - return sassNull; - }); + AsyncBuiltInCallable.mixin( + String name, + String parameters, + FutureOr callback(List parameters), { + Object? url, + bool acceptsContent = false, + }) : this.parsed( + name, + ParameterList.parse('@mixin $name($parameters) {', url: url), + (arguments) async { + await callback(arguments); + // We could encode the fact that functions return values and mixins + // don't in the type system, but that would get very messy very + // quickly so it's easier to just return Sass's `null` for mixins and + // simply ignore it at the call site. + return sassNull; + }, + ); /// Creates a callable with a single [parameters] declaration and a single /// [callback]. - AsyncBuiltInCallable.parsed(this.name, this._parameters, this._callback, - {this.acceptsContent = false}); + AsyncBuiltInCallable.parsed( + this.name, + this._parameters, + this._callback, { + this.acceptsContent = false, + }); /// Returns the parameter declaration and Dart callback for the given /// positional and named parameters. @@ -81,12 +94,16 @@ class AsyncBuiltInCallable implements AsyncCallable { /// If no exact match is found, finds the closest approximation. Note that this /// doesn't guarantee that [positional] and [names] are valid for the returned /// [ParameterList]. - (ParameterList, Callback) callbackFor(int positional, Set names) => - (_parameters, _callback); + (ParameterList, Callback) callbackFor(int positional, Set names) => ( + _parameters, + _callback, + ); /// Returns a copy of this callable that emits a deprecation warning. - AsyncBuiltInCallable withDeprecationWarning(String module, - [String? newName]) => + AsyncBuiltInCallable withDeprecationWarning( + String module, [ + String? newName, + ]) => AsyncBuiltInCallable.parsed(name, _parameters, (args) { warnForGlobalBuiltIn(module, newName ?? name); return _callback(args); @@ -97,9 +114,10 @@ class AsyncBuiltInCallable implements AsyncCallable { /// available as function [name] in built-in module [module]. void warnForGlobalBuiltIn(String module, String name) { warnForDeprecation( - 'Global built-in functions are deprecated and will be removed in Dart ' - 'Sass 3.0.0.\n' - 'Use $module.$name instead.\n\n' - 'More info and automated migrator: https://sass-lang.com/d/import', - Deprecation.globalBuiltin); + 'Global built-in functions are deprecated and will be removed in Dart ' + 'Sass 3.0.0.\n' + 'Use $module.$name instead.\n\n' + 'More info and automated migrator: https://sass-lang.com/d/import', + Deprecation.globalBuiltin, + ); } diff --git a/lib/src/callable/built_in.dart b/lib/src/callable/built_in.dart index e78e05014..59b181cdd 100644 --- a/lib/src/callable/built_in.dart +++ b/lib/src/callable/built_in.dart @@ -32,12 +32,15 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// If passed, [url] is the URL of the module in which the function is /// defined. BuiltInCallable.function( - String name, String parameters, Value callback(List arguments), - {Object? url}) - : this.parsed( - name, - ParameterList.parse('@function $name($parameters) {', url: url), - callback); + String name, + String parameters, + Value callback(List arguments), { + Object? url, + }) : this.parsed( + name, + ParameterList.parse('@function $name($parameters) {', url: url), + callback, + ); /// Creates a mixin with a single [parameters] declaration and a single /// [callback]. @@ -48,21 +51,29 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// If passed, [url] is the URL of the module in which the mixin is /// defined. BuiltInCallable.mixin( - String name, String parameters, void callback(List arguments), - {Object? url, bool acceptsContent = false}) - : this.parsed( - name, ParameterList.parse('@mixin $name($parameters) {', url: url), - (arguments) { - callback(arguments); - return sassNull; - }, acceptsContent: acceptsContent); + String name, + String parameters, + void callback(List arguments), { + Object? url, + bool acceptsContent = false, + }) : this.parsed( + name, + ParameterList.parse('@mixin $name($parameters) {', url: url), + (arguments) { + callback(arguments); + return sassNull; + }, + acceptsContent: acceptsContent, + ); /// Creates a callable with a single [parameters] declaration and a single /// [callback]. - BuiltInCallable.parsed(this.name, ParameterList parameters, - Value callback(List arguments), - {this.acceptsContent = false}) - : _overloads = [(parameters, callback)]; + BuiltInCallable.parsed( + this.name, + ParameterList parameters, + Value callback(List arguments), { + this.acceptsContent = false, + }) : _overloads = [(parameters, callback)]; /// Creates a function with multiple implementations. /// @@ -73,14 +84,16 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { /// /// If passed, [url] is the URL of the module in which the function is /// defined. - BuiltInCallable.overloadedFunction(this.name, Map overloads, - {Object? url}) - : _overloads = [ + BuiltInCallable.overloadedFunction( + this.name, + Map overloads, { + Object? url, + }) : _overloads = [ for (var (args, callback) in overloads.pairs) ( ParameterList.parse('@function $name($args) {', url: url), - callback - ) + callback, + ), ], acceptsContent = false; @@ -135,8 +148,8 @@ final class BuiltInCallable implements Callable, AsyncBuiltInCallable { (args) { warnForGlobalBuiltIn(module, newName ?? name); return function(args); - } - ) + }, + ), ], acceptsContent); } diff --git a/lib/src/callable/user_defined.dart b/lib/src/callable/user_defined.dart index 6e0ecfacc..ed21a9648 100644 --- a/lib/src/callable/user_defined.dart +++ b/lib/src/callable/user_defined.dart @@ -23,6 +23,9 @@ final class UserDefinedCallable implements Callable { String get name => declaration.name; - UserDefinedCallable(this.declaration, this.environment, - {required this.inDependency}); + UserDefinedCallable( + this.declaration, + this.environment, { + required this.inDependency, + }); } diff --git a/lib/src/color_names.dart b/lib/src/color_names.dart index ae315663d..4c50aaaa1 100644 --- a/lib/src/color_names.dart +++ b/lib/src/color_names.dart @@ -162,5 +162,5 @@ final colorsByName = { /// A map from Sass colors to (lowercase) color names. final namesByColor = { - for (var (name, color) in colorsByName.pairs) color: name + for (var (name, color) in colorsByName.pairs) color: name, }; diff --git a/lib/src/compile.dart b/lib/src/compile.dart index 35bc75872..bfca2bc4a 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 9dcf6641342288ad16f44b134ad9a555b4e1e992 +// Checksum: ef3310e5d44fa5a9411c9416fb36f51741dfe6ad // // ignore_for_file: unused_import @@ -37,30 +37,33 @@ import 'visitor/serialize.dart'; /// /// If both `importCache` and `nodeImporter` are provided, the importers in /// `importCache` will be evaluated before `nodeImporter`. -CompileResult compile(String path, - {Syntax? syntax, - Logger? logger, - ImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? functions, - OutputStyle? style, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) { +CompileResult compile( + String path, { + Syntax? syntax, + Logger? logger, + ImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? functions, + OutputStyle? style, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) { DeprecationProcessingLogger deprecationLogger = - logger = DeprecationProcessingLogger(logger ?? Logger.stderr(), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - limitRepetition: !verbose) - ..validate(); + logger = DeprecationProcessingLogger( + logger ?? Logger.stderr(), + silenceDeprecations: {...?silenceDeprecations}, + fatalDeprecations: {...?fatalDeprecations}, + futureDeprecations: {...?futureDeprecations}, + limitRepetition: !verbose, + )..validate(); // If the syntax is different than the importer would default to, we have to // parse the file manually and we can't store it in the cache. @@ -69,28 +72,33 @@ CompileResult compile(String path, (syntax == null || syntax == Syntax.forPath(path))) { importCache ??= ImportCache.none(); stylesheet = importCache.importCanonical( - FilesystemImporter.cwd, p.toUri(canonicalize(path)), - originalUrl: p.toUri(path))!; + FilesystemImporter.cwd, + p.toUri(canonicalize(path)), + originalUrl: p.toUri(path), + )!; } else { stylesheet = Stylesheet.parse( - readFile(path), syntax ?? Syntax.forPath(path), - url: p.toUri(path)); + readFile(path), + syntax ?? Syntax.forPath(path), + url: p.toUri(path), + ); } var result = _compileStylesheet( - stylesheet, - logger, - importCache, - nodeImporter, - FilesystemImporter.cwd, - functions, - style, - useSpaces, - indentWidth, - lineFeed, - quietDeps, - sourceMap, - charset); + stylesheet, + logger, + importCache, + nodeImporter, + FilesystemImporter.cwd, + functions, + style, + useSpaces, + indentWidth, + lineFeed, + quietDeps, + sourceMap, + charset, + ); deprecationLogger.summarize(js: nodeImporter != null); return result; @@ -100,51 +108,55 @@ CompileResult compile(String path, /// support the node-sass compatible API. /// /// At most one of `importCache` and `nodeImporter` may be provided at once. -CompileResult compileString(String source, - {Syntax? syntax, - Logger? logger, - ImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? importers, - Iterable? loadPaths, - Importer? importer, - Iterable? functions, - OutputStyle? style, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - Object? url, - bool quietDeps = false, - bool verbose = false, - bool sourceMap = false, - bool charset = true, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations}) { +CompileResult compileString( + String source, { + Syntax? syntax, + Logger? logger, + ImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? importers, + Iterable? loadPaths, + Importer? importer, + Iterable? functions, + OutputStyle? style, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + Object? url, + bool quietDeps = false, + bool verbose = false, + bool sourceMap = false, + bool charset = true, + Iterable? silenceDeprecations, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, +}) { DeprecationProcessingLogger deprecationLogger = - logger = DeprecationProcessingLogger(logger ?? Logger.stderr(), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - limitRepetition: !verbose) - ..validate(); + logger = DeprecationProcessingLogger( + logger ?? Logger.stderr(), + silenceDeprecations: {...?silenceDeprecations}, + fatalDeprecations: {...?fatalDeprecations}, + futureDeprecations: {...?futureDeprecations}, + limitRepetition: !verbose, + )..validate(); var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url); var result = _compileStylesheet( - stylesheet, - logger, - importCache, - nodeImporter, - importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), - functions, - style, - useSpaces, - indentWidth, - lineFeed, - quietDeps, - sourceMap, - charset); + stylesheet, + logger, + importCache, + nodeImporter, + importer ?? (isBrowser ? NoOpImporter() : FilesystemImporter.cwd), + functions, + style, + useSpaces, + indentWidth, + lineFeed, + quietDeps, + sourceMap, + charset, + ); deprecationLogger.summarize(js: nodeImporter != null); return result; @@ -154,53 +166,61 @@ CompileResult compileString(String source, /// /// Arguments are handled as for [compileString]. CompileResult _compileStylesheet( - Stylesheet stylesheet, - Logger? logger, - ImportCache? importCache, - NodeImporter? nodeImporter, - Importer importer, - Iterable? functions, - OutputStyle? style, - bool useSpaces, - int? indentWidth, - LineFeed? lineFeed, - bool quietDeps, - bool sourceMap, - bool charset) { + Stylesheet stylesheet, + Logger? logger, + ImportCache? importCache, + NodeImporter? nodeImporter, + Importer importer, + Iterable? functions, + OutputStyle? style, + bool useSpaces, + int? indentWidth, + LineFeed? lineFeed, + bool quietDeps, + bool sourceMap, + bool charset, +) { if (nodeImporter != null) { logger?.warnForDeprecation( - Deprecation.legacyJsApi, - 'The legacy JS API is deprecated and will be removed in ' - 'Dart Sass 2.0.0.\n\n' - 'More info: https://sass-lang.com/d/legacy-js-api'); + Deprecation.legacyJsApi, + 'The legacy JS API is deprecated and will be removed in ' + 'Dart Sass 2.0.0.\n\n' + 'More info: https://sass-lang.com/d/legacy-js-api', + ); } - var evaluateResult = evaluate(stylesheet, - importCache: importCache, - nodeImporter: nodeImporter, - importer: importer, - functions: functions, - logger: logger, - quietDeps: quietDeps, - sourceMap: sourceMap); - - var serializeResult = serialize(evaluateResult.stylesheet, - style: style, - useSpaces: useSpaces, - indentWidth: indentWidth, - lineFeed: lineFeed, - logger: logger, - sourceMap: sourceMap, - charset: charset); + var evaluateResult = evaluate( + stylesheet, + importCache: importCache, + nodeImporter: nodeImporter, + importer: importer, + functions: functions, + logger: logger, + quietDeps: quietDeps, + sourceMap: sourceMap, + ); + + var serializeResult = serialize( + evaluateResult.stylesheet, + style: style, + useSpaces: useSpaces, + indentWidth: indentWidth, + lineFeed: lineFeed, + logger: logger, + sourceMap: sourceMap, + charset: charset, + ); var resultSourceMap = serializeResult.sourceMap; if (resultSourceMap != null && importCache != null) { mapInPlace( - resultSourceMap.urls, - (url) => url == '' - ? Uri.dataFromString(stylesheet.span.file.getText(0), - encoding: utf8) - .toString() - : importCache.sourceMapUrl(Uri.parse(url)).toString()); + resultSourceMap.urls, + (url) => url == '' + ? Uri.dataFromString( + stylesheet.span.file.getText(0), + encoding: utf8, + ).toString() + : importCache.sourceMapUrl(Uri.parse(url)).toString(), + ); } return CompileResult(evaluateResult, serializeResult); diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 8cf380f4b..fb4138481 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -123,9 +123,11 @@ final class ExplicitConfiguration extends Configuration { /// Creates an [ExplicitConfiguration] with a [values] map, a [nodeWithSpan] /// and if this is a copy a reference to the [_originalConfiguration]. - ExplicitConfiguration._(Map values, - this.nodeWithSpan, Configuration? originalConfiguration) - : super._(values, originalConfiguration); + ExplicitConfiguration._( + Map values, + this.nodeWithSpan, + Configuration? originalConfiguration, + ) : super._(values, originalConfiguration); /// Returns a copy of `this` with the given [values] map. /// diff --git a/lib/src/configured_value.dart b/lib/src/configured_value.dart index faf969cad..612645e8f 100644 --- a/lib/src/configured_value.dart +++ b/lib/src/configured_value.dart @@ -22,7 +22,10 @@ final class ConfiguredValue { /// Creates a variable value that's been configured explicitly with a `with` /// clause. ConfiguredValue.explicit( - this.value, this.configurationSpan, this.assignmentNode); + this.value, + this.configurationSpan, + this.assignmentNode, + ); /// Creates a variable value that's implicitly configured by setting a /// variable prior to an `@import` of a file that contains a `@forward`. diff --git a/lib/src/deprecation.dart b/lib/src/deprecation.dart index 1962ec627..dff00aab5 100644 --- a/lib/src/deprecation.dart +++ b/lib/src/deprecation.dart @@ -18,109 +18,153 @@ enum Deprecation { // Checksum: 47c97f7824eb25d7f1e64e3230938b88330d40b4 /// Deprecation for passing a string directly to meta.call(). - callString('call-string', - deprecatedIn: '0.0.0', - description: 'Passing a string directly to meta.call().'), + callString( + 'call-string', + deprecatedIn: '0.0.0', + description: 'Passing a string directly to meta.call().', + ), /// Deprecation for @elseif. elseif('elseif', deprecatedIn: '1.3.2', description: '@elseif.'), /// Deprecation for @-moz-document. - mozDocument('moz-document', - deprecatedIn: '1.7.2', description: '@-moz-document.'), + mozDocument( + 'moz-document', + deprecatedIn: '1.7.2', + description: '@-moz-document.', + ), /// Deprecation for imports using relative canonical URLs. - relativeCanonical('relative-canonical', - deprecatedIn: '1.14.2', - description: 'Imports using relative canonical URLs.'), + relativeCanonical( + 'relative-canonical', + deprecatedIn: '1.14.2', + description: 'Imports using relative canonical URLs.', + ), /// Deprecation for declaring new variables with !global. - newGlobal('new-global', - deprecatedIn: '1.17.2', - description: 'Declaring new variables with !global.'), + newGlobal( + 'new-global', + deprecatedIn: '1.17.2', + description: 'Declaring new variables with !global.', + ), /// Deprecation for using color module functions in place of plain CSS functions. - colorModuleCompat('color-module-compat', - deprecatedIn: '1.23.0', - description: - 'Using color module functions in place of plain CSS functions.'), + colorModuleCompat( + 'color-module-compat', + deprecatedIn: '1.23.0', + description: + 'Using color module functions in place of plain CSS functions.', + ), /// Deprecation for / operator for division. - slashDiv('slash-div', - deprecatedIn: '1.33.0', description: '/ operator for division.'), + slashDiv( + 'slash-div', + deprecatedIn: '1.33.0', + description: '/ operator for division.', + ), /// Deprecation for leading, trailing, and repeated combinators. - bogusCombinators('bogus-combinators', - deprecatedIn: '1.54.0', - description: 'Leading, trailing, and repeated combinators.'), + bogusCombinators( + 'bogus-combinators', + deprecatedIn: '1.54.0', + description: 'Leading, trailing, and repeated combinators.', + ), /// Deprecation for ambiguous + and - operators. - strictUnary('strict-unary', - deprecatedIn: '1.55.0', description: 'Ambiguous + and - operators.'), + strictUnary( + 'strict-unary', + deprecatedIn: '1.55.0', + description: 'Ambiguous + and - operators.', + ), /// Deprecation for passing invalid units to built-in functions. - functionUnits('function-units', - deprecatedIn: '1.56.0', - description: 'Passing invalid units to built-in functions.'), + functionUnits( + 'function-units', + deprecatedIn: '1.56.0', + description: 'Passing invalid units to built-in functions.', + ), /// Deprecation for using !default or !global multiple times for one variable. - duplicateVarFlags('duplicate-var-flags', - deprecatedIn: '1.62.0', - description: - 'Using !default or !global multiple times for one variable.'), + duplicateVarFlags( + 'duplicate-var-flags', + deprecatedIn: '1.62.0', + description: 'Using !default or !global multiple times for one variable.', + ), /// Deprecation for passing null as alpha in the ${isJS ? 'JS': 'Dart'} API. - nullAlpha('null-alpha', - deprecatedIn: '1.62.3', - description: 'Passing null as alpha in the ${isJS ? 'JS' : 'Dart'} API.'), + nullAlpha( + 'null-alpha', + deprecatedIn: '1.62.3', + description: 'Passing null as alpha in the ${isJS ? 'JS' : 'Dart'} API.', + ), /// Deprecation for passing percentages to the Sass abs() function. - absPercent('abs-percent', - deprecatedIn: '1.65.0', - description: 'Passing percentages to the Sass abs() function.'), + absPercent( + 'abs-percent', + deprecatedIn: '1.65.0', + description: 'Passing percentages to the Sass abs() function.', + ), /// Deprecation for using the current working directory as an implicit load path. - fsImporterCwd('fs-importer-cwd', - deprecatedIn: '1.73.0', - description: - 'Using the current working directory as an implicit load path.'), + fsImporterCwd( + 'fs-importer-cwd', + deprecatedIn: '1.73.0', + description: + 'Using the current working directory as an implicit load path.', + ), /// Deprecation for function and mixin names beginning with --. - cssFunctionMixin('css-function-mixin', - deprecatedIn: '1.76.0', - description: 'Function and mixin names beginning with --.'), + cssFunctionMixin( + 'css-function-mixin', + deprecatedIn: '1.76.0', + description: 'Function and mixin names beginning with --.', + ), /// Deprecation for declarations after or between nested rules. - mixedDecls('mixed-decls', - deprecatedIn: '1.77.7', - description: 'Declarations after or between nested rules.'), + mixedDecls( + 'mixed-decls', + deprecatedIn: '1.77.7', + description: 'Declarations after or between nested rules.', + ), /// Deprecation for meta.feature-exists - featureExists('feature-exists', - deprecatedIn: '1.78.0', description: 'meta.feature-exists'), + featureExists( + 'feature-exists', + deprecatedIn: '1.78.0', + description: 'meta.feature-exists', + ), /// Deprecation for certain uses of built-in sass:color functions. - color4Api('color-4-api', - deprecatedIn: '1.79.0', - description: 'Certain uses of built-in sass:color functions.'), + color4Api( + 'color-4-api', + deprecatedIn: '1.79.0', + description: 'Certain uses of built-in sass:color functions.', + ), /// Deprecation for using global color functions instead of sass:color. - colorFunctions('color-functions', - deprecatedIn: '1.79.0', - description: 'Using global color functions instead of sass:color.'), + colorFunctions( + 'color-functions', + deprecatedIn: '1.79.0', + description: 'Using global color functions instead of sass:color.', + ), /// Deprecation for legacy JS API. - legacyJsApi('legacy-js-api', - deprecatedIn: '1.79.0', description: 'Legacy JS API.'), + legacyJsApi( + 'legacy-js-api', + deprecatedIn: '1.79.0', + description: 'Legacy JS API.', + ), /// Deprecation for @import rules. import('import', deprecatedIn: '1.80.0', description: '@import rules.'), /// Deprecation for global built-in functions that are available in sass: modules. - globalBuiltin('global-builtin', - deprecatedIn: '1.80.0', - description: - 'Global built-in functions that are available in sass: modules.'), + globalBuiltin( + 'global-builtin', + deprecatedIn: '1.80.0', + description: + 'Global built-in functions that are available in sass: modules.', + ), // END AUTOGENERATED CODE @@ -181,7 +225,7 @@ enum Deprecation { isFuture = false; /// Constructs a future deprecation. - // ignore: unused_element + // ignore: unused_element, unused_element_parameter const Deprecation.future(this.id, {this.description}) : _deprecatedIn = null, _obsoleteIn = null, diff --git a/lib/src/embedded/compilation_dispatcher.dart b/lib/src/embedded/compilation_dispatcher.dart index 83456445e..190583890 100644 --- a/lib/src/embedded/compilation_dispatcher.dart +++ b/lib/src/embedded/compilation_dispatcher.dart @@ -85,15 +85,17 @@ final class CompilationDispatcher { InboundMessage_Message.fileImportResponse || InboundMessage_Message.functionCallResponse: throw paramsError( - "Response ID ${message.id} doesn't match any outstanding requests" - " in compilation $_compilationId."); + "Response ID ${message.id} doesn't match any outstanding requests" + " in compilation $_compilationId.", + ); case InboundMessage_Message.notSet: throw parseError("InboundMessage.message is not set."); default: // ignore: unreachable_switch_default throw parseError( - "Unknown message type: ${message.toDebugString()}"); + "Unknown message type: ${message.toDebugString()}", + ); } } catch (error, stackTrace) { _handleError(error, stackTrace); @@ -102,7 +104,8 @@ final class CompilationDispatcher { } OutboundMessage_CompileResponse _compile( - InboundMessage_CompileRequest request) { + InboundMessage_CompileRequest request, + ) { var functions = OpaqueRegistry(); var mixins = OpaqueRegistry(); @@ -111,12 +114,16 @@ final class CompilationDispatcher { : sass.OutputStyle.expanded; var logger = request.silent ? Logger.quiet - : EmbeddedLogger(this, - color: request.alertColor, ascii: request.alertAscii); + : EmbeddedLogger( + this, + color: request.alertColor, + ascii: request.alertAscii, + ); Iterable? parseDeprecationsOrWarn( - Iterable deprecations, - {bool supportVersions = false}) { + Iterable deprecations, { + bool supportVersions = false, + }) { return () sync* { for (var item in deprecations) { var deprecation = sass.Deprecation.fromId(item); @@ -137,61 +144,71 @@ final class CompilationDispatcher { }(); } - var fatalDeprecations = parseDeprecationsOrWarn(request.fatalDeprecation, - supportVersions: true); - var silenceDeprecations = - parseDeprecationsOrWarn(request.silenceDeprecation); + var fatalDeprecations = parseDeprecationsOrWarn( + request.fatalDeprecation, + supportVersions: true, + ); + var silenceDeprecations = parseDeprecationsOrWarn( + request.silenceDeprecation, + ); var futureDeprecations = parseDeprecationsOrWarn(request.futureDeprecation); try { - var importers = request.importers.map((importer) => - _decodeImporter(importer) ?? - (throw mandatoryError("Importer.importer"))); + var importers = request.importers.map( + (importer) => + _decodeImporter(importer) ?? + (throw mandatoryError("Importer.importer")), + ); - var globalFunctions = request.globalFunctions - .map((signature) => hostCallable(this, functions, mixins, signature)); + var globalFunctions = request.globalFunctions.map( + (signature) => hostCallable(this, functions, mixins, signature), + ); late sass.CompileResult result; switch (request.whichInput()) { case InboundMessage_CompileRequest_Input.string: var input = request.string; - result = sass.compileStringToResult(input.source, + result = sass.compileStringToResult( + input.source, + color: request.alertColor, + logger: logger, + importers: importers, + importer: _decodeImporter(input.importer) ?? + (input.url.startsWith("file:") ? null : sass.Importer.noOp), + functions: globalFunctions, + syntax: syntaxToSyntax(input.syntax), + style: style, + url: input.url.isEmpty ? null : input.url, + quietDeps: request.quietDeps, + verbose: request.verbose, + fatalDeprecations: fatalDeprecations, + silenceDeprecations: silenceDeprecations, + futureDeprecations: futureDeprecations, + sourceMap: request.sourceMap, + charset: request.charset, + ); + + case InboundMessage_CompileRequest_Input.path: + if (request.path.isEmpty) { + throw mandatoryError("CompileRequest.Input.path"); + } + + try { + result = sass.compileToResult( + request.path, color: request.alertColor, logger: logger, importers: importers, - importer: _decodeImporter(input.importer) ?? - (input.url.startsWith("file:") ? null : sass.Importer.noOp), functions: globalFunctions, - syntax: syntaxToSyntax(input.syntax), style: style, - url: input.url.isEmpty ? null : input.url, quietDeps: request.quietDeps, verbose: request.verbose, fatalDeprecations: fatalDeprecations, silenceDeprecations: silenceDeprecations, futureDeprecations: futureDeprecations, sourceMap: request.sourceMap, - charset: request.charset); - - case InboundMessage_CompileRequest_Input.path: - if (request.path.isEmpty) { - throw mandatoryError("CompileRequest.Input.path"); - } - - try { - result = sass.compileToResult(request.path, - color: request.alertColor, - logger: logger, - importers: importers, - functions: globalFunctions, - style: style, - quietDeps: request.quietDeps, - verbose: request.verbose, - fatalDeprecations: fatalDeprecations, - silenceDeprecations: silenceDeprecations, - futureDeprecations: futureDeprecations, - sourceMap: request.sourceMap, - charset: request.charset); + charset: request.charset, + ); } on FileSystemException catch (error) { return OutboundMessage_CompileResponse() ..failure = (OutboundMessage_CompileResponse_CompileFailure() @@ -213,16 +230,20 @@ final class CompilationDispatcher { var sourceMap = result.sourceMap; if (sourceMap != null) { - success.sourceMap = json.encode(sourceMap.toJson( - includeSourceContents: request.sourceMapIncludeSources)); + success.sourceMap = json.encode( + sourceMap.toJson( + includeSourceContents: request.sourceMapIncludeSources, + ), + ); } return OutboundMessage_CompileResponse() ..success = success ..loadedUrls.addAll(result.loadedUrls.map((url) => url.toString())); } on sass.SassException catch (error) { var formatted = withGlyphs( - () => error.toString(color: request.alertColor), - ascii: request.alertAscii); + () => error.toString(color: request.alertColor), + ascii: request.alertAscii, + ); return OutboundMessage_CompileResponse() ..failure = (OutboundMessage_CompileResponse_CompileFailure() ..message = error.message @@ -235,7 +256,8 @@ final class CompilationDispatcher { /// Converts [importer] into a [sass.Importer]. sass.Importer? _decodeImporter( - InboundMessage_CompileRequest_Importer importer) { + InboundMessage_CompileRequest_Importer importer, + ) { switch (importer.whichImporter()) { case InboundMessage_CompileRequest_Importer_Importer.path: _checkNoNonCanonicalScheme(importer); @@ -243,7 +265,10 @@ final class CompilationDispatcher { case InboundMessage_CompileRequest_Importer_Importer.importerId: return HostImporter( - this, importer.importerId, importer.nonCanonicalScheme); + this, + importer.importerId, + importer.nonCanonicalScheme, + ); case InboundMessage_CompileRequest_Importer_Importer.fileImporterId: _checkNoNonCanonicalScheme(importer); @@ -251,7 +276,8 @@ final class CompilationDispatcher { case InboundMessage_CompileRequest_Importer_Importer.nodePackageImporter: return npi.NodePackageImporter( - importer.nodePackageImporter.entryPointDirectory); + importer.nodePackageImporter.entryPointDirectory, + ); case InboundMessage_CompileRequest_Importer_Importer.notSet: _checkNoNonCanonicalScheme(importer); @@ -262,10 +288,13 @@ final class CompilationDispatcher { /// Throws a [ProtocolError] if [importer] contains one or more /// `nonCanonicalScheme`s. void _checkNoNonCanonicalScheme( - InboundMessage_CompileRequest_Importer importer) { + InboundMessage_CompileRequest_Importer importer, + ) { if (importer.nonCanonicalScheme.isEmpty) return; - throw paramsError("Importer.non_canonical_scheme may only be set along " - "with Importer.importer.importer_id"); + throw paramsError( + "Importer.non_canonical_scheme may only be set along " + "with Importer.importer.importer_id", + ); } /// Sends [event] to the host. @@ -280,24 +309,32 @@ final class CompilationDispatcher { } InboundMessage_CanonicalizeResponse sendCanonicalizeRequest( - OutboundMessage_CanonicalizeRequest request) => + OutboundMessage_CanonicalizeRequest request, + ) => _sendRequest( - OutboundMessage()..canonicalizeRequest = request); + OutboundMessage()..canonicalizeRequest = request, + ); InboundMessage_ImportResponse sendImportRequest( - OutboundMessage_ImportRequest request) => + OutboundMessage_ImportRequest request, + ) => _sendRequest( - OutboundMessage()..importRequest = request); + OutboundMessage()..importRequest = request, + ); InboundMessage_FileImportResponse sendFileImportRequest( - OutboundMessage_FileImportRequest request) => + OutboundMessage_FileImportRequest request, + ) => _sendRequest( - OutboundMessage()..fileImportRequest = request); + OutboundMessage()..fileImportRequest = request, + ); InboundMessage_FunctionCallResponse sendFunctionCallRequest( - OutboundMessage_FunctionCallRequest request) => + OutboundMessage_FunctionCallRequest request, + ) => _sendRequest( - OutboundMessage()..functionCallRequest = request); + OutboundMessage()..functionCallRequest = request, + ); /// Sends [request] to the host and returns the message sent in response. T _sendRequest(OutboundMessage message) { @@ -305,8 +342,10 @@ final class CompilationDispatcher { _send(message); try { - var messageBuffer = - Uint8List.sublistView(_receive(), _compilationIdVarint.length); + var messageBuffer = Uint8List.sublistView( + _receive(), + _compilationIdVarint.length, + ); InboundMessage message; try { @@ -324,21 +363,24 @@ final class CompilationDispatcher { message.functionCallResponse, InboundMessage_Message.compileRequest => throw paramsError( "A CompileRequest with compilation ID $_compilationId is already " - "active."), + "active.", + ), InboundMessage_Message.versionRequest => throw paramsError("VersionRequest must have compilation ID 0."), InboundMessage_Message.notSet => - throw parseError("InboundMessage.message is not set.") + throw parseError("InboundMessage.message is not set."), }; if (message.id != _outboundRequestId) { throw paramsError( - "Response ID ${message.id} doesn't match any outstanding requests " - "in compilation $_compilationId."); + "Response ID ${message.id} doesn't match any outstanding requests " + "in compilation $_compilationId.", + ); } else if (response is! T) { throw paramsError( - "Request ID $_outboundRequestId doesn't match response type " - "${response.runtimeType} in compilation $_compilationId."); + "Request ID $_outboundRequestId doesn't match response type " + "${response.runtimeType} in compilation $_compilationId.", + ); } return response; @@ -370,11 +412,12 @@ final class CompilationDispatcher { // [IsolateDispatcher] knows whether to treat this isolate as inactive or // close out entirely. var packet = Uint8List( - 1 + _compilationIdVarint.length + protobufWriter.lengthInBytes); + 1 + _compilationIdVarint.length + protobufWriter.lengthInBytes, + ); packet[0] = switch (message.whichMessage()) { OutboundMessage_Message.compileResponse => 1, OutboundMessage_Message.error => 2, - _ => 0 + _ => 0, }; packet.setAll(1, _compilationIdVarint); protobufWriter.writeTo(packet, 1 + _compilationIdVarint.length); diff --git a/lib/src/embedded/executable.dart b/lib/src/embedded/executable.dart index 9248713ae..d6eb24636 100644 --- a/lib/src/embedded/executable.dart +++ b/lib/src/embedded/executable.dart @@ -16,22 +16,27 @@ void main(List args) { var response = IsolateDispatcher.versionResponse(); response.id = 0; stdout.writeln( - JsonEncoder.withIndent(" ").convert(response.toProto3Json())); + JsonEncoder.withIndent(" ").convert(response.toProto3Json()), + ); return; case [_, ...]: stderr.writeln( - "sass --embedded is not intended to be executed with additional " - "arguments.\n" - "See https://github.com/sass/dart-sass#embedded-dart-sass for " - "details."); + "sass --embedded is not intended to be executed with additional " + "arguments.\n" + "See https://github.com/sass/dart-sass#embedded-dart-sass for " + "details.", + ); // USAGE error from https://bit.ly/2poTt90 exitCode = 64; return; } IsolateDispatcher( - StreamChannel.withGuarantees(stdin, stdout, allowSinkErrors: false) - .transform(lengthDelimited)) - .listen(); + StreamChannel.withGuarantees( + stdin, + stdout, + allowSinkErrors: false, + ).transform(lengthDelimited), + ).listen(); } diff --git a/lib/src/embedded/host_callable.dart b/lib/src/embedded/host_callable.dart index 95e15221a..0fe465c12 100644 --- a/lib/src/embedded/host_callable.dart +++ b/lib/src/embedded/host_callable.dart @@ -21,17 +21,19 @@ import 'utils.dart'; /// /// Throws a [SassException] if [signature] is invalid. Callable hostCallable( - CompilationDispatcher dispatcher, - OpaqueRegistry functions, - OpaqueRegistry mixins, - String signature, - {int? id}) { + CompilationDispatcher dispatcher, + OpaqueRegistry functions, + OpaqueRegistry mixins, + String signature, { + int? id, +}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { var protofier = Protofier(dispatcher, functions, mixins); var request = OutboundMessage_FunctionCallRequest() - ..arguments.addAll( - [for (var argument in arguments) protofier.protofy(argument)]); + ..arguments.addAll([ + for (var argument in arguments) protofier.protofy(argument), + ]); if (id != null) { request.functionId = id; diff --git a/lib/src/embedded/importer/host.dart b/lib/src/embedded/importer/host.dart index e5342dc31..b9eb690d9 100644 --- a/lib/src/embedded/importer/host.dart +++ b/lib/src/embedded/importer/host.dart @@ -20,13 +20,16 @@ final class HostImporter extends ImporterBase { final Set _nonCanonicalSchemes; HostImporter( - super.dispatcher, this._importerId, Iterable nonCanonicalSchemes) - : _nonCanonicalSchemes = Set.unmodifiable(nonCanonicalSchemes) { + super.dispatcher, + this._importerId, + Iterable nonCanonicalSchemes, + ) : _nonCanonicalSchemes = Set.unmodifiable(nonCanonicalSchemes) { for (var scheme in _nonCanonicalSchemes) { if (isValidUrlScheme(scheme)) continue; throw SassException( - '"$scheme" isn\'t a valid URL scheme (for example "file").', - bogusSpan); + '"$scheme" isn\'t a valid URL scheme (for example "file").', + bogusSpan, + ); } } @@ -43,27 +46,35 @@ final class HostImporter extends ImporterBase { if (!response.containingUrlUnused) canonicalizeContext.containingUrl; return switch (response.whichResult()) { - InboundMessage_CanonicalizeResponse_Result.url => - parseAbsoluteUrl("The importer", response.url), + InboundMessage_CanonicalizeResponse_Result.url => parseAbsoluteUrl( + "The importer", + response.url, + ), InboundMessage_CanonicalizeResponse_Result.error => throw response.error, - InboundMessage_CanonicalizeResponse_Result.notSet => null + InboundMessage_CanonicalizeResponse_Result.notSet => null, }; } ImporterResult? load(Uri url) { - var response = dispatcher.sendImportRequest(OutboundMessage_ImportRequest() - ..importerId = _importerId - ..url = url.toString()); + var response = dispatcher.sendImportRequest( + OutboundMessage_ImportRequest() + ..importerId = _importerId + ..url = url.toString(), + ); return switch (response.whichResult()) { InboundMessage_ImportResponse_Result.success => ImporterResult( response.success.contents, sourceMapUrl: response.success.sourceMapUrl.isEmpty ? null - : parseAbsoluteUrl("The importer", response.success.sourceMapUrl), - syntax: syntaxToSyntax(response.success.syntax)), + : parseAbsoluteUrl( + "The importer", + response.success.sourceMapUrl, + ), + syntax: syntaxToSyntax(response.success.syntax), + ), InboundMessage_ImportResponse_Result.error => throw response.error, - InboundMessage_ImportResponse_Result.notSet => null + InboundMessage_ImportResponse_Result.notSet => null, }; } diff --git a/lib/src/embedded/isolate_dispatcher.dart b/lib/src/embedded/isolate_dispatcher.dart index fe79f034c..1ae8015f5 100644 --- a/lib/src/embedded/isolate_dispatcher.dart +++ b/lib/src/embedded/isolate_dispatcher.dart @@ -50,55 +50,67 @@ class IsolateDispatcher { IsolateDispatcher(this._channel); void listen() { - _channel.stream.listen((packet) async { - int? compilationId; - InboundMessage? message; - try { - Uint8List messageBuffer; - (compilationId, messageBuffer) = parsePacket(packet); - - if (compilationId != 0) { - var isolate = await _activeIsolates.putIfAbsent( - compilationId, () => _getIsolate(compilationId!)); - - // The shutdown may have started by the time the isolate is spawned - if (_closed) return; + _channel.stream.listen( + (packet) async { + int? compilationId; + InboundMessage? message; + try { + Uint8List messageBuffer; + (compilationId, messageBuffer) = parsePacket(packet); + + if (compilationId != 0) { + var isolate = await _activeIsolates.putIfAbsent( + compilationId, + () => _getIsolate(compilationId!), + ); + + // The shutdown may have started by the time the isolate is spawned + if (_closed) return; + + try { + isolate.send(packet); + return; + } on StateError catch (_) { + throw paramsError( + "Received multiple messages for compilation ID $compilationId", + ); + } + } try { - isolate.send(packet); - return; - } on StateError catch (_) { - throw paramsError( - "Received multiple messages for compilation ID $compilationId"); + message = InboundMessage.fromBuffer(messageBuffer); + } on InvalidProtocolBufferException catch (error) { + throw parseError(error.message); } - } - try { - message = InboundMessage.fromBuffer(messageBuffer); - } on InvalidProtocolBufferException catch (error) { - throw parseError(error.message); - } + if (message.whichMessage() case var type + when type != InboundMessage_Message.versionRequest) { + throw paramsError( + "Only VersionRequest may have wire ID 0, was $type.", + ); + } - if (message.whichMessage() case var type - when type != InboundMessage_Message.versionRequest) { - throw paramsError( - "Only VersionRequest may have wire ID 0, was $type."); + var request = message.versionRequest; + var response = versionResponse(); + response.id = request.id; + _send(0, OutboundMessage()..versionResponse = response); + } catch (error, stackTrace) { + _handleError( + error, + stackTrace, + compilationId: compilationId, + messageId: message?.id, + ); } - - var request = message.versionRequest; - var response = versionResponse(); - response.id = request.id; - _send(0, OutboundMessage()..versionResponse = response); - } catch (error, stackTrace) { - _handleError(error, stackTrace, - compilationId: compilationId, messageId: message?.id); - } - }, onError: (Object error, StackTrace stackTrace) { - _handleError(error, stackTrace); - }, onDone: () { - _closed = true; - _allIsolates.stream.listen((isolate) => isolate.kill()); - }); + }, + onError: (Object error, StackTrace stackTrace) { + _handleError(error, stackTrace); + }, + onDone: () { + _closed = true; + _allIsolates.stream.listen((isolate) => isolate.kill()); + }, + ); } /// Returns an isolate that's ready to run a new compilation. @@ -112,10 +124,12 @@ class IsolateDispatcher { isolate = _inactiveIsolates.first; _inactiveIsolates.remove(isolate); } else { - var future = ReusableIsolate.spawn(_isolateMain, - onError: (Object error, StackTrace stackTrace) { - _handleError(error, stackTrace); - }); + var future = ReusableIsolate.spawn( + _isolateMain, + onError: (Object error, StackTrace stackTrace) { + _handleError(error, stackTrace); + }, + ); isolate = await future; _allIsolates.add(isolate); } @@ -164,10 +178,16 @@ class IsolateDispatcher { /// /// The [compilationId] and [messageId] indicate the IDs of the message being /// responded to, if available. - void _handleError(Object error, StackTrace stackTrace, - {int? compilationId, int? messageId}) { - sendError(compilationId ?? errorId, - handleError(error, stackTrace, messageId: messageId)); + void _handleError( + Object error, + StackTrace stackTrace, { + int? compilationId, + int? messageId, + }) { + sendError( + compilationId ?? errorId, + handleError(error, stackTrace, messageId: messageId), + ); _channel.sink.close(); } diff --git a/lib/src/embedded/logger.dart b/lib/src/embedded/logger.dart index e831af6c7..c99b0823e 100644 --- a/lib/src/embedded/logger.dart +++ b/lib/src/embedded/logger.dart @@ -30,18 +30,24 @@ final class EmbeddedLogger extends LoggerWithDeprecationType { _ascii = ascii; void debug(String message, SourceSpan span) { - _dispatcher.sendLog(OutboundMessage_LogEvent() - ..type = LogEventType.DEBUG - ..message = message - ..span = protofySpan(span) - ..formatted = (span.start.sourceUrl.andThen(p.prettyUri) ?? '-') + - ':${span.start.line + 1} ' + - (_color ? '\u001b[1mDebug\u001b[0m' : 'DEBUG') + - ': $message\n'); + _dispatcher.sendLog( + OutboundMessage_LogEvent() + ..type = LogEventType.DEBUG + ..message = message + ..span = protofySpan(span) + ..formatted = (span.start.sourceUrl.andThen(p.prettyUri) ?? '-') + + ':${span.start.line + 1} ' + + (_color ? '\u001b[1mDebug\u001b[0m' : 'DEBUG') + + ': $message\n', + ); } - void internalWarn(String message, - {FileSpan? span, Trace? trace, Deprecation? deprecation}) { + void internalWarn( + String message, { + FileSpan? span, + Trace? trace, + Deprecation? deprecation, + }) { var formatted = withGlyphs(() { var buffer = StringBuffer(); var showDeprecation = diff --git a/lib/src/embedded/protofier.dart b/lib/src/embedded/protofier.dart index 58a496be3..7c7d60fc4 100644 --- a/lib/src/embedded/protofier.dart +++ b/lib/src/embedded/protofier.dart @@ -55,11 +55,12 @@ final class Protofier { result.number = _protofyNumber(value); case SassColor(): result.color = Value_Color( - space: value.space.name, - channel1: value.channel0OrNull, - channel2: value.channel1OrNull, - channel3: value.channel2OrNull, - alpha: value.alphaOrNull); + space: value.space.name, + channel1: value.channel0OrNull, + channel2: value.channel1OrNull, + channel3: value.channel2OrNull, + alpha: value.alphaOrNull, + ); case SassArgumentList(): _argumentLists.add(value); result.argumentList = Value_ArgumentList() @@ -67,7 +68,7 @@ final class Protofier { ..separator = _protofySeparator(value.separator) ..keywords.addAll({ for (var (key, value) in value.keywordsWithoutMarking.pairs) - key: protofy(value) + key: protofy(value), }) ..contents.addAll(value.asList.map(protofy)); case SassList(): @@ -78,15 +79,18 @@ final class Protofier { case SassMap(): result.map = Value_Map(); for (var (key, value) in value.contents.pairs) { - result.map.entries.add(Value_Map_Entry() - ..key = protofy(key) - ..value = protofy(value)); + result.map.entries.add( + Value_Map_Entry() + ..key = protofy(key) + ..value = protofy(value), + ); } case SassCalculation(): result.calculation = _protofyCalculation(value); case SassFunction(): - result.compilerFunction = - Value_CompilerFunction(id: _functions.getId(value)); + result.compilerFunction = Value_CompilerFunction( + id: _functions.getId(value), + ); case SassMixin(): result.compilerMixin = Value_CompilerMixin(id: _mixins.getId(value)); case sassTrue: @@ -113,7 +117,7 @@ final class Protofier { ListSeparator.comma => proto.ListSeparator.COMMA, ListSeparator.space => proto.ListSeparator.SPACE, ListSeparator.slash => proto.ListSeparator.SLASH, - ListSeparator.undecided => proto.ListSeparator.UNDECIDED + ListSeparator.undecided => proto.ListSeparator.UNDECIDED, }; /// Converts [calculation] to its protocol buffer representation. @@ -146,12 +150,13 @@ final class Protofier { /// Converts [operator] to its protocol buffer representation. proto.CalculationOperator _protofyCalculationOperator( - CalculationOperator operator) => + CalculationOperator operator, + ) => switch (operator) { CalculationOperator.plus => proto.CalculationOperator.PLUS, CalculationOperator.minus => proto.CalculationOperator.MINUS, CalculationOperator.times => proto.CalculationOperator.TIMES, - CalculationOperator.dividedBy => proto.CalculationOperator.DIVIDE + CalculationOperator.dividedBy => proto.CalculationOperator.DIVIDE, }; /// Converts [response]'s return value to its Sass representation. @@ -236,15 +241,16 @@ final class Protofier { var length = value.argumentList.contents.length; if (separator == ListSeparator.undecided && length > 1) { throw paramsError( - "List $value can't have an undecided separator because it has " - "$length elements"); + "List $value can't have an undecided separator because it has " + "$length elements", + ); } return SassArgumentList( value.argumentList.contents.map(_deprotofy), { for (var (name, value) in value.argumentList.keywords.pairs) - name: _deprotofy(value) + name: _deprotofy(value), }, separator); @@ -252,43 +258,57 @@ final class Protofier { var separator = _deprotofySeparator(value.list.separator); if (value.list.contents.isEmpty) { return SassList.empty( - separator: separator, brackets: value.list.hasBrackets); + separator: separator, + brackets: value.list.hasBrackets, + ); } var length = value.list.contents.length; if (separator == ListSeparator.undecided && length > 1) { throw paramsError( - "List $value can't have an undecided separator because it has " - "$length elements"); + "List $value can't have an undecided separator because it has " + "$length elements", + ); } - return SassList(value.list.contents.map(_deprotofy), separator, - brackets: value.list.hasBrackets); + return SassList( + value.list.contents.map(_deprotofy), + separator, + brackets: value.list.hasBrackets, + ); case Value_Value.map: return value.map.entries.isEmpty ? const SassMap.empty() : SassMap({ for (var Value_Map_Entry(:key, :value) in value.map.entries) - _deprotofy(key): _deprotofy(value) + _deprotofy(key): _deprotofy(value), }); case Value_Value.compilerFunction: var id = value.compilerFunction.id; if (_functions[id] case var function?) return function; throw paramsError( - "CompilerFunction.id $id doesn't match any known functions"); + "CompilerFunction.id $id doesn't match any known functions", + ); case Value_Value.hostFunction: - return SassFunction(hostCallable( - _dispatcher, _functions, _mixins, value.hostFunction.signature, - id: value.hostFunction.id)); + return SassFunction( + hostCallable( + _dispatcher, + _functions, + _mixins, + value.hostFunction.signature, + id: value.hostFunction.id, + ), + ); case Value_Value.compilerMixin: var id = value.compilerMixin.id; if (_mixins[id] case var mixin?) return mixin; throw paramsError( - "CompilerMixin.id $id doesn't match any known mixins"); + "CompilerMixin.id $id doesn't match any known mixins", + ); case Value_Value.calculation: return _deprotofyCalculation(value.calculation); @@ -298,7 +318,7 @@ final class Protofier { SingletonValue.TRUE => sassTrue, SingletonValue.FALSE => sassFalse, SingletonValue.NULL => sassNull, - _ => throw "Unknown Value.singleton ${value.singleton}" + _ => throw "Unknown Value.singleton ${value.singleton}", }; case Value_Value.notSet: @@ -315,26 +335,30 @@ final class Protofier { } throw paramsError( - '$name must be between ${error.start} and ${error.end}, was ' - '${error.invalidValue}'); + '$name must be between ${error.start} and ${error.end}, was ' + '${error.invalidValue}', + ); } } /// Converts [number] to its Sass representation. - SassNumber _deprotofyNumber(Value_Number number) => - SassNumber.withUnits(number.value, - numeratorUnits: number.numerators, - denominatorUnits: number.denominators); + SassNumber _deprotofyNumber(Value_Number number) => SassNumber.withUnits( + number.value, + numeratorUnits: number.numerators, + denominatorUnits: number.denominators, + ); /// Returns the argument list in [_argumentLists] that corresponds to [id]. SassArgumentList _argumentListForId(int id) { if (id < 1) { throw paramsError( - "Value.ArgumentList.id $id can't be marked as accessed"); + "Value.ArgumentList.id $id can't be marked as accessed", + ); } else if (id > _argumentLists.length) { throw paramsError( - "Value.ArgumentList.id $id doesn't match any known argument " - "lists"); + "Value.ArgumentList.id $id doesn't match any known argument " + "lists", + ); } else { return _argumentLists[id - 1]; } @@ -357,59 +381,73 @@ final class Protofier { SassCalculation.calc(_deprotofyCalculationValue(arg)), Value_Calculation(name: "calc") => throw paramsError( "Value.Calculation.arguments must have exactly one argument for " - "calc()."), + "calc().", + ), Value_Calculation( name: "clamp", - arguments: [var arg1, ...var rest] && List(length: < 4) + arguments: [var arg1, ...var rest] && List(length: < 4), ) => SassCalculation.clamp( - _deprotofyCalculationValue(arg1), - rest.elementAtOrNull(0).andThen(_deprotofyCalculationValue), - rest.elementAtOrNull(1).andThen(_deprotofyCalculationValue)), + _deprotofyCalculationValue(arg1), + rest.elementAtOrNull(0).andThen(_deprotofyCalculationValue), + rest.elementAtOrNull(1).andThen(_deprotofyCalculationValue), + ), Value_Calculation(name: "clamp") => throw paramsError( "Value.Calculation.arguments must have 1 to 3 arguments for " - "clamp()."), + "clamp().", + ), Value_Calculation(name: "min" || "max", arguments: []) => throw paramsError( - "Value.Calculation.arguments must have at least 1 argument for " - "${calculation.name}()."), - Value_Calculation(name: "min", :var arguments) => - SassCalculation.min(arguments.map(_deprotofyCalculationValue)), - Value_Calculation(name: "max", :var arguments) => - SassCalculation.max(arguments.map(_deprotofyCalculationValue)), + "Value.Calculation.arguments must have at least 1 argument for " + "${calculation.name}().", + ), + Value_Calculation(name: "min", :var arguments) => SassCalculation.min( + arguments.map(_deprotofyCalculationValue), + ), + Value_Calculation(name: "max", :var arguments) => SassCalculation.max( + arguments.map(_deprotofyCalculationValue), + ), _ => throw paramsError( 'Value.Calculation.name "${calculation.name}" is not a recognized ' - 'calculation type.') + 'calculation type.', + ), }; /// Converts [value] to its Sass representation. Object _deprotofyCalculationValue(Value_Calculation_CalculationValue value) => switch (value.whichValue()) { - Value_Calculation_CalculationValue_Value.number => - _deprotofyNumber(value.number), + Value_Calculation_CalculationValue_Value.number => _deprotofyNumber( + value.number, + ), Value_Calculation_CalculationValue_Value.calculation => _deprotofyCalculation(value.calculation), - Value_Calculation_CalculationValue_Value.string => - SassString(value.string, quotes: false), + Value_Calculation_CalculationValue_Value.string => SassString( + value.string, + quotes: false, + ), Value_Calculation_CalculationValue_Value.operation => SassCalculation.operate( - _deprotofyCalculationOperator(value.operation.operator), - _deprotofyCalculationValue(value.operation.left), - _deprotofyCalculationValue(value.operation.right)), - Value_Calculation_CalculationValue_Value.interpolation => - SassString('(${value.interpolation})', quotes: false), + _deprotofyCalculationOperator(value.operation.operator), + _deprotofyCalculationValue(value.operation.left), + _deprotofyCalculationValue(value.operation.right), + ), + Value_Calculation_CalculationValue_Value.interpolation => SassString( + '(${value.interpolation})', + quotes: false, + ), Value_Calculation_CalculationValue_Value.notSet => - throw mandatoryError("Value.Calculation.value") + throw mandatoryError("Value.Calculation.value"), }; /// Converts [operator] to its Sass representation. CalculationOperator _deprotofyCalculationOperator( - proto.CalculationOperator operator) => + proto.CalculationOperator operator, + ) => switch (operator) { proto.CalculationOperator.PLUS => CalculationOperator.plus, proto.CalculationOperator.MINUS => CalculationOperator.minus, proto.CalculationOperator.TIMES => CalculationOperator.times, proto.CalculationOperator.DIVIDE => CalculationOperator.dividedBy, - _ => throw "Unknown CalculationOperator $operator" + _ => throw "Unknown CalculationOperator $operator", }; } diff --git a/lib/src/embedded/reusable_isolate.dart b/lib/src/embedded/reusable_isolate.dart index 4140f5a37..dc21d6873 100644 --- a/lib/src/embedded/reusable_isolate.dart +++ b/lib/src/embedded/reusable_isolate.dart @@ -37,17 +37,25 @@ class ReusableIsolate { /// Whether the current isolate has been borrowed. bool _borrowed = false; - ReusableIsolate._(this._isolate, this._mailbox, this._receivePort, - {Function? onError}) - : _subscription = _receivePort.listen(_defaultOnData, onError: onError); + ReusableIsolate._( + this._isolate, + this._mailbox, + this._receivePort, { + Function? onError, + }) : _subscription = _receivePort.listen(_defaultOnData, onError: onError); /// Spawns a [ReusableIsolate] that runs the given [entryPoint]. - static Future spawn(ReusableIsolateEntryPoint entryPoint, - {Function? onError}) async { + static Future spawn( + ReusableIsolateEntryPoint entryPoint, { + Function? onError, + }) async { var mailbox = Mailbox(); var receivePort = ReceivePort(); - var isolate = await Isolate.spawn( - _isolateMain, (entryPoint, mailbox.asSendable, receivePort.sendPort)); + var isolate = await Isolate.spawn(_isolateMain, ( + entryPoint, + mailbox.asSendable, + receivePort.sendPort, + )); return ReusableIsolate._(isolate, mailbox, receivePort, onError: onError); } @@ -98,7 +106,8 @@ void _defaultOnData(dynamic _) { } void _isolateMain( - (ReusableIsolateEntryPoint, Sendable, SendPort) message) { + (ReusableIsolateEntryPoint, Sendable, SendPort) message, +) { var (entryPoint, sendableMailbox, sendPort) = message; entryPoint(sendableMailbox.materialize(), sendPort); } diff --git a/lib/src/embedded/util/length_delimited_transformer.dart b/lib/src/embedded/util/length_delimited_transformer.dart index 8e8b782e9..8748917c1 100644 --- a/lib/src/embedded/util/length_delimited_transformer.dart +++ b/lib/src/embedded/util/length_delimited_transformer.dart @@ -16,8 +16,10 @@ import 'varint_builder.dart'; /// arbitrarily-chunked binary data to one that sends and receives packets of /// set length using [lengthDelimitedEncoder] and [lengthDelimitedDecoder]. final StreamChannelTransformer> lengthDelimited = - StreamChannelTransformer>(lengthDelimitedDecoder, - StreamSinkTransformer.fromStreamTransformer(lengthDelimitedEncoder)); + StreamChannelTransformer>( + lengthDelimitedDecoder, + StreamSinkTransformer.fromStreamTransformer(lengthDelimitedEncoder), +); /// A transformer that converts an arbitrarily-chunked byte stream where each /// packet is prefixed with a 32-bit little-endian number indicating its length @@ -42,53 +44,58 @@ final lengthDelimitedDecoder = // need the outer one to establish a closure context so we can share state // across different input chunks, and the inner one takes care of all the // boilerplate of creating a new stream based on [stream]. - return stream - .transform(StreamTransformer.fromHandlers(handleData: (chunk, sink) { - // The index of the next byte to read from [chunk]. We have to track this - // because the chunk may contain the length *and* the message, or even - // multiple messages. - var i = 0; + return stream.transform( + StreamTransformer.fromHandlers( + handleData: (chunk, sink) { + // The index of the next byte to read from [chunk]. We have to track this + // because the chunk may contain the length *and* the message, or even + // multiple messages. + var i = 0; - while (i < chunk.length) { - var buffer_ = buffer; // dart-lang/language#1536 + while (i < chunk.length) { + var buffer_ = buffer; // dart-lang/language#1536 - // We can be in one of two states here: - // - // * [buffer] is `null`, in which case we're adding data to - // [nextMessageLength] until we reach a byte with its most significant - // bit set to 0. - // - // * [buffer] is not `null`, in which case we're waiting for [buffer] to - // have [nextMessageLength] bytes in it before we send it to - // [queue.local.sink] and start waiting for the next message. - if (buffer_ == null) { - var length = nextMessageLengthBuilder.add(chunk[i]); - i++; - if (length == null) continue; + // We can be in one of two states here: + // + // * [buffer] is `null`, in which case we're adding data to + // [nextMessageLength] until we reach a byte with its most significant + // bit set to 0. + // + // * [buffer] is not `null`, in which case we're waiting for [buffer] to + // have [nextMessageLength] bytes in it before we send it to + // [queue.local.sink] and start waiting for the next message. + if (buffer_ == null) { + var length = nextMessageLengthBuilder.add(chunk[i]); + i++; + if (length == null) continue; - // Otherwise, [nextMessageLength] is now finalized and we can allocate - // the data buffer. - buffer_ = buffer = Uint8List(length); - bufferIndex = 0; - } + // Otherwise, [nextMessageLength] is now finalized and we can allocate + // the data buffer. + buffer_ = buffer = Uint8List(length); + bufferIndex = 0; + } - // Copy as many bytes as we can from [chunk] to [buffer], making sure not - // to try to copy more than the buffer can hold (if the chunk has another - // message after the current one) or more than the chunk has available (if - // the current message is split across multiple chunks). - var bytesToWrite = - math.min(buffer_.length - bufferIndex, chunk.length - i); - buffer_.setRange(bufferIndex, bufferIndex + bytesToWrite, chunk, i); - i += bytesToWrite; - bufferIndex += bytesToWrite; - if (bufferIndex < buffer_.length) return; + // Copy as many bytes as we can from [chunk] to [buffer], making sure not + // to try to copy more than the buffer can hold (if the chunk has another + // message after the current one) or more than the chunk has available (if + // the current message is split across multiple chunks). + var bytesToWrite = math.min( + buffer_.length - bufferIndex, + chunk.length - i, + ); + buffer_.setRange(bufferIndex, bufferIndex + bytesToWrite, chunk, i); + i += bytesToWrite; + bufferIndex += bytesToWrite; + if (bufferIndex < buffer_.length) return; - // Once we've filled the buffer, emit it and reset our state. - sink.add(buffer_); - nextMessageLengthBuilder.reset(); - buffer = null; - } - })); + // Once we've filled the buffer, emit it and reset our state. + sink.add(buffer_); + nextMessageLengthBuilder.reset(); + buffer = null; + } + }, + ), + ); }); /// A transformer that adds 32-bit little-endian numbers indicating the length @@ -96,13 +103,14 @@ final lengthDelimitedDecoder = /// preserve packet boundaries. final lengthDelimitedEncoder = StreamTransformer>.fromHandlers( - handleData: (message, sink) { - var length = message.length; - if (length == 0) { - sink.add([0]); - return; - } + handleData: (message, sink) { + var length = message.length; + if (length == 0) { + sink.add([0]); + return; + } - sink.add(serializeVarint(length)); - sink.add(message); -}); + sink.add(serializeVarint(length)); + sink.add(message); + }, +); diff --git a/lib/src/embedded/util/proto_extensions.dart b/lib/src/embedded/util/proto_extensions.dart index 43eea2dc9..7147d3f71 100644 --- a/lib/src/embedded/util/proto_extensions.dart +++ b/lib/src/embedded/util/proto_extensions.dart @@ -14,7 +14,7 @@ extension InboundMessageExtensions on InboundMessage { InboundMessage_Message.importResponse => importResponse.id, InboundMessage_Message.fileImportResponse => fileImportResponse.id, InboundMessage_Message.functionCallResponse => functionCallResponse.id, - _ => null + _ => null, }; } @@ -28,7 +28,7 @@ extension OutboundMessageExtensions on OutboundMessage { OutboundMessage_Message.fileImportRequest => fileImportRequest.id, OutboundMessage_Message.functionCallRequest => functionCallRequest.id, OutboundMessage_Message.versionResponse => versionResponse.id, - _ => throw ArgumentError("Unknown message type: ${toDebugString()}") + _ => throw ArgumentError("Unknown message type: ${toDebugString()}"), }; /// Sets the outbound ID of this message, regardless of its type. diff --git a/lib/src/embedded/util/varint_builder.dart b/lib/src/embedded/util/varint_builder.dart index 78610bc9d..97e24bb60 100644 --- a/lib/src/embedded/util/varint_builder.dart +++ b/lib/src/embedded/util/varint_builder.dart @@ -76,7 +76,8 @@ class VarintBuilder { } /// Returns a [ProtocolError] indicating that the varint exceeded [_maxLength]. - ProtocolError _tooLong() => - parseError("Varint ${_name == null ? '' : '$_name '}was longer than " - "$_maxLength bits."); + ProtocolError _tooLong() => parseError( + "Varint ${_name == null ? '' : '$_name '}was longer than " + "$_maxLength bits.", + ); } diff --git a/lib/src/embedded/utils.dart b/lib/src/embedded/utils.dart index ff987cf2f..ea7430cdd 100644 --- a/lib/src/embedded/utils.dart +++ b/lib/src/embedded/utils.dart @@ -61,7 +61,7 @@ Syntax syntaxToSyntax(proto.Syntax syntax) => switch (syntax) { proto.Syntax.SCSS => Syntax.scss, proto.Syntax.INDENTED => Syntax.sass, proto.Syntax.CSS => Syntax.css, - _ => throw "Unknown syntax $syntax." + _ => throw "Unknown syntax $syntax.", }; /// Returns the result of running [callback] with the global ASCII config set @@ -117,7 +117,8 @@ final _compilationIdBuilder = VarintBuilder(32, 'compilation ID'); while (true) { if (i == packet.length) { throw parseError( - "Invalid compilation ID: continuation bit always set."); + "Invalid compilation ID: continuation bit always set.", + ); } var compilationId = _compilationIdBuilder.add(packet[i]); @@ -132,8 +133,11 @@ final _compilationIdBuilder = VarintBuilder(32, 'compilation ID'); } /// Wraps error object into ProtocolError, writes error to stderr, and returns the ProtocolError. -ProtocolError handleError(Object error, StackTrace stackTrace, - {int? messageId}) { +ProtocolError handleError( + Object error, + StackTrace stackTrace, { + int? messageId, +}) { if (error is ProtocolError) { error.id = messageId ?? errorId; stderr.write("Host caused ${error.type.name.toLowerCase()} error"); diff --git a/lib/src/environment.dart b/lib/src/environment.dart index fc0911f39..5312ad71f 100644 --- a/lib/src/environment.dart +++ b/lib/src/environment.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_environment.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 3698889f94437e131e4d581757d1538fd46be497 +// Checksum: add8a3972aec53ef29de3639af2bc84edcbde9e7 // // ignore_for_file: unused_import @@ -170,23 +170,24 @@ final class Environment { _mixinIndices = {}; Environment._( - this._modules, - this._namespaceNodes, - this._globalModules, - this._importedModules, - this._forwardedModules, - this._nestedForwardedModules, - this._allModules, - this._variables, - this._variableNodes, - this._functions, - this._mixins, - this._content) - // Lazily fill in the indices rather than eagerly copying them from the - // existing environment in closure() because the copying took a lot of - // time and was rarely helpful. This saves a bunch of time on Susy's - // tests. - : _variableIndices = {}, + this._modules, + this._namespaceNodes, + this._globalModules, + this._importedModules, + this._forwardedModules, + this._nestedForwardedModules, + this._allModules, + this._variables, + this._variableNodes, + this._functions, + this._mixins, + this._content, + ) + // Lazily fill in the indices rather than eagerly copying them from the + // existing environment in closure() because the copying took a lot of + // time and was rarely helpful. This saves a bunch of time on Susy's + // tests. + : _variableIndices = {}, _functionIndices = {}, _mixinIndices = {}; @@ -196,18 +197,19 @@ final class Environment { /// However, any new declarations or assignments in scopes that are visible /// when the closure was created will be reflected. Environment closure() => Environment._( - _modules, - _namespaceNodes, - _globalModules, - _importedModules, - _forwardedModules, - _nestedForwardedModules, - _allModules, - _variables.toList(), - _variableNodes.toList(), - _functions.toList(), - _mixins.toList(), - _content); + _modules, + _namespaceNodes, + _globalModules, + _importedModules, + _forwardedModules, + _nestedForwardedModules, + _allModules, + _variables.toList(), + _variableNodes.toList(), + _functions.toList(), + _mixins.toList(), + _content, + ); /// Returns a new environment to use for an imported file. /// @@ -215,18 +217,19 @@ final class Environment { /// and mixins, but excludes most modules (except for global modules that /// result from importing a file with forwards). Environment forImport() => Environment._( - {}, - {}, - {}, - _importedModules, - null, - null, - [], - _variables.toList(), - _variableNodes.toList(), - _functions.toList(), - _mixins.toList(), - _content); + {}, + {}, + {}, + _importedModules, + null, + null, + [], + _variables.toList(), + _variableNodes.toList(), + _functions.toList(), + _mixins.toList(), + _content, + ); /// Adds [module] to the set of modules visible in this environment. /// @@ -247,16 +250,18 @@ final class Environment { if (_variables.first.keys.firstWhereOrNull(module.variables.containsKey) case var name?) { throw SassScriptException( - 'This module and the new module both define a variable named ' - '"\$$name".'); + 'This module and the new module both define a variable named ' + '"\$$name".', + ); } } else { if (_modules.containsKey(namespace)) { var span = _namespaceNodes[namespace]?.span; throw MultiSpanSassScriptException( - "There's already a module with namespace \"$namespace\".", - "new @use", - {if (span != null) span: "original @use"}); + "There's already a module with namespace \"$namespace\".", + "new @use", + {if (span != null) span: "original @use"}, + ); } _modules[namespace] = module; @@ -273,9 +278,19 @@ final class Environment { var view = ForwardedModuleView.ifNecessary(module, rule); for (var other in forwardedModules.keys) { _assertNoConflicts( - view.variables, other.variables, view, other, "variable"); + view.variables, + other.variables, + view, + other, + "variable", + ); _assertNoConflicts( - view.functions, other.functions, view, other, "function"); + view.functions, + other.functions, + view, + other, + "function", + ); _assertNoConflicts(view.mixins, other.mixins, view, other, "mixin"); } @@ -292,11 +307,12 @@ final class Environment { /// /// The [type] and [newModuleNodeWithSpan] are used for error reporting. void _assertNoConflicts( - Map newMembers, - Map oldMembers, - Module newModule, - Module oldModule, - String type) { + Map newMembers, + Map oldMembers, + Module newModule, + Module oldModule, + String type, + ) { Map smaller; Map larger; if (newMembers.length < oldMembers.length) { @@ -319,9 +335,10 @@ final class Environment { if (type == "variable") name = "\$$name"; var span = _forwardedModules?[oldModule]?.span; throw MultiSpanSassScriptException( - 'Two forwarded modules both define a $type named $name.', - "new @forward", - {if (span != null) span: "original @forward"}); + 'Two forwarded modules both define a $type named $name.', + "new @forward", + {if (span != null) span: "original @forward"}, + ); } } @@ -349,23 +366,25 @@ final class Environment { } var forwardedVariableNames = { - for (var module in forwarded.keys) ...module.variables.keys + for (var module in forwarded.keys) ...module.variables.keys, }; var forwardedFunctionNames = { - for (var module in forwarded.keys) ...module.functions.keys + for (var module in forwarded.keys) ...module.functions.keys, }; var forwardedMixinNames = { - for (var module in forwarded.keys) ...module.mixins.keys + for (var module in forwarded.keys) ...module.mixins.keys, }; if (atRoot) { // Hide members from modules that have already been imported or // forwarded that would otherwise conflict with the @imported members. for (var (module, node) in _importedModules.pairs.toList()) { - var shadowed = ShadowedModuleView.ifNecessary(module, - variables: forwardedVariableNames, - mixins: forwardedMixinNames, - functions: forwardedFunctionNames); + var shadowed = ShadowedModuleView.ifNecessary( + module, + variables: forwardedVariableNames, + mixins: forwardedMixinNames, + functions: forwardedFunctionNames, + ); if (shadowed != null) { _importedModules.remove(module); if (!shadowed.isEmpty) _importedModules[shadowed] = node; @@ -373,10 +392,12 @@ final class Environment { } for (var (module, node) in forwardedModules.pairs.toList()) { - var shadowed = ShadowedModuleView.ifNecessary(module, - variables: forwardedVariableNames, - mixins: forwardedMixinNames, - functions: forwardedFunctionNames); + var shadowed = ShadowedModuleView.ifNecessary( + module, + variables: forwardedVariableNames, + mixins: forwardedMixinNames, + functions: forwardedFunctionNames, + ); if (shadowed != null) { forwardedModules.remove(module); if (!shadowed.isEmpty) forwardedModules[shadowed] = node; @@ -386,8 +407,10 @@ final class Environment { _importedModules.addAll(forwarded); forwardedModules.addAll(forwarded); } else { - (_nestedForwardedModules ??= - List.generate(_variables.length - 1, (_) => [])) + (_nestedForwardedModules ??= List.generate( + _variables.length - 1, + (_) => [], + )) .last .addAll(forwarded.keys); } @@ -536,8 +559,13 @@ final class Environment { /// defined with the given namespace, if no variable with the given [name] is /// defined in module with the given namespace, or if no [namespace] is passed /// and multiple global modules define variables named [name]. - void setVariable(String name, Value value, AstNode nodeWithSpan, - {String? namespace, bool global = false}) { + void setVariable( + String name, + Value value, + AstNode nodeWithSpan, { + String? namespace, + bool global = false, + }) { if (namespace != null) { _getModule(namespace).setVariable(name, value, nodeWithSpan); return; @@ -555,8 +583,11 @@ final class Environment { // If this module doesn't already contain a variable named [name], try // setting it in a global module. if (!_variables.first.containsKey(name)) { - var moduleWithName = _fromOneModule(name, "variable", - (module) => module.variables.containsKey(name) ? module : null); + var moduleWithName = _fromOneModule( + name, + "variable", + (module) => module.variables.containsKey(name) ? module : null, + ); if (moduleWithName != null) { moduleWithName.setVariable(name, value, nodeWithSpan); return; @@ -585,7 +616,9 @@ final class Environment { var index = _lastVariableName == name ? _lastVariableIndex! : _variableIndices.putIfAbsent( - name, () => _variableIndex(name) ?? _variables.length - 1); + name, + () => _variableIndex(name) ?? _variables.length - 1, + ); if (!_inSemiGlobalScope && index == 0) { index = _variables.length - 1; _variableIndices[name] = index; @@ -710,7 +743,10 @@ final class Environment { } /// Sets [content] as [this.content] for the duration of [callback]. - void withContent(UserDefinedCallable? content, void callback()) { + void withContent( + UserDefinedCallable? content, + void callback(), + ) { var oldContent = _content; _content = content; callback(); @@ -733,7 +769,11 @@ final class Environment { /// /// If [when] is false, this doesn't create a new scope and instead just /// executes [callback] and returns its result. - T scope(T callback(), {bool semiGlobal = false, bool when = true}) { + T scope( + T callback(), { + bool semiGlobal = false, + bool when = true, + }) { // We have to track semi-globalness even if `!when` so that // // div { @@ -798,12 +838,18 @@ final class Environment { /// that contains [css] and [preModuleComments] as its CSS, which can be /// extended using [extensionStore]. Module toModule( - CssStylesheet css, - Map, List> preModuleComments, - ExtensionStore extensionStore) { + CssStylesheet css, + Map, List> preModuleComments, + ExtensionStore extensionStore, + ) { assert(atRoot); - return _EnvironmentModule(this, css, preModuleComments, extensionStore, - forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); + return _EnvironmentModule( + this, + css, + preModuleComments, + extensionStore, + forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules)), + ); } /// Returns a module with the same members and upstream modules as `this`, but @@ -813,19 +859,23 @@ final class Environment { /// members into the current scope. It's the only situation in which a nested /// environment can become a module. Module toDummyModule() => _EnvironmentModule( - this, - CssStylesheet(const [], - SourceFile.decoded(const [], url: "").span(0)), - const {}, - ExtensionStore.empty, - forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); + this, + CssStylesheet( + const [], + SourceFile.decoded(const [], url: "").span(0), + ), + const {}, + ExtensionStore.empty, + forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules)), + ); /// Returns the module with the given [namespace], or throws a /// [SassScriptException] if none exists. Module _getModule(String namespace) { if (_modules[namespace] case var module?) return module; throw SassScriptException( - 'There is no module with the namespace "$namespace".'); + 'There is no module with the namespace "$namespace".', + ); } /// Returns the result of [callback] if it returns non-`null` for exactly one @@ -865,11 +915,13 @@ final class Environment { if (value != null) { throw MultiSpanSassScriptException( - 'This $type is available from multiple global modules.', - '$type use', { - for (var (module, node) in _globalModules.pairs) - if (callback(module) != null) node.span: 'includes $type' - }); + 'This $type is available from multiple global modules.', + '$type use', + { + for (var (module, node) in _globalModules.pairs) + if (callback(module) != null) node.span: 'includes $type', + }, + ); } value = valueInModule; @@ -906,36 +958,48 @@ final class _EnvironmentModule implements Module { final Map> _modulesByVariable; factory _EnvironmentModule( - Environment environment, - CssStylesheet css, - Map, List> preModuleComments, - ExtensionStore extensionStore, - {Set>? forwarded}) { + Environment environment, + CssStylesheet css, + Map, List> preModuleComments, + ExtensionStore extensionStore, { + Set>? forwarded, + }) { forwarded ??= const {}; return _EnvironmentModule._( - environment, - css, - Map.unmodifiable({ - for (var (module, comments) in preModuleComments.pairs) - module: List.unmodifiable(comments) - }), - extensionStore, - _makeModulesByVariable(forwarded), - _memberMap(environment._variables.first, - forwarded.map((module) => module.variables)), - _memberMap(environment._variableNodes.first, - forwarded.map((module) => module.variableNodes)), - _memberMap(environment._functions.first, - forwarded.map((module) => module.functions)), - _memberMap(environment._mixins.first, - forwarded.map((module) => module.mixins)), - transitivelyContainsCss: css.children.isNotEmpty || - preModuleComments.isNotEmpty || - environment._allModules - .any((module) => module.transitivelyContainsCss), - transitivelyContainsExtensions: !extensionStore.isEmpty || - environment._allModules - .any((module) => module.transitivelyContainsExtensions)); + environment, + css, + Map.unmodifiable({ + for (var (module, comments) in preModuleComments.pairs) + module: List.unmodifiable(comments), + }), + extensionStore, + _makeModulesByVariable(forwarded), + _memberMap( + environment._variables.first, + forwarded.map((module) => module.variables), + ), + _memberMap( + environment._variableNodes.first, + forwarded.map((module) => module.variableNodes), + ), + _memberMap( + environment._functions.first, + forwarded.map((module) => module.functions), + ), + _memberMap( + environment._mixins.first, + forwarded.map((module) => module.mixins), + ), + transitivelyContainsCss: css.children.isNotEmpty || + preModuleComments.isNotEmpty || + environment._allModules.any( + (module) => module.transitivelyContainsCss, + ), + transitivelyContainsExtensions: !extensionStore.isEmpty || + environment._allModules.any( + (module) => module.transitivelyContainsExtensions, + ), + ); } /// Create [_modulesByVariable] for a set of forwarded modules. @@ -950,8 +1014,11 @@ final class _EnvironmentModule implements Module { for (var child in module._modulesByVariable.values) { setAll(modulesByVariable, child.variables.keys, child); } - setAll(modulesByVariable, module._environment._variables.first.keys, - module); + setAll( + modulesByVariable, + module._environment._variables.first.keys, + module, + ); } else { setAll(modulesByVariable, module.variables.keys, module); } @@ -962,14 +1029,16 @@ final class _EnvironmentModule implements Module { /// Returns a map that exposes the public members of [localMap] as well as all /// the members of [otherMaps]. static Map _memberMap( - Map localMap, Iterable> otherMaps) { + Map localMap, + Iterable> otherMaps, + ) { localMap = PublicMemberMapView(localMap); if (otherMaps.isEmpty) return localMap; var allMaps = [ for (var map in otherMaps) if (map.isNotEmpty) map, - localMap + localMap, ]; if (allMaps.length == 1) return localMap; @@ -977,18 +1046,18 @@ final class _EnvironmentModule implements Module { } _EnvironmentModule._( - this._environment, - this.css, - this.preModuleComments, - this.extensionStore, - this._modulesByVariable, - this.variables, - this.variableNodes, - this.functions, - this.mixins, - {required this.transitivelyContainsCss, - required this.transitivelyContainsExtensions}) - : upstream = _environment._allModules; + this._environment, + this.css, + this.preModuleComments, + this.extensionStore, + this._modulesByVariable, + this.variables, + this.variableNodes, + this.functions, + this.mixins, { + required this.transitivelyContainsCss, + required this.transitivelyContainsExtensions, + }) : upstream = _environment._allModules; void setVariable(String name, Value value, AstNode nodeWithSpan) { if (_modulesByVariable[name] case var module?) { @@ -1014,20 +1083,23 @@ final class _EnvironmentModule implements Module { Module cloneCss() { if (!transitivelyContainsCss) return this; - var (newStylesheet, newExtensionStore) = - cloneCssStylesheet(css, extensionStore); + var (newStylesheet, newExtensionStore) = cloneCssStylesheet( + css, + extensionStore, + ); return _EnvironmentModule._( - _environment, - newStylesheet, - preModuleComments, - newExtensionStore, - _modulesByVariable, - variables, - variableNodes, - functions, - mixins, - transitivelyContainsCss: transitivelyContainsCss, - transitivelyContainsExtensions: transitivelyContainsExtensions); + _environment, + newStylesheet, + preModuleComments, + newExtensionStore, + _modulesByVariable, + variables, + variableNodes, + functions, + mixins, + transitivelyContainsCss: transitivelyContainsCss, + transitivelyContainsExtensions: transitivelyContainsExtensions, + ); } String toString() => url == null ? "" : p.prettyUri(url); diff --git a/lib/src/evaluation_context.dart b/lib/src/evaluation_context.dart index 1f831cd87..1b38b2300 100644 --- a/lib/src/evaluation_context.dart +++ b/lib/src/evaluation_context.dart @@ -26,7 +26,7 @@ abstract interface class EvaluationContext { static EvaluationContext? get currentOrNull => switch (Zone.current[#_evaluationContext]) { EvaluationContext context => context, - _ => null + _ => null, }; /// The current evaluation context, or null if there isn't a Sass stylesheet @@ -65,18 +65,22 @@ abstract interface class EvaluationContext { /// {@category Compile} void warn(String message, {bool deprecation = false}) => switch (EvaluationContext.currentOrNull) { - var context? => - context.warn(message, deprecation ? Deprecation.userAuthored : null), - _ when deprecation => (const Logger.stderr()) - .warnForDeprecation(Deprecation.userAuthored, message), - _ => (const Logger.stderr()).warn(message) + var context? => context.warn( + message, + deprecation ? Deprecation.userAuthored : null, + ), + _ when deprecation => (const Logger.stderr()).warnForDeprecation( + Deprecation.userAuthored, + message, + ), + _ => (const Logger.stderr()).warn(message), }; /// Prints a deprecation warning with [message] of type [deprecation]. void warnForDeprecation(String message, Deprecation deprecation) => switch (EvaluationContext.currentOrNull) { var context? => context.warn(message, deprecation), - _ => (const Logger.stderr()).warnForDeprecation(deprecation, message) + _ => (const Logger.stderr()).warnForDeprecation(deprecation, message), }; /// Prints a deprecation warning with [message] of type [deprecation], diff --git a/lib/src/exception.dart b/lib/src/exception.dart index 898258c88..6a46fcc25 100644 --- a/lib/src/exception.dart +++ b/lib/src/exception.dart @@ -119,23 +119,44 @@ class MultiSpanSassException extends SassException final String primaryLabel; final Map secondarySpans; - MultiSpanSassException(String message, FileSpan span, this.primaryLabel, - Map secondarySpans, - [Iterable? loadedUrls]) - : secondarySpans = Map.unmodifiable(secondarySpans), + MultiSpanSassException( + String message, + FileSpan span, + this.primaryLabel, + Map secondarySpans, [ + Iterable? loadedUrls, + ]) : secondarySpans = Map.unmodifiable(secondarySpans), super(message, span, loadedUrls); MultiSpanSassException withAdditionalSpan(FileSpan span, String label) => - MultiSpanSassException(message, this.span, primaryLabel, - {...secondarySpans, span: label}, loadedUrls); + MultiSpanSassException( + message, + this.span, + primaryLabel, + { + ...secondarySpans, + span: label, + }, + loadedUrls); MultiSpanSassRuntimeException withTrace(Trace trace) => MultiSpanSassRuntimeException( - message, span, primaryLabel, secondarySpans, trace, loadedUrls); + message, + span, + primaryLabel, + secondarySpans, + trace, + loadedUrls, + ); MultiSpanSassException withLoadedUrls(Iterable loadedUrls) => MultiSpanSassException( - message, span, primaryLabel, secondarySpans, loadedUrls); + message, + span, + primaryLabel, + secondarySpans, + loadedUrls, + ); String toString({Object? color, String? secondaryColor}) { var useColor = false; @@ -150,10 +171,13 @@ class MultiSpanSassException extends SassException var buffer = StringBuffer("Error: $message\n"); span - .highlightMultiple(primaryLabel, secondarySpans, - color: useColor, - primaryColor: primaryColor, - secondaryColor: secondaryColor) + .highlightMultiple( + primaryLabel, + secondarySpans, + color: useColor, + primaryColor: primaryColor, + secondaryColor: secondaryColor, + ) .andThen(buffer.write); for (var frame in trace.toString().split("\n")) { @@ -170,16 +194,27 @@ class SassRuntimeException extends SassException { final Trace trace; MultiSpanSassRuntimeException withAdditionalSpan( - FileSpan span, String label) => + FileSpan span, + String label, + ) => MultiSpanSassRuntimeException( - message, this.span, "", {span: label}, trace, loadedUrls); + message, + this.span, + "", + {span: label}, + trace, + loadedUrls, + ); SassRuntimeException withLoadedUrls(Iterable loadedUrls) => SassRuntimeException(message, span, trace, loadedUrls); - SassRuntimeException(String message, FileSpan span, this.trace, - [Iterable? loadedUrls]) - : super(message, span, loadedUrls); + SassRuntimeException( + String message, + FileSpan span, + this.trace, [ + Iterable? loadedUrls, + ]) : super(message, span, loadedUrls); } /// A [SassRuntimeException] that's also a [MultiSpanSassException]. @@ -187,19 +222,37 @@ class MultiSpanSassRuntimeException extends MultiSpanSassException implements SassRuntimeException { final Trace trace; - MultiSpanSassRuntimeException(String message, FileSpan span, - String primaryLabel, Map secondarySpans, this.trace, - [Iterable? loadedUrls]) - : super(message, span, primaryLabel, secondarySpans, loadedUrls); + MultiSpanSassRuntimeException( + String message, + FileSpan span, + String primaryLabel, + Map secondarySpans, + this.trace, [ + Iterable? loadedUrls, + ]) : super(message, span, primaryLabel, secondarySpans, loadedUrls); MultiSpanSassRuntimeException withAdditionalSpan( - FileSpan span, String label) => - MultiSpanSassRuntimeException(message, this.span, primaryLabel, - {...secondarySpans, span: label}, trace, loadedUrls); + FileSpan span, + String label, + ) => + MultiSpanSassRuntimeException( + message, + this.span, + primaryLabel, + {...secondarySpans, span: label}, + trace, + loadedUrls, + ); MultiSpanSassRuntimeException withLoadedUrls(Iterable loadedUrls) => MultiSpanSassRuntimeException( - message, span, primaryLabel, secondarySpans, trace, loadedUrls); + message, + span, + primaryLabel, + secondarySpans, + trace, + loadedUrls, + ); } /// An exception thrown when Sass parsing has failed. @@ -215,9 +268,17 @@ class SassFormatException extends SassException /// @nodoc @internal MultiSpanSassFormatException withAdditionalSpan( - FileSpan span, String label) => + FileSpan span, + String label, + ) => MultiSpanSassFormatException( - message, this.span, "", {span: label}, loadedUrls); + message, + this.span, + "", + { + span: label, + }, + loadedUrls); /// @nodoc SassFormatException withLoadedUrls(Iterable loadedUrls) => @@ -237,17 +298,35 @@ class MultiSpanSassFormatException extends MultiSpanSassException int get offset => span.start.offset; MultiSpanSassFormatException withAdditionalSpan( - FileSpan span, String label) => - MultiSpanSassFormatException(message, this.span, primaryLabel, - {...secondarySpans, span: label}, loadedUrls); + FileSpan span, + String label, + ) => + MultiSpanSassFormatException( + message, + this.span, + primaryLabel, + { + ...secondarySpans, + span: label, + }, + loadedUrls); MultiSpanSassFormatException withLoadedUrls(Iterable loadedUrls) => MultiSpanSassFormatException( - message, span, primaryLabel, secondarySpans, loadedUrls); + message, + span, + primaryLabel, + secondarySpans, + loadedUrls, + ); MultiSpanSassFormatException( - super.message, super.span, super.primaryLabel, super.secondarySpans, - [super.loadedUrls]); + super.message, + super.span, + super.primaryLabel, + super.secondarySpans, [ + super.loadedUrls, + ]); } /// An exception thrown by SassScript. @@ -283,8 +362,10 @@ class MultiSpanSassScriptException extends SassScriptException { final Map secondarySpans; MultiSpanSassScriptException( - super.message, this.primaryLabel, Map secondarySpans) - : secondarySpans = Map.unmodifiable(secondarySpans); + super.message, + this.primaryLabel, + Map secondarySpans, + ) : secondarySpans = Map.unmodifiable(secondarySpans); /// Converts this to a [SassException] with the given primary [span]. MultiSpanSassException withSpan(FileSpan span) => diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 50bdfaac3..62ea7568e 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -33,13 +33,21 @@ import 'options.dart'; /// modification time. Note that these modification times are cached by [graph]. /// /// Returns `(exitCode, error, stackTrace)` when an error occurs. -Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options, - StylesheetGraph graph, String? source, String? destination, - {bool ifModified = false}) async { +Future<(int, String, String?)?> compileStylesheet( + ExecutableOptions options, + StylesheetGraph graph, + String? source, + String? destination, { + bool ifModified = false, +}) async { try { await _compileStylesheetWithoutErrorHandling( - options, graph, source, destination, - ifModified: ifModified); + options, + graph, + source, + destination, + ifModified: ifModified, + ); } on SassException catch (error, stackTrace) { if (destination != null && !options.emitErrorCss) { _tryDelete(destination); @@ -49,7 +57,10 @@ Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options, // Exit code 65 indicates invalid data per // https://www.freebsd.org/cgi/man.cgi?query=sysexits. return _getErrorWithStackTrace( - 65, message, options.trace ? getTrace(error) ?? stackTrace : null); + 65, + message, + options.trace ? getTrace(error) ?? stackTrace : null, + ); } on FileSystemException catch (error, stackTrace) { var path = error.path; var message = path == null @@ -58,23 +69,33 @@ Future<(int, String, String?)?> compileStylesheet(ExecutableOptions options, // Exit code 66 indicates no input. return _getErrorWithStackTrace( - 66, message, options.trace ? getTrace(error) ?? stackTrace : null); + 66, + message, + options.trace ? getTrace(error) ?? stackTrace : null, + ); } return null; } /// Like [compileStylesheet], but throws errors instead of handling them /// internally. -Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, - StylesheetGraph graph, String? source, String? destination, - {bool ifModified = false}) async { +Future _compileStylesheetWithoutErrorHandling( + ExecutableOptions options, + StylesheetGraph graph, + String? source, + String? destination, { + bool ifModified = false, +}) async { var importer = FilesystemImporter.cwd; if (ifModified) { try { if (source != null && destination != null && - !graph.modifiedSince(p.toUri(p.absolute(source)), - modificationTime(destination), importer)) { + !graph.modifiedSince( + p.toUri(p.absolute(source)), + modificationTime(destination), + importer, + )) { return; } } on FileSystemException catch (_) { @@ -95,10 +116,13 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, try { if (options.asynchronous) { var importCache = AsyncImportCache( - importers: options.pkgImporters, loadPaths: options.loadPaths); + importers: options.pkgImporters, + loadPaths: options.loadPaths, + ); result = source == null - ? await compileStringAsync(await readStdin(), + ? await compileStringAsync( + await readStdin(), syntax: syntax, logger: options.logger, importCache: importCache, @@ -110,8 +134,10 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, charset: options.charset, silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, - futureDeprecations: options.futureDeprecations) - : await compileAsync(source, + futureDeprecations: options.futureDeprecations, + ) + : await compileAsync( + source, syntax: syntax, logger: options.logger, importCache: importCache, @@ -122,14 +148,16 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, charset: options.charset, silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, - futureDeprecations: options.futureDeprecations); + futureDeprecations: options.futureDeprecations, + ); } else { // Double-check that all modified files (according to mtime) are actually // reloaded in the graph so we don't end up with stale ASTs. graph.reloadAllModified(); result = source == null - ? compileString(await readStdin(), + ? compileString( + await readStdin(), syntax: syntax, logger: options.logger, importCache: graph.importCache, @@ -141,8 +169,10 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, charset: options.charset, silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, - futureDeprecations: options.futureDeprecations) - : compile(source, + futureDeprecations: options.futureDeprecations, + ) + : compile( + source, syntax: syntax, logger: options.logger, importCache: graph.importCache, @@ -153,7 +183,8 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, charset: options.charset, silenceDeprecations: options.silenceDeprecations, fatalDeprecations: options.fatalDeprecations, - futureDeprecations: options.futureDeprecations); + futureDeprecations: options.futureDeprecations, + ); } } on SassException catch (error) { if (options.emitErrorCss) { @@ -205,7 +236,10 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, /// /// Returns the source map comment to add to the end of the CSS file. String _writeSourceMap( - ExecutableOptions options, SingleMapping? sourceMap, String? destination) { + ExecutableOptions options, + SingleMapping? sourceMap, + String? destination, +) { if (sourceMap == null) return ""; if (destination != null) { @@ -214,15 +248,21 @@ String _writeSourceMap( // TODO(nweiz): Don't explicitly use a type parameter when dart-lang/sdk#25490 // is fixed. - mapInPlace(sourceMap.urls, - (url) => options.sourceMapUrl(Uri.parse(url), destination).toString()); - var sourceMapText = - jsonEncode(sourceMap.toJson(includeSourceContents: options.embedSources)); + mapInPlace( + sourceMap.urls, + (url) => options.sourceMapUrl(Uri.parse(url), destination).toString(), + ); + var sourceMapText = jsonEncode( + sourceMap.toJson(includeSourceContents: options.embedSources), + ); Uri url; if (options.embedSourceMap) { - url = Uri.dataFromString(sourceMapText, - mimeType: 'application/json', encoding: utf8); + url = Uri.dataFromString( + sourceMapText, + mimeType: 'application/json', + encoding: utf8, + ); } else { // [destination] can't be null here because --embed-source-map is // incompatible with writing to stdout. @@ -252,12 +292,15 @@ void _tryDelete(String path) { /// Return a Record of `(exitCode, error, stackTrace)` for the given error. (int, String, String?) _getErrorWithStackTrace( - int exitCode, String error, StackTrace? stackTrace) { + int exitCode, + String error, + StackTrace? stackTrace, +) { return ( exitCode, error, stackTrace != null ? Trace.from(stackTrace).terse.toString().trimRight() - : null + : null, ); } diff --git a/lib/src/executable/concurrent.dart b/lib/src/executable/concurrent.dart index f3020f679..5ee04afd8 100644 --- a/lib/src/executable/concurrent.dart +++ b/lib/src/executable/concurrent.dart @@ -15,21 +15,34 @@ import 'options.dart'; /// Compiles the stylesheets concurrently and returns whether all stylesheets are compiled /// successfully. -Future compileStylesheets(ExecutableOptions options, - StylesheetGraph graph, Map sourcesToDestinations, - {bool ifModified = false}) async { +Future compileStylesheets( + ExecutableOptions options, + StylesheetGraph graph, + Map sourcesToDestinations, { + bool ifModified = false, +}) async { var errorsWithStackTraces = switch ([...sourcesToDestinations.pairs]) { // Concurrency does add some overhead, so avoid it in the common case of // compiling a single stylesheet. [(var source, var destination)] => [ - await compileStylesheet(options, graph, source, destination, - ifModified: ifModified) + await compileStylesheet( + options, + graph, + source, + destination, + ifModified: ifModified, + ), ], var pairs => await Future.wait([ for (var (source, destination) in pairs) - compileStylesheetConcurrently(options, graph, source, destination, - ifModified: ifModified) - ], eagerError: options.stopOnError) + compileStylesheetConcurrently( + options, + graph, + source, + destination, + ifModified: ifModified, + ), + ], eagerError: options.stopOnError), }; var printedError = false; diff --git a/lib/src/executable/concurrent/vm.dart b/lib/src/executable/concurrent/vm.dart index b063b52f0..0182a6d25 100644 --- a/lib/src/executable/concurrent/vm.dart +++ b/lib/src/executable/concurrent/vm.dart @@ -14,20 +14,31 @@ import '../compile_stylesheet.dart'; /// /// Runs in a new Dart Isolate, unless [source] is `null`. Future<(int, String, String?)?> compileStylesheetConcurrently( - ExecutableOptions options, - StylesheetGraph graph, - String? source, - String? destination, - {bool ifModified = false}) { + ExecutableOptions options, + StylesheetGraph graph, + String? source, + String? destination, { + bool ifModified = false, +}) { // Reading from stdin does not work properly in dart isolate. if (source == null) { - return compileStylesheet(options, graph, source, destination, - ifModified: ifModified); + return compileStylesheet( + options, + graph, + source, + destination, + ifModified: ifModified, + ); } return Isolate.run(() { term_glyph.ascii = !options.unicode; - return compileStylesheet(options, graph, source, destination, - ifModified: ifModified); + return compileStylesheet( + options, + graph, + source, + destination, + ifModified: ifModified, + ); }); } diff --git a/lib/src/executable/options.dart b/lib/src/executable/options.dart index c0aeaa077..2c901b665 100644 --- a/lib/src/executable/options.dart +++ b/lib/src/executable/options.dart @@ -28,11 +28,9 @@ final class ExecutableOptions { /// The parser that defines the arguments the executable allows. static final ArgParser _parser = () { var parser = ArgParser(allowTrailingOptions: true) - // This is used for compatibility with sass-spec, even though we don't // support setting the precision. ..addOption('precision', hide: true) - // This is used when testing to ensure that the asynchronous evaluator path // works the same as the synchronous one. ..addFlag('async', hide: true); @@ -40,91 +38,145 @@ final class ExecutableOptions { parser ..addSeparator(_separator('Input and Output')) ..addFlag('stdin', help: 'Read the stylesheet from stdin.') - ..addFlag('indented', - help: 'Use the indented syntax for input from stdin.') - ..addMultiOption('load-path', - abbr: 'I', - valueHelp: 'PATH', - help: 'A path to use when resolving imports.\n' - 'May be passed multiple times.', - splitCommas: false) - ..addMultiOption('pkg-importer', - abbr: 'p', - valueHelp: 'TYPE', - allowed: ['node'], - help: 'Built-in importer(s) to use for pkg: URLs.', - allowedHelp: {'node': 'Load files like Node.js package resolution.'}) - ..addOption('style', - abbr: 's', - valueHelp: 'NAME', - help: 'Output style.', - allowed: ['expanded', 'compressed'], - defaultsTo: 'expanded') - ..addFlag('charset', - help: 'Emit a @charset or BOM for CSS with non-ASCII characters.', - defaultsTo: true) - ..addFlag('error-css', - help: 'When an error occurs, emit a stylesheet describing it.\n' - 'Defaults to true when compiling to a file.', - defaultsTo: null) - ..addFlag('update', - help: 'Only compile out-of-date stylesheets.', negatable: false); + ..addFlag( + 'indented', + help: 'Use the indented syntax for input from stdin.', + ) + ..addMultiOption( + 'load-path', + abbr: 'I', + valueHelp: 'PATH', + help: 'A path to use when resolving imports.\n' + 'May be passed multiple times.', + splitCommas: false, + ) + ..addMultiOption( + 'pkg-importer', + abbr: 'p', + valueHelp: 'TYPE', + allowed: ['node'], + help: 'Built-in importer(s) to use for pkg: URLs.', + allowedHelp: {'node': 'Load files like Node.js package resolution.'}, + ) + ..addOption( + 'style', + abbr: 's', + valueHelp: 'NAME', + help: 'Output style.', + allowed: ['expanded', 'compressed'], + defaultsTo: 'expanded', + ) + ..addFlag( + 'charset', + help: 'Emit a @charset or BOM for CSS with non-ASCII characters.', + defaultsTo: true, + ) + ..addFlag( + 'error-css', + help: 'When an error occurs, emit a stylesheet describing it.\n' + 'Defaults to true when compiling to a file.', + defaultsTo: null, + ) + ..addFlag( + 'update', + help: 'Only compile out-of-date stylesheets.', + negatable: false, + ); parser ..addSeparator(_separator('Source Maps')) - ..addFlag('source-map', - help: 'Whether to generate source maps.', defaultsTo: true) - ..addOption('source-map-urls', - defaultsTo: 'relative', - help: 'How to link from source maps to source files.', - allowed: ['relative', 'absolute']) - ..addFlag('embed-sources', - help: 'Embed source file contents in source maps.', defaultsTo: false) - ..addFlag('embed-source-map', - help: 'Embed source map contents in CSS.', defaultsTo: false); + ..addFlag( + 'source-map', + help: 'Whether to generate source maps.', + defaultsTo: true, + ) + ..addOption( + 'source-map-urls', + defaultsTo: 'relative', + help: 'How to link from source maps to source files.', + allowed: ['relative', 'absolute'], + ) + ..addFlag( + 'embed-sources', + help: 'Embed source file contents in source maps.', + defaultsTo: false, + ) + ..addFlag( + 'embed-source-map', + help: 'Embed source map contents in CSS.', + defaultsTo: false, + ); parser ..addSeparator(_separator('Warnings')) ..addFlag('quiet', abbr: 'q', help: "Don't print warnings.") - ..addFlag('quiet-deps', - help: "Don't print compiler warnings from dependencies.\n" - "Stylesheets imported through load paths count as dependencies.") - ..addFlag('verbose', - help: "Print all deprecation warnings even when they're repetitive.") - ..addMultiOption('fatal-deprecation', - help: 'Deprecations to treat as errors. You may also pass a Sass\n' - 'version to include any behavior deprecated in or before it.\n' - 'See https://sass-lang.com/documentation/breaking-changes for \n' - 'a complete list.') + ..addFlag( + 'quiet-deps', + help: "Don't print compiler warnings from dependencies.\n" + "Stylesheets imported through load paths count as dependencies.", + ) + ..addFlag( + 'verbose', + help: "Print all deprecation warnings even when they're repetitive.", + ) + ..addMultiOption( + 'fatal-deprecation', + help: 'Deprecations to treat as errors. You may also pass a Sass\n' + 'version to include any behavior deprecated in or before it.\n' + 'See https://sass-lang.com/documentation/breaking-changes for \n' + 'a complete list.', + ) ..addMultiOption('silence-deprecation', help: 'Deprecations to ignore.') - ..addMultiOption('future-deprecation', - help: 'Opt in to a deprecation early.'); + ..addMultiOption( + 'future-deprecation', + help: 'Opt in to a deprecation early.', + ); parser ..addSeparator(_separator('Other')) - ..addFlag('watch', - abbr: 'w', - help: 'Watch stylesheets and recompile when they change.', - negatable: false) - ..addFlag('poll', - help: 'Manually check for changes rather than using a native ' - 'watcher.\n' - 'Only valid with --watch.') - ..addFlag('stop-on-error', - help: "Don't compile more files once an error is encountered.") - ..addFlag('interactive', - abbr: 'i', - help: 'Run an interactive SassScript shell.', - negatable: false) - ..addFlag('color', - abbr: 'c', help: 'Whether to use terminal colors for messages.') - ..addFlag('unicode', - help: 'Whether to use Unicode characters for messages.') + ..addFlag( + 'watch', + abbr: 'w', + help: 'Watch stylesheets and recompile when they change.', + negatable: false, + ) + ..addFlag( + 'poll', + help: 'Manually check for changes rather than using a native ' + 'watcher.\n' + 'Only valid with --watch.', + ) + ..addFlag( + 'stop-on-error', + help: "Don't compile more files once an error is encountered.", + ) + ..addFlag( + 'interactive', + abbr: 'i', + help: 'Run an interactive SassScript shell.', + negatable: false, + ) + ..addFlag( + 'color', + abbr: 'c', + help: 'Whether to use terminal colors for messages.', + ) + ..addFlag( + 'unicode', + help: 'Whether to use Unicode characters for messages.', + ) ..addFlag('trace', help: 'Print full Dart stack traces for exceptions.') - ..addFlag('help', - abbr: 'h', help: 'Print this usage information.', negatable: false) - ..addFlag('version', - help: 'Print the version of Dart Sass.', negatable: false); + ..addFlag( + 'help', + abbr: 'h', + help: 'Print this usage information.', + negatable: false, + ) + ..addFlag( + 'version', + help: 'Print the version of Dart Sass.', + negatable: false, + ); return parser; }(); @@ -158,7 +210,7 @@ final class ExecutableOptions { var invalidOptions = [ 'stdin', 'indented', 'style', 'source-map', 'source-map-urls', // - 'embed-sources', 'embed-source-map', 'update', 'watch' + 'embed-sources', 'embed-source-map', 'update', 'watch', ]; if (invalidOptions.firstWhereOrNull(_options.wasParsed) case var option?) { throw UsageException("--$option isn't allowed with --interactive."); @@ -209,7 +261,7 @@ final class ExecutableOptions { /// The list of built-in importers to use to load `pkg:` URLs. List get pkgImporters => [ for (var _ in _options['pkg-importer'] as List) - NodePackageImporter('.') + NodePackageImporter('.'), ]; /// Whether to run the evaluator in asynchronous mode, for debugging purposes. @@ -300,8 +352,9 @@ final class ExecutableOptions { } else if (watch) { _fail("--watch is not allowed with --stdin."); } - _sourcesToDestinations = Map.unmodifiable( - {null: _options.rest.isEmpty ? null : _options.rest.first}); + _sourcesToDestinations = Map.unmodifiable({ + null: _options.rest.isEmpty ? null : _options.rest.first, + }); } else if (_options.rest.length > 2) { _fail("Only two positional args may be passed."); } else if (directories.isNotEmpty) { @@ -369,8 +422,9 @@ final class ExecutableOptions { } } _sourcesToDestinations = UnmodifiableMapView(sourcesToDestinations); - _sourceDirectoriesToDestinations = - UnmodifiableMapView(sourceDirectoriesToDestinations); + _sourceDirectoriesToDestinations = UnmodifiableMapView( + sourceDirectoriesToDestinations, + ); } /// Splits an argument that contains a colon and returns its source and its @@ -411,8 +465,10 @@ final class ExecutableOptions { if (_isEntrypoint(path) && // Don't compile a CSS file to its own location. !(source == destination && p.extension(path) == '.css')) - path: p.join(destination, - p.setExtension(p.relative(path, from: source), '.css')) + path: p.join( + destination, + p.setExtension(p.relative(path, from: source), '.css'), + ), }; } @@ -443,20 +499,26 @@ final class ExecutableOptions { if (_ifParsed('source-map-urls') == 'relative') { _fail( - "--source-map-urls=relative isn't allowed when printing to stdout."); + "--source-map-urls=relative isn't allowed when printing to stdout.", + ); } if (_options['embed-source-map'] as bool) { return _options['source-map'] as bool; } else if (_ifParsed('source-map') == true) { _fail( - "When printing to stdout, --source-map requires --embed-source-map."); + "When printing to stdout, --source-map requires --embed-source-map.", + ); } else if (_options.wasParsed('source-map-urls')) { - _fail("When printing to stdout, --source-map-urls requires " - "--embed-source-map."); + _fail( + "When printing to stdout, --source-map-urls requires " + "--embed-source-map.", + ); } else if (_options['embed-sources'] as bool) { - _fail("When printing to stdout, --embed-sources requires " - "--embed-source-map."); + _fail( + "When printing to stdout, --embed-sources requires " + "--embed-source-map.", + ); } else { return false; } @@ -495,17 +557,19 @@ final class ExecutableOptions { if (url.scheme.isNotEmpty && url.scheme != 'file') return url; var path = p.fromUri(url); - return p.toUri(_options['source-map-urls'] == 'relative' && !_writeToStdout - // [destination] can't be null here because --source-map-urls=relative - // is incompatible with writing to stdout. - ? p.relative(path, from: p.dirname(destination!)) - : p.absolute(path)); + return p.toUri( + _options['source-map-urls'] == 'relative' && !_writeToStdout + // [destination] can't be null here because --source-map-urls=relative + // is incompatible with writing to stdout. + ? p.relative(path, from: p.dirname(destination!)) + : p.absolute(path), + ); } /// The set of deprecations whose warnings should be silenced. Set get silenceDeprecations => { for (var id in _options['silence-deprecation'] as List) - Deprecation.fromId(id) ?? _fail('Invalid deprecation "$id".') + Deprecation.fromId(id) ?? _fail('Invalid deprecation "$id".'), }; /// The set of deprecations that cause errors. @@ -522,13 +586,17 @@ final class ExecutableOptions { // We can't get the version synchronously when running from // source, so we just ignore this check by using a version higher // than any that will ever be used. - var sassVersion = Version.parse(const bool.hasEnvironment('version') - ? const String.fromEnvironment('version') - : '1000.0.0'); + var sassVersion = Version.parse( + const bool.hasEnvironment('version') + ? const String.fromEnvironment('version') + : '1000.0.0', + ); if (argVersion > sassVersion) { - _fail('Invalid version $argVersion. --fatal-deprecation ' - 'requires a version less than or equal to the current ' - 'Dart Sass version.'); + _fail( + 'Invalid version $argVersion. --fatal-deprecation ' + 'requires a version less than or equal to the current ' + 'Dart Sass version.', + ); } deprecations.addAll(Deprecation.forVersion(argVersion)); } on FormatException { @@ -542,7 +610,7 @@ final class ExecutableOptions { /// The set of future deprecations that should emit warnings anyway. Set get futureDeprecations => { for (var id in _options['future-deprecation'] as List) - Deprecation.fromId(id) ?? _fail('Invalid deprecation "$id".') + Deprecation.fromId(id) ?? _fail('Invalid deprecation "$id".'), }; /// Returns the value of [name] in [options] if it was explicitly provided by diff --git a/lib/src/executable/repl.dart b/lib/src/executable/repl.dart index 726279bd8..b3f9cb6a0 100644 --- a/lib/src/executable/repl.dart +++ b/lib/src/executable/repl.dart @@ -24,12 +24,13 @@ import '../visitor/evaluate.dart'; Future repl(ExecutableOptions options) async { var repl = Repl(prompt: '>> '); var trackingLogger = TrackingLogger(options.logger); - var logger = DeprecationProcessingLogger(trackingLogger, - silenceDeprecations: options.silenceDeprecations, - fatalDeprecations: options.fatalDeprecations, - futureDeprecations: options.futureDeprecations, - limitRepetition: !options.verbose) - ..validate(); + var logger = DeprecationProcessingLogger( + trackingLogger, + silenceDeprecations: options.silenceDeprecations, + fatalDeprecations: options.fatalDeprecations, + futureDeprecations: options.futureDeprecations, + limitRepetition: !options.verbose, + )..validate(); void warn(ParseTimeWarning warning) { switch (warning) { @@ -41,10 +42,13 @@ Future repl(ExecutableOptions options) async { } var evaluator = Evaluator( - importer: FilesystemImporter.cwd, - importCache: ImportCache( - importers: options.pkgImporters, loadPaths: options.loadPaths), - logger: logger); + importer: FilesystemImporter.cwd, + importCache: ImportCache( + importers: options.pkgImporters, + loadPaths: options.loadPaths, + ), + logger: logger, + ); await for (String line in repl.runAsync()) { if (line.trim().isEmpty) continue; try { @@ -59,23 +63,38 @@ Future repl(ExecutableOptions options) async { var (node, warnings) = ScssParser(line).parseVariableDeclaration(); warnings.forEach(warn); evaluator.setVariable(node); - print(evaluator.evaluate(VariableExpression(node.name, node.span, - namespace: node.namespace))); + print( + evaluator.evaluate( + VariableExpression(node.name, node.span, namespace: node.namespace), + ), + ); } else { var (node, warnings) = ScssParser(line).parseExpression(); warnings.forEach(warn); print(evaluator.evaluate(node)); } } on SassException catch (error, stackTrace) { - _logError(error, getTrace(error) ?? stackTrace, line, repl, options, - trackingLogger); + _logError( + error, + getTrace(error) ?? stackTrace, + line, + repl, + options, + trackingLogger, + ); } } } /// Logs an error from the interactive shell. -void _logError(SassException error, StackTrace stackTrace, String line, - Repl repl, ExecutableOptions options, TrackingLogger logger) { +void _logError( + SassException error, + StackTrace stackTrace, + String line, + Repl repl, + ExecutableOptions options, + TrackingLogger logger, +) { // If the error doesn't come from the repl line, or if something was logged // after the user's input, just print the error normally. if (error.span.sourceUrl != null || diff --git a/lib/src/executable/watch.dart b/lib/src/executable/watch.dart index 000bf3201..902b2ed46 100644 --- a/lib/src/executable/watch.dart +++ b/lib/src/executable/watch.dart @@ -19,18 +19,20 @@ Future watch(ExecutableOptions options, StylesheetGraph graph) async { var directoriesToWatch = [ ..._sourceDirectoriesToDestinations(options).keys, for (var dir in _sourcesToDestinations(options).keys) p.dirname(dir), - ...options.loadPaths + ...options.loadPaths, ]; var dirWatcher = MultiDirWatcher(poll: options.poll); - await Future.wait(directoriesToWatch.map((dir) { - // If a directory doesn't exist, watch its parent directory so that we're - // notified once it starts existing. - while (!dirExists(dir)) { - dir = p.dirname(dir); - } - return dirWatcher.watch(dir); - })); + await Future.wait( + directoriesToWatch.map((dir) { + // If a directory doesn't exist, watch its parent directory so that we're + // notified once it starts existing. + while (!dirExists(dir)) { + dir = p.dirname(dir); + } + return dirWatcher.watch(dir); + }), + ); // Before we start paying attention to changes, compile all the stylesheets as // they currently exist. This ensures that changes that come in update a @@ -39,11 +41,18 @@ Future watch(ExecutableOptions options, StylesheetGraph graph) async { var sourcesToDestinations = _sourcesToDestinations(options); for (var source in sourcesToDestinations.keys) { graph.addCanonical( - FilesystemImporter.cwd, p.toUri(canonicalize(source)), p.toUri(source), - recanonicalize: false); + FilesystemImporter.cwd, + p.toUri(canonicalize(source)), + p.toUri(source), + recanonicalize: false, + ); } - var success = await compileStylesheets(options, graph, sourcesToDestinations, - ifModified: true); + var success = await compileStylesheets( + options, + graph, + sourcesToDestinations, + ifModified: true, + ); if (!success && options.stopOnError) { dirWatcher.events.listen(null).cancel(); return; @@ -109,8 +118,12 @@ final class _Watcher { var toRecompile = {..._toRecompile}; _toRecompile.clear(); - var success = await compileStylesheets(_options, _graph, toRecompile, - ifModified: true); + var success = await compileStylesheets( + _options, + _graph, + toRecompile, + ifModified: true, + ); if (!success && _options.stopOnError) return; } } @@ -139,7 +152,10 @@ final class _Watcher { var destination = _destinationFor(path); if (destination != null) _toRecompile[path] = destination; var downstream = _graph.addCanonical( - FilesystemImporter.cwd, _canonicalize(path), p.toUri(path)); + FilesystemImporter.cwd, + _canonicalize(path), + p.toUri(path), + ); _recompileDownstream(downstream); } @@ -174,13 +190,13 @@ final class _Watcher { (null, var newType) => newType, (_, ChangeType.REMOVE) => ChangeType.REMOVE, (ChangeType.ADD, _) => ChangeType.ADD, - (_, _) => ChangeType.MODIFY + (_, _) => ChangeType.MODIFY, }; } return [ // PathMap always has nullable keys - for (var (path!, type) in typeForPath.pairs) WatchEvent(type, path) + for (var (path!, type) in typeForPath.pairs) WatchEvent(type, path), ]; }); } @@ -192,7 +208,7 @@ final class _Watcher { while (nodes.isNotEmpty) { nodes = [ for (var node in nodes) - if (seen.add(node)) node + if (seen.add(node)) node, ]; _toRecompile.addAll(_sourceEntrypointsToDestinations(nodes)); @@ -203,7 +219,8 @@ final class _Watcher { /// Returns a sourcesToDestinations mapping for nodes that are entrypoints. Map _sourceEntrypointsToDestinations( - Iterable nodes) { + Iterable nodes, + ) { var entrypoints = {}; for (var node in nodes) { var url = node.canonicalUrl; @@ -231,8 +248,10 @@ final class _Watcher { in _sourceDirectoriesToDestinations(_options).pairs) { if (!p.isWithin(sourceDir, source)) continue; - var destination = p.join(destinationDir, - p.setExtension(p.relative(source, from: sourceDir), '.css')); + var destination = p.join( + destinationDir, + p.setExtension(p.relative(source, from: sourceDir), '.css'), + ); // Don't compile ".css" files to their own locations. if (!p.equals(destination, source)) return destination; @@ -250,5 +269,6 @@ Map _sourcesToDestinations(ExecutableOptions options) => /// Exposes [options.sourcesDirectoriesToDestinations] as a non-nullable map, /// since stdin inputs and stdout outputs aren't allowed in `--watch` mode. Map _sourceDirectoriesToDestinations( - ExecutableOptions options) => + ExecutableOptions options, +) => options.sourceDirectoriesToDestinations.cast(); diff --git a/lib/src/extend/empty_extension_store.dart b/lib/src/extend/empty_extension_store.dart index a00c85d86..de32d55ef 100644 --- a/lib/src/extend/empty_extension_store.dart +++ b/lib/src/extend/empty_extension_store.dart @@ -21,27 +21,38 @@ final class EmptyExtensionStore implements ExtensionStore { const EmptyExtensionStore(); Iterable extensionsWhereTarget( - bool callback(SimpleSelector target)) => + bool callback(SimpleSelector target), + ) => const []; - Box addSelector(SelectorList selector, - [List? mediaContext]) { + Box addSelector( + SelectorList selector, [ + List? mediaContext, + ]) { throw UnsupportedError( - "addSelector() can't be called for a const ExtensionStore."); + "addSelector() can't be called for a const ExtensionStore.", + ); } void addExtension( - SelectorList extender, SimpleSelector target, ExtendRule extend, - [List? mediaContext]) { + SelectorList extender, + SimpleSelector target, + ExtendRule extend, [ + List? mediaContext, + ]) { throw UnsupportedError( - "addExtension() can't be called for a const ExtensionStore."); + "addExtension() can't be called for a const ExtensionStore.", + ); } void addExtensions(Iterable extenders) { throw UnsupportedError( - "addExtensions() can't be called for a const ExtensionStore."); + "addExtensions() can't be called for a const ExtensionStore.", + ); } - (ExtensionStore, Map>) clone() => - const (EmptyExtensionStore(), {}); + (ExtensionStore, Map>) clone() => const ( + EmptyExtensionStore(), + {}, + ); } diff --git a/lib/src/extend/extension.dart b/lib/src/extend/extension.dart index 87f0995d7..dca487dbd 100644 --- a/lib/src/extend/extension.dart +++ b/lib/src/extend/extension.dart @@ -34,16 +34,24 @@ class Extension { final FileSpan span; /// Creates a new extension. - Extension(ComplexSelector extender, this.target, this.span, - {this.mediaContext, bool optional = false}) - : extender = Extender(extender), + Extension( + ComplexSelector extender, + this.target, + this.span, { + this.mediaContext, + bool optional = false, + }) : extender = Extender(extender), isOptional = optional { this.extender._extension = this; } - Extension withExtender(ComplexSelector newExtender) => - Extension(newExtender, target, span, - mediaContext: mediaContext, optional: isOptional); + Extension withExtender(ComplexSelector newExtender) => Extension( + newExtender, + target, + span, + mediaContext: mediaContext, + optional: isOptional, + ); String toString() => "$extender {@extend $target${isOptional ? ' !optional' : ''}}"; @@ -90,7 +98,9 @@ final class Extender { } throw SassException( - "You may not @extend selectors across media queries.", extension.span); + "You may not @extend selectors across media queries.", + extension.span, + ); } String toString() => selector.toString(); diff --git a/lib/src/extend/extension_store.dart b/lib/src/extend/extension_store.dart index 5f760005d..379e45d73 100644 --- a/lib/src/extend/extension_store.dart +++ b/lib/src/extend/extension_store.dart @@ -79,22 +79,31 @@ class ExtensionStore { /// This works as though `source {@extend target}` were written in the /// stylesheet, with the exception that [target] can contain compound /// selectors which must be extended as a unit. - static SelectorList extend(SelectorList selector, SelectorList source, - SelectorList targets, FileSpan span) => + static SelectorList extend( + SelectorList selector, + SelectorList source, + SelectorList targets, + FileSpan span, + ) => _extendOrReplace(selector, source, targets, ExtendMode.allTargets, span); /// Returns a copy of [selector] with [targets] replaced by [source]. - static SelectorList replace(SelectorList selector, SelectorList source, - SelectorList targets, FileSpan span) => + static SelectorList replace( + SelectorList selector, + SelectorList source, + SelectorList targets, + FileSpan span, + ) => _extendOrReplace(selector, source, targets, ExtendMode.replace, span); /// A helper function for [extend] and [replace]. static SelectorList _extendOrReplace( - SelectorList selector, - SelectorList source, - SelectorList targets, - ExtendMode mode, - FileSpan span) { + SelectorList selector, + SelectorList source, + SelectorList targets, + ExtendMode mode, + FileSpan span, + ) { var extender = ExtensionStore._mode(mode); if (!selector.isInvisible) extender._originals.addAll(selector.components); @@ -108,8 +117,8 @@ class ExtensionStore { for (var simple in compound.components) simple: { for (var complex in source.components) - complex: Extension(complex, simple, span, optional: true) - } + complex: Extension(complex, simple, span, optional: true), + }, }); } @@ -133,13 +142,13 @@ class ExtensionStore { _originals = Set.identity(); ExtensionStore._( - this._selectors, - this._extensions, - this._extensionsByExtender, - this._mediaContexts, - this._sourceSpecificity, - this._originals) - : _mode = ExtendMode.normal; + this._selectors, + this._extensions, + this._extensionsByExtender, + this._mediaContexts, + this._sourceSpecificity, + this._originals, + ) : _mode = ExtendMode.normal; /// Returns all mandatory extensions in this extender for whose targets /// [callback] returns `true`. @@ -147,14 +156,15 @@ class ExtensionStore { /// This un-merges any [MergedExtension] so only base [Extension]s are /// returned. Iterable extensionsWhereTarget( - bool callback(SimpleSelector target)) sync* { + bool callback(SimpleSelector target), + ) sync* { for (var (simple, sources) in _extensions.pairs) { if (!callback(simple)) continue; for (var extension in sources.values) { if (extension is MergedExtension) { - yield* extension - .unmerge() - .where((extension) => !extension.isOptional); + yield* extension.unmerge().where( + (extension) => !extension.isOptional, + ); } else if (!extension.isOptional) { yield extension; } @@ -170,8 +180,10 @@ class ExtensionStore { /// /// The [mediaContext] is the media query context in which the selector was /// defined, or `null` if it was defined at the top level of the document. - Box addSelector(SelectorList selector, - [List? mediaContext]) { + Box addSelector( + SelectorList selector, [ + List? mediaContext, + ]) { var originalSelector = selector; if (!originalSelector.isInvisible) { _originals.addAll(originalSelector.components); @@ -182,12 +194,14 @@ class ExtensionStore { selector = _extendList(originalSelector, _extensions, mediaContext); } on SassException catch (error, stackTrace) { throwWithTrace( - SassException( - "From ${error.span.message('')}\n" - "${error.message}", - error.span), - error, - stackTrace); + SassException( + "From ${error.span.message('')}\n" + "${error.message}", + error.span, + ), + error, + stackTrace, + ); } } @@ -201,7 +215,9 @@ class ExtensionStore { /// Registers the [SimpleSelector]s in [list] to point to [selector] in /// [_selectors]. void _registerSelector( - SelectorList list, ModifiableBox selector) { + SelectorList list, + ModifiableBox selector, + ) { for (var complex in list.components) { for (var component in complex.components) { for (var simple in component.selector.components) { @@ -224,8 +240,11 @@ class ExtensionStore { /// is defined. It can only extend selectors within the same context. A `null` /// context indicates no media queries. void addExtension( - SelectorList extender, SimpleSelector target, ExtendRule extend, - [List? mediaContext]) { + SelectorList extender, + SimpleSelector target, + ExtendRule extend, [ + List? mediaContext, + ]) { var selectors = _selectors[target]; var existingExtensions = _extensionsByExtender[target]; @@ -234,8 +253,13 @@ class ExtensionStore { for (var complex in extender.components) { if (complex.isUseless) continue; - var extension = Extension(complex, target, extend.span, - mediaContext: mediaContext, optional: extend.isOptional); + var extension = Extension( + complex, + target, + extend.span, + mediaContext: mediaContext, + optional: extend.isOptional, + ); if (sources[complex] case var existingExtension?) { // If there's already an extend from [extender] to [target], we don't need @@ -263,8 +287,10 @@ class ExtensionStore { var newExtensionsByTarget = {target: newExtensions}; if (existingExtensions != null) { - var additionalExtensions = - _extendExistingExtensions(existingExtensions, newExtensionsByTarget); + var additionalExtensions = _extendExistingExtensions( + existingExtensions, + newExtensionsByTarget, + ); if (additionalExtensions != null) { mapAddAll2(newExtensionsByTarget, additionalExtensions); } @@ -306,8 +332,10 @@ class ExtensionStore { /// /// Returns `null` if there are no extensions to add. Map>? - _extendExistingExtensions(List extensions, - Map> newExtensions) { + _extendExistingExtensions( + List extensions, + Map> newExtensions, + ) { Map>? additionalExtensions; for (var extension in extensions.toList()) { @@ -316,14 +344,20 @@ class ExtensionStore { Iterable? selectors; try { selectors = _extendComplex( - extension.extender.selector, newExtensions, extension.mediaContext); + extension.extender.selector, + newExtensions, + extension.mediaContext, + ); if (selectors == null) continue; } on SassException catch (error, stackTrace) { throwWithTrace( - error.withAdditionalSpan( - extension.extender.selector.span, "target selector"), - error, - stackTrace); + error.withAdditionalSpan( + extension.extender.selector.span, + "target selector", + ), + error, + stackTrace, + ); } // If the output contains the original complex selector, there's no need @@ -334,8 +368,10 @@ class ExtensionStore { for (var complex in selectors) { var withExtender = extension.withExtender(complex); if (sources[complex] case var existingExtension?) { - sources[complex] = - MergedExtension.merge(existingExtension, withExtender); + sources[complex] = MergedExtension.merge( + existingExtension, + withExtender, + ); } else { sources[complex] = withExtender; @@ -349,8 +385,10 @@ class ExtensionStore { if (newExtensions.containsKey(extension.target)) { additionalExtensions ??= {}; - var additionalSources = - additionalExtensions.putIfAbsent(extension.target, () => {}); + var additionalSources = additionalExtensions.putIfAbsent( + extension.target, + () => {}, + ); additionalSources[complex] = withExtender; } } @@ -361,22 +399,29 @@ class ExtensionStore { } /// Extend [extensions] using [newExtensions]. - void _extendExistingSelectors(Set> selectors, - Map> newExtensions) { + void _extendExistingSelectors( + Set> selectors, + Map> newExtensions, + ) { for (var selector in selectors) { var oldValue = selector.value; try { selector.value = _extendList( - selector.value, newExtensions, _mediaContexts[selector]); + selector.value, + newExtensions, + _mediaContexts[selector], + ); } on SassException catch (error, stackTrace) { // TODO(nweiz): Make this a MultiSpanSassException. throwWithTrace( - SassException( - "From ${selector.value.span.message('')}\n" - "${error.message}", - error.span), - error, - stackTrace); + SassException( + "From ${selector.value.span.message('')}\n" + "${error.message}", + error.span, + ), + error, + stackTrace, + ); } // If no extends actually happened (for example because unification @@ -426,7 +471,10 @@ class ExtensionStore { if (_extensions[target] case var existingSources?) { for (var (extender, extension) in newSources.pairs) { extension = existingSources.putOrMerge( - extender, extension, MergedExtension.merge); + extender, + extension, + MergedExtension.merge, + ); if (extensionsForTarget != null || selectorsForTarget != null) { (newExtensions ??= {}).putIfAbsent(target, () => {})[extender] = @@ -456,9 +504,11 @@ class ExtensionStore { } /// Extends [list] using [extensions]. - SelectorList _extendList(SelectorList list, - Map> extensions, - [List? mediaQueryContext]) { + SelectorList _extendList( + SelectorList list, + Map> extensions, [ + List? mediaQueryContext, + ]) { // This could be written more simply using [List.map], but we want to avoid // any allocations in the common case where no extends apply. List? extended; @@ -466,9 +516,10 @@ class ExtensionStore { var complex = list.components[i]; var result = _extendComplex(complex, extensions, mediaQueryContext); assert( - result?.isNotEmpty ?? true, - '_extendComplex($complex) should return null rather than [] if ' - 'extension fails'); + result?.isNotEmpty ?? true, + '_extendComplex($complex) should return null rather than [] if ' + 'extension fails', + ); if (result == null) { extended?.add(complex); } else { @@ -484,9 +535,10 @@ class ExtensionStore { /// Extends [complex] using [extensions], and returns the contents of a /// [SelectorList]. List? _extendComplex( - ComplexSelector complex, - Map> extensions, - List? mediaQueryContext) { + ComplexSelector complex, + Map> extensions, + List? mediaQueryContext, + ) { if (complex.leadingCombinators.length > 1) return null; // The complex selectors that each compound selector in [complex.components] @@ -510,27 +562,39 @@ class ExtensionStore { var isOriginal = _originals.contains(complex); for (var i = 0; i < complex.components.length; i++) { var component = complex.components[i]; - var extended = _extendCompound(component, extensions, mediaQueryContext, - inOriginal: isOriginal); + var extended = _extendCompound( + component, + extensions, + mediaQueryContext, + inOriginal: isOriginal, + ); assert( - extended?.isNotEmpty ?? true, - '_extendCompound($component) should return null rather than [] if ' - 'extension fails'); + extended?.isNotEmpty ?? true, + '_extendCompound($component) should return null rather than [] if ' + 'extension fails', + ); if (extended == null) { extendedNotExpanded?.add([ - ComplexSelector(const [], [component], complex.span, - lineBreak: complex.lineBreak) + ComplexSelector( + const [], + [component], + complex.span, + lineBreak: complex.lineBreak, + ), ]); } else if (extendedNotExpanded != null) { extendedNotExpanded.add(extended); } else if (i != 0) { extendedNotExpanded = [ [ - ComplexSelector(complex.leadingCombinators, - complex.components.take(i), complex.span, - lineBreak: complex.lineBreak) + ComplexSelector( + complex.leadingCombinators, + complex.components.take(i), + complex.span, + lineBreak: complex.lineBreak, + ), ], - extended + extended, ]; } else if (complex.leadingCombinators.isEmpty) { extendedNotExpanded = [extended]; @@ -539,12 +603,17 @@ class ExtensionStore { [ for (var newComplex in extended) if (newComplex.leadingCombinators.isEmpty || - listEquals(complex.leadingCombinators, - newComplex.leadingCombinators)) - ComplexSelector(complex.leadingCombinators, - newComplex.components, complex.span, - lineBreak: complex.lineBreak || newComplex.lineBreak) - ] + listEquals( + complex.leadingCombinators, + newComplex.leadingCombinators, + )) + ComplexSelector( + complex.leadingCombinators, + newComplex.components, + complex.span, + lineBreak: complex.lineBreak || newComplex.lineBreak, + ), + ], ]; } } @@ -552,8 +621,9 @@ class ExtensionStore { var first = true; return paths(extendedNotExpanded).expand((path) { - return weave(path, complex.span, forceLineBreak: complex.lineBreak) - .map((outputComplex) { + return weave(path, complex.span, forceLineBreak: complex.lineBreak).map(( + outputComplex, + ) { // Make sure that copies of [complex] retain their status as "original" // selectors. This includes selectors that are modified because a :not() // was extended into. @@ -576,10 +646,11 @@ class ExtensionStore { /// The [lineBreak] parameter indicates whether [component] appears in a /// complex selector with a line break. List? _extendCompound( - ComplexSelectorComponent component, - Map> extensions, - List? mediaQueryContext, - {required bool inOriginal}) { + ComplexSelectorComponent component, + Map> extensions, + List? mediaQueryContext, { + required bool inOriginal, + }) { // If there's more than one target and they all need to match, we track // which targets are actually extended. var targetsUsed = _mode == ExtendMode.normal || extensions.length < 2 @@ -592,20 +663,26 @@ class ExtensionStore { List>? options; for (var i = 0; i < simples.length; i++) { var simple = simples[i]; - var extended = - _extendSimple(simple, extensions, mediaQueryContext, targetsUsed); + var extended = _extendSimple( + simple, + extensions, + mediaQueryContext, + targetsUsed, + ); assert( - extended?.isNotEmpty ?? true, - '_extendSimple($simple) should return null rather than [] if ' - 'extension fails'); + extended?.isNotEmpty ?? true, + '_extendSimple($simple) should return null rather than [] if ' + 'extension fails', + ); if (extended == null) { options?.add([_extenderForSimple(simple)]); } else { if (options == null) { options = []; if (i != 0) { - options - .add([_extenderForCompound(simples.take(i), component.span)]); + options.add([ + _extenderForCompound(simples.take(i), component.span), + ]); } } @@ -626,8 +703,9 @@ class ExtensionStore { List? result; for (var extender in extenders) { extender.assertCompatibleMediaContext(mediaQueryContext); - var complex = - extender.selector.withAdditionalCombinators(component.combinators); + var complex = extender.selector.withAdditionalCombinators( + component.combinators, + ); if (complex.isUseless) continue; result ??= []; result.add(complex); @@ -677,13 +755,17 @@ class ExtensionStore { // don't have to do any unification. ComplexSelector(const [], [ ComplexSelectorComponent( - CompoundSelector(extenderPaths.first.expand((extender) { + CompoundSelector( + extenderPaths.first.expand((extender) { assert(extender.selector.components.length == 1); return extender.selector.components.last.selector.components; - }), component.selector.span), - component.combinators, - component.span) - ], component.span) + }), + component.selector.span, + ), + component.combinators, + component.span, + ), + ], component.span), ]; for (var path in extenderPaths.skip(_mode == ExtendMode.replace ? 0 : 1)) { @@ -691,8 +773,9 @@ class ExtensionStore { if (extended == null) continue; for (var complex in extended) { - var withCombinators = - complex.withAdditionalCombinators(component.combinators); + var withCombinators = complex.withAdditionalCombinators( + component.combinators, + ); if (!withCombinators.isUseless) result.add(withCombinators); } } @@ -712,8 +795,11 @@ class ExtensionStore { /// elements matched by all of [extenders]' selectors. /// /// The [span] will be used for the new selectors. - List? _unifyExtenders(List extenders, - List? mediaQueryContext, FileSpan span) { + List? _unifyExtenders( + List extenders, + List? mediaQueryContext, + FileSpan span, + ) { var toUnify = QueueList(); List? originals; var originalsLineBreak = false; @@ -732,10 +818,20 @@ class ExtensionStore { } if (originals != null) { - toUnify.addFirst(ComplexSelector(const [], [ - ComplexSelectorComponent( - CompoundSelector(originals, span), const [], span) - ], span, lineBreak: originalsLineBreak)); + toUnify.addFirst( + ComplexSelector( + const [], + [ + ComplexSelectorComponent( + CompoundSelector(originals, span), + const [], + span, + ), + ], + span, + lineBreak: originalsLineBreak, + ), + ); } var complexes = unifyComplex(toUnify, span); @@ -754,10 +850,11 @@ class ExtensionStore { /// Each element of the returned iterable is a list of choices, which will be /// combined using [paths]. Iterable>? _extendSimple( - SimpleSelector simple, - Map> extensions, - List? mediaQueryContext, - Set? targetsUsed) { + SimpleSelector simple, + Map> extensions, + List? mediaQueryContext, + Set? targetsUsed, + ) { // Extends [simple] without extending the contents of any selector pseudos // it contains. List? withoutPseudo(SimpleSelector simple) { @@ -767,7 +864,7 @@ class ExtensionStore { return [ if (_mode != ExtendMode.replace) _extenderForSimple(simple), - for (var extension in extensionsForSimple.values) extension.extender + for (var extension in extensionsForSimple.values) extension.extender, ]; } @@ -775,7 +872,8 @@ class ExtensionStore { if (_extendPseudo(simple, extensions, mediaQueryContext) case var extended?) { return extended.map( - (pseudo) => withoutPseudo(pseudo) ?? [_extenderForSimple(pseudo)]); + (pseudo) => withoutPseudo(pseudo) ?? [_extenderForSimple(pseudo)], + ); } } @@ -785,32 +883,41 @@ class ExtensionStore { /// Returns an [Extender] composed solely of a compound selector containing /// [simples]. Extender _extenderForCompound( - Iterable simples, FileSpan span) { + Iterable simples, + FileSpan span, + ) { var compound = CompoundSelector(simples, span); return Extender( - ComplexSelector(const [], - [ComplexSelectorComponent(compound, const [], span)], span), - specificity: _sourceSpecificityFor(compound), - original: true); + ComplexSelector(const [], [ + ComplexSelectorComponent(compound, const [], span), + ], span), + specificity: _sourceSpecificityFor(compound), + original: true, + ); } /// Returns an [Extender] composed solely of [simple]. Extender _extenderForSimple(SimpleSelector simple) => Extender( - ComplexSelector(const [], [ - ComplexSelectorComponent( - CompoundSelector([simple], simple.span), const [], simple.span) - ], simple.span), - specificity: _sourceSpecificity[simple] ?? 0, - original: true); + ComplexSelector(const [], [ + ComplexSelectorComponent( + CompoundSelector([simple], simple.span), + const [], + simple.span, + ), + ], simple.span), + specificity: _sourceSpecificity[simple] ?? 0, + original: true, + ); /// Extends [pseudo] using [extensions], and returns a list of resulting /// pseudo selectors. /// /// This requires that [pseudo] have a selector argument. List? _extendPseudo( - PseudoSelector pseudo, - Map> extensions, - List? mediaQueryContext) { + PseudoSelector pseudo, + Map> extensions, + List? mediaQueryContext, + ) { var selector = pseudo.selector; if (selector == null) { throw ArgumentError("Selector $pseudo must have a selector argument."); @@ -828,8 +935,9 @@ class ExtensionStore { if (pseudo.normalizedName == "not" && !selector.components.any((complex) => complex.components.length > 1) && extended.components.any((complex) => complex.components.length == 1)) { - complexes = extended.components - .where((complex) => complex.components.length <= 1); + complexes = extended.components.where( + (complex) => complex.components.length <= 1, + ); } complexes = complexes.expand((complex) { @@ -846,8 +954,11 @@ class ExtensionStore { // become `.foo:not(.bar)`. However, this is a narrow edge case and // supporting it properly would make this code and the code calling it // a lot more complicated, so it's not supported for now. - if (!const {'is', 'matches', 'where'} - .contains(innerPseudo.normalizedName)) { + if (!const { + 'is', + 'matches', + 'where', + }.contains(innerPseudo.normalizedName)) { return []; } return innerSelector.components; @@ -885,8 +996,10 @@ class ExtensionStore { // unless it originally contained a selector list. if (pseudo.normalizedName == 'not' && selector.components.length == 1) { var result = complexes - .map((complex) => - pseudo.withSelector(SelectorList([complex], selector.span))) + .map( + (complex) => + pseudo.withSelector(SelectorList([complex], selector.span)), + ) .toList(); return result.isEmpty ? null : result; } else { @@ -899,8 +1012,10 @@ class ExtensionStore { // // The [isOriginal] callback indicates which selectors are original to the // document, and thus should never be trimmed. - List _trim(List selectors, - bool isOriginal(ComplexSelector complex)) { + List _trim( + List selectors, + bool isOriginal(ComplexSelector complex), + ) { // Avoid truly horrific quadratic behavior. // // TODO(nweiz): I think there may be a way to get perfect trimming without @@ -938,23 +1053,29 @@ class ExtensionStore { // greater or equal to this. var maxSpecificity = 0; for (var component in complex1.components) { - maxSpecificity = - math.max(maxSpecificity, _sourceSpecificityFor(component.selector)); + maxSpecificity = math.max( + maxSpecificity, + _sourceSpecificityFor(component.selector), + ); } // Look in [result] rather than [selectors] for selectors after [i]. This // ensures that we aren't comparing against a selector that's already been // trimmed, and thus that if there are two identical selectors only one is // trimmed. - if (result.any((complex2) => - complex2.specificity >= maxSpecificity && - complex2.isSuperselector(complex1))) { + if (result.any( + (complex2) => + complex2.specificity >= maxSpecificity && + complex2.isSuperselector(complex1), + )) { continue; } - if (selectors.take(i).any((complex2) => - complex2.specificity >= maxSpecificity && - complex2.isSuperselector(complex1))) { + if (selectors.take(i).any( + (complex2) => + complex2.specificity >= maxSpecificity && + complex2.isSuperselector(complex1), + )) { continue; } @@ -998,13 +1119,14 @@ class ExtensionStore { return ( ExtensionStore._( - newSelectors, - copyMapOfMap(_extensions), - copyMapOfList(_extensionsByExtender), - newMediaContexts, - Map.identity()..addAll(_sourceSpecificity), - Set.identity()..addAll(_originals)), - oldToNewSelectors + newSelectors, + copyMapOfMap(_extensions), + copyMapOfList(_extensionsByExtender), + newMediaContexts, + Map.identity()..addAll(_sourceSpecificity), + Set.identity()..addAll(_originals), + ), + oldToNewSelectors, ); } } diff --git a/lib/src/extend/functions.dart b/lib/src/extend/functions.dart index 190bf4429..767f75c83 100644 --- a/lib/src/extend/functions.dart +++ b/lib/src/extend/functions.dart @@ -33,7 +33,9 @@ final _rootishPseudoClasses = {'root', 'scope', 'host', 'host-context'}; /// /// If no such list can be produced, returns `null`. List? unifyComplex( - List complexes, FileSpan span) { + List complexes, + FileSpan span, +) { if (complexes.length == 1) return complexes; CompoundSelector? unifiedBase; @@ -45,7 +47,7 @@ List? unifyComplex( if (complex case ComplexSelector( components: [_], - leadingCombinators: [var newLeadingCombinator] + leadingCombinators: [var newLeadingCombinator], )) { if (leadingCombinator == null) { leadingCombinator = newLeadingCombinator; @@ -57,7 +59,7 @@ List? unifyComplex( var base = complex.components.last; if (base case ComplexSelectorComponent( - combinators: [var newTrailingCombinator] + combinators: [var newTrailingCombinator], )) { if (trailingCombinator != null && trailingCombinator != newTrailingCombinator) { @@ -77,28 +79,36 @@ List? unifyComplex( var withoutBases = [ for (var complex in complexes) if (complex.components.length > 1) - ComplexSelector(complex.leadingCombinators, - complex.components.exceptLast, complex.span, - lineBreak: complex.lineBreak), + ComplexSelector( + complex.leadingCombinators, + complex.components.exceptLast, + complex.span, + lineBreak: complex.lineBreak, + ), ]; var base = ComplexSelector( - leadingCombinator == null ? const [] : [leadingCombinator], - [ - ComplexSelectorComponent(unifiedBase!, - trailingCombinator == null ? const [] : [trailingCombinator], span) - ], - span, - lineBreak: complexes.any((complex) => complex.lineBreak)); + leadingCombinator == null ? const [] : [leadingCombinator], + [ + ComplexSelectorComponent( + unifiedBase!, + trailingCombinator == null ? const [] : [trailingCombinator], + span, + ), + ], + span, + lineBreak: complexes.any((complex) => complex.lineBreak), + ); return weave( - withoutBases.isEmpty - ? [base] - : [ - ...withoutBases.exceptLast, - withoutBases.last.concatenate(base, span) - ], - span); + withoutBases.isEmpty + ? [base] + : [ + ...withoutBases.exceptLast, + withoutBases.last.concatenate(base, span), + ], + span, + ); } /// Returns a [CompoundSelector] that matches only elements that are matched by @@ -121,7 +131,9 @@ List? unifyComplex( /// /// If no such selector can be produced, returns `null`. CompoundSelector? unifyCompound( - CompoundSelector compound1, CompoundSelector compound2) { + CompoundSelector compound1, + CompoundSelector compound2, +) { var result = compound1.components; var pseudoResult = []; var pseudoElementFound = false; @@ -152,7 +164,9 @@ CompoundSelector? unifyCompound( /// /// If no such selector can be produced, returns `null`. SimpleSelector? unifyUniversalAndElement( - SimpleSelector selector1, SimpleSelector selector2) { + SimpleSelector selector1, + SimpleSelector selector2, +) { var (namespace1, name1) = _namespaceAndName(selector1, 'selector1'); var (namespace2, name2) = _namespaceAndName(selector2, 'selector2'); @@ -184,15 +198,20 @@ SimpleSelector? unifyUniversalAndElement( /// /// The [name] parameter is used for error reporting. (String? namespace, String? name) _namespaceAndName( - SimpleSelector selector, String name) => + SimpleSelector selector, + String name, +) => switch (selector) { UniversalSelector(:var namespace) => (namespace, null), TypeSelector(name: QualifiedName(:var name, :var namespace)) => ( namespace, - name + name, ), _ => throw ArgumentError.value( - selector, name, 'must be a UniversalSelector or a TypeSelector') + selector, + name, + 'must be a UniversalSelector or a TypeSelector', + ), }; /// Expands "parenthesized selectors" in [complexes]. @@ -209,14 +228,20 @@ SimpleSelector? unifyUniversalAndElement( /// /// If [forceLineBreak] is `true`, this will mark all returned complex selectors /// as having line breaks. -List weave(List complexes, FileSpan span, - {bool forceLineBreak = false}) { +List weave( + List complexes, + FileSpan span, { + bool forceLineBreak = false, +}) { if (complexes case [var complex]) { if (!forceLineBreak || complex.lineBreak) return complexes; return [ ComplexSelector( - complex.leadingCombinators, complex.components, complex.span, - lineBreak: true) + complex.leadingCombinators, + complex.components, + complex.span, + lineBreak: true, + ), ]; } @@ -224,8 +249,11 @@ List weave(List complexes, FileSpan span, for (var complex in complexes.skip(1)) { if (complex.components.length == 1) { for (var i = 0; i < prefixes.length; i++) { - prefixes[i] = prefixes[i] - .concatenate(complex, span, forceLineBreak: forceLineBreak); + prefixes[i] = prefixes[i].concatenate( + complex, + span, + forceLineBreak: forceLineBreak, + ); } continue; } @@ -234,8 +262,11 @@ List weave(List complexes, FileSpan span, for (var prefix in prefixes) for (var parentPrefix in _weaveParents(prefix, complex, span) ?? const []) - parentPrefix.withAdditionalComponent(complex.components.last, span, - forceLineBreak: forceLineBreak), + parentPrefix.withAdditionalComponent( + complex.components.last, + span, + forceLineBreak: forceLineBreak, + ), ]; } @@ -262,9 +293,14 @@ List weave(List complexes, FileSpan span, /// /// Returns `null` if this intersection is empty. Iterable? _weaveParents( - ComplexSelector prefix, ComplexSelector base, FileSpan span) { + ComplexSelector prefix, + ComplexSelector base, + FileSpan span, +) { var leadingCombinators = _mergeLeadingCombinators( - prefix.leadingCombinators, base.leadingCombinators); + prefix.leadingCombinators, + base.leadingCombinators, + ); if (leadingCombinators == null) return null; // Make queues of _only_ the parent selectors. The prefix only contains @@ -282,10 +318,12 @@ Iterable? _weaveParents( case (var rootish1?, var rootish2?): var rootish = unifyCompound(rootish1.selector, rootish2.selector); if (rootish == null) return null; - queue1.addFirst(ComplexSelectorComponent( - rootish, rootish1.combinators, rootish1.span)); - queue2.addFirst(ComplexSelectorComponent( - rootish, rootish2.combinators, rootish1.span)); + queue1.addFirst( + ComplexSelectorComponent(rootish, rootish1.combinators, rootish1.span), + ); + queue2.addFirst( + ComplexSelectorComponent(rootish, rootish2.combinators, rootish1.span), + ); case (var rootish?, null): case (null, var rootish?): @@ -299,27 +337,31 @@ Iterable? _weaveParents( var groups1 = _groupSelectors(queue1); var groups2 = _groupSelectors(queue2); var lcs = longestCommonSubsequence>( - groups2, groups1, select: (group1, group2) { - if (listEquals(group1, group2)) return group1; - if (_complexIsParentSuperselector(group1, group2)) return group2; - if (_complexIsParentSuperselector(group2, group1)) return group1; - if (!_mustUnify(group1, group2)) return null; - - var unified = unifyComplex([ - ComplexSelector(const [], group1, span), - ComplexSelector(const [], group2, span) - ], span); - return unified?.singleOrNull?.components; - }); + groups2, + groups1, + select: (group1, group2) { + if (listEquals(group1, group2)) return group1; + if (_complexIsParentSuperselector(group1, group2)) return group2; + if (_complexIsParentSuperselector(group2, group1)) return group1; + if (!_mustUnify(group1, group2)) return null; + + var unified = unifyComplex([ + ComplexSelector(const [], group1, span), + ComplexSelector(const [], group2, span), + ], span); + return unified?.singleOrNull?.components; + }, + ); var choices = >>[]; for (var group in lcs) { choices.add([ for (var chunk in _chunks>( - groups1, - groups2, - (sequence) => _complexIsParentSuperselector(sequence.first, group))) - [for (var components in chunk) ...components] + groups1, + groups2, + (sequence) => _complexIsParentSuperselector(sequence.first, group), + )) + [for (var components in chunk) ...components], ]); choices.add([group]); groups1.removeFirst(); @@ -327,15 +369,18 @@ Iterable? _weaveParents( } choices.add([ for (var chunk in _chunks(groups1, groups2, (sequence) => sequence.isEmpty)) - [for (var components in chunk) ...components] + [for (var components in chunk) ...components], ]); choices.addAll(trailingCombinators); return [ for (var path in paths(choices.where((choice) => choice.isNotEmpty))) - ComplexSelector(leadingCombinators, - [for (var components in path) ...components], span, - lineBreak: prefix.lineBreak || base.lineBreak) + ComplexSelector( + leadingCombinators, + [for (var components in path) ...components], + span, + lineBreak: prefix.lineBreak || base.lineBreak, + ), ]; } @@ -343,11 +388,15 @@ Iterable? _weaveParents( /// appear in a complex selector's first component, removes and returns that /// element. ComplexSelectorComponent? _firstIfRootish( - QueueList queue) { + QueueList queue, +) { if (queue case [var first, ...]) { for (var simple in first.selector.components) { - if (simple case PseudoSelector(isClass: true, :var normalizedName) - when _rootishPseudoClasses.contains(normalizedName)) { + if (simple + case PseudoSelector( + isClass: true, + :var normalizedName, + ) when _rootishPseudoClasses.contains(normalizedName)) { queue.removeFirst(); return first; } @@ -362,14 +411,15 @@ ComplexSelectorComponent? _firstIfRootish( /// /// Returns `null` if the combinator lists can't be unified. List>? _mergeLeadingCombinators( - List>? combinators1, - List>? combinators2) => - // Allow null arguments just to make calls to `Iterable.reduce()` easier. + List>? combinators1, + List>? combinators2, +) => +// Allow null arguments just to make calls to `Iterable.reduce()` easier. switch ((combinators1, combinators2)) { (null, _) || (_, null) => null, (List(length: > 1), _) || (_, List(length: > 1)) => null, ([], var combinators) || (var combinators, []) => combinators, - _ => listEquals(combinators1, combinators2) ? combinators1 : null + _ => listEquals(combinators1, combinators2) ? combinators1 : null, }; /// Extracts trailing [ComplexSelectorComponent]s with trailing combinators from @@ -386,19 +436,20 @@ List>? _mergeLeadingCombinators( /// /// The [span] will be used for any new combined selectors. List>>? _mergeTrailingCombinators( - QueueList components1, - QueueList components2, - FileSpan span, - [QueueList>>? result]) { + QueueList components1, + QueueList components2, + FileSpan span, [ + QueueList>>? result, +]) { result ??= QueueList(); var combinators1 = switch (components1) { [..., var last] => last.combinators, - _ => const >[] + _ => const >[], }; var combinators2 = switch (components2) { [..., var last] => last.combinators, - _ => const >[] + _ => const >[], }; if (combinators1.isEmpty && combinators2.isEmpty) return result; if (combinators1.length > 1 || combinators2.length > 1) return null; @@ -411,29 +462,29 @@ List>>? _mergeTrailingCombinators( // Include the component lists in the pattern match so we can easily // generalize cases across different orderings of the two combinators. components1, - components2 + components2, )) { case (Combinator.followingSibling, Combinator.followingSibling, _, _): var component1 = components1.removeLast(); var component2 = components2.removeLast(); if (component1.selector.isSuperselector(component2.selector)) { result.addFirst([ - [component2] + [component2], ]); } else if (component2.selector.isSuperselector(component1.selector)) { result.addFirst([ - [component1] + [component1], ]); } else { var choices = [ [component1, component2], - [component2, component1] + [component2, component1], ]; if (unifyCompound(component1.selector, component2.selector) case var unified?) { choices.add([ - ComplexSelectorComponent(unified, [combinators1.first], span) + ComplexSelectorComponent(unified, [combinators1.first], span), ]); } @@ -444,26 +495,26 @@ List>>? _mergeTrailingCombinators( Combinator.followingSibling, Combinator.nextSibling, var followingComponents, - var nextComponents + var nextComponents, ) || ( Combinator.nextSibling, Combinator.followingSibling, var nextComponents, - var followingComponents + var followingComponents, ): var next = nextComponents.removeLast(); var following = followingComponents.removeLast(); if (following.selector.isSuperselector(next.selector)) { result.addFirst([ - [next] + [next], ]); } else { result.addFirst([ [following, next], if (unifyCompound(following.selector, next.selector) case var unified?) - [ComplexSelectorComponent(unified, next.combinators, span)] + [ComplexSelectorComponent(unified, next.combinators, span)], ]); } @@ -471,49 +522,52 @@ List>>? _mergeTrailingCombinators( Combinator.child, Combinator.nextSibling || Combinator.followingSibling, _, - var siblingComponents + var siblingComponents, ): case ( Combinator.nextSibling || Combinator.followingSibling, Combinator.child, var siblingComponents, - _ + _, ): result.addFirst([ - [siblingComponents.removeLast()] + [siblingComponents.removeLast()], ]); case (var combinator1?, var combinator2?, _, _) when combinator1 == combinator2: var unified = unifyCompound( - components1.removeLast().selector, components2.removeLast().selector); + components1.removeLast().selector, + components2.removeLast().selector, + ); if (unified == null) return null; result.addFirst([ [ - ComplexSelectorComponent(unified, [combinators1.first], span) - ] + ComplexSelectorComponent(unified, [combinators1.first], span), + ], ]); case ( var combinator?, null, var combinatorComponents, - var descendantComponents + var descendantComponents, ): case ( null, var combinator?, var descendantComponents, - var combinatorComponents + var combinatorComponents, ): if (combinator == Combinator.child && - (descendantComponents.lastOrNull?.selector - .isSuperselector(combinatorComponents.last.selector) ?? + (descendantComponents.lastOrNull?.selector.isSuperselector( + combinatorComponents.last.selector, + ) ?? false)) { descendantComponents.removeLast(); } result.addFirst([ - [combinatorComponents.removeLast()] + [combinatorComponents.removeLast()], ]); case _: @@ -528,16 +582,21 @@ List>>? _mergeTrailingCombinators( /// /// This is necessary when both selectors contain the same unique simple /// selector, such as an ID. -bool _mustUnify(List complex1, - List complex2) { +bool _mustUnify( + List complex1, + List complex2, +) { var uniqueSelectors = { for (var component in complex1) - ...component.selector.components.where(_isUnique) + ...component.selector.components.where(_isUnique), }; if (uniqueSelectors.isEmpty) return false; - return complex2.any((component) => component.selector.components - .any((simple) => _isUnique(simple) && uniqueSelectors.contains(simple))); + return complex2.any( + (component) => component.selector.components.any( + (simple) => _isUnique(simple) && uniqueSelectors.contains(simple), + ), + ); } /// Returns whether a [CompoundSelector] may contain only one simple selector of @@ -557,7 +616,10 @@ bool _isUnique(SimpleSelector simple) => /// the boundary of the initial subsequence), this would return `[(A B C 1 2), /// (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. List> _chunks( - Queue queue1, Queue queue2, bool done(Queue queue)) { + Queue queue1, + Queue queue2, + bool done(Queue queue), +) { var chunk1 = []; while (!done(queue1)) { chunk1.add(queue1.removeFirst()); @@ -573,8 +635,8 @@ List> _chunks( ([], var chunk) || (var chunk, []) => [chunk], _ => [ [...chunk1, ...chunk2], - [...chunk2, ...chunk1] - ] + [...chunk2, ...chunk1], + ], }; } @@ -589,10 +651,11 @@ List> _chunks( /// [2, 4, 5]] /// ``` List> paths(Iterable> choices) => choices.fold( - [[]], - (paths, choice) => choice - .expand((option) => paths.map((path) => [...path, option])) - .toList()); + [[]], + (paths, choice) => choice + .expand((option) => paths.map((path) => [...path, option])) + .toList(), + ); /// Returns [complex], grouped into the longest possible sub-lists such that /// [ComplexSelectorComponent]s without combinators only appear at the end of @@ -601,7 +664,8 @@ List> paths(Iterable> choices) => choices.fold( /// For example, `(A B > C D + E ~ G)` is grouped into /// `[(A) (B > C) (D + E ~ G)]`. QueueList> _groupSelectors( - Iterable complex) { + Iterable complex, +) { var groups = QueueList>(); var group = []; for (var component in complex) { @@ -621,9 +685,12 @@ QueueList> _groupSelectors( /// That is, whether [list1] matches every element that [list2] matches, as well /// as possibly additional elements. bool listIsSuperselector( - List list1, List list2) => - list2.every((complex1) => - list1.any((complex2) => complex2.isSuperselector(complex1))); + List list1, + List list2, +) => + list2.every( + (complex1) => list1.any((complex2) => complex2.isSuperselector(complex1)), + ); /// Like [complexIsSuperselector], but compares [complex1] and [complex2] as /// though they shared an implicit base [SimpleSelector]. @@ -631,16 +698,19 @@ bool listIsSuperselector( /// For example, `B` is not normally a superselector of `B A`, since it doesn't /// match elements that match `A`. However, it *is* a parent superselector, /// since `B X` is a superselector of `B A X`. -bool _complexIsParentSuperselector(List complex1, - List complex2) { +bool _complexIsParentSuperselector( + List complex1, + List complex2, +) { if (complex1.length > complex2.length) return false; // TODO(nweiz): There's got to be a way to do this without a bunch of extra // allocations... var base = ComplexSelectorComponent( - CompoundSelector([PlaceholderSelector('', bogusSpan)], bogusSpan), - const [], - bogusSpan); + CompoundSelector([PlaceholderSelector('', bogusSpan)], bogusSpan), + const [], + bogusSpan, + ); return complexIsSuperselector([...complex1, base], [...complex2, base]); } @@ -648,8 +718,10 @@ bool _complexIsParentSuperselector(List complex1, /// /// That is, whether [complex1] matches every element that [complex2] matches, as well /// as possibly additional elements. -bool complexIsSuperselector(List complex1, - List complex2) { +bool complexIsSuperselector( + List complex1, + List complex2, +) { // Selectors with trailing operators are neither superselectors nor // subselectors. if (complex1.last.combinators.isNotEmpty) return false; @@ -673,10 +745,12 @@ bool complexIsSuperselector(List complex1, return false; } else { return compoundIsSuperselector( - component1.selector, complex2.last.selector, - parents: component1.selector.hasComplicatedSuperselectorSemantics - ? complex2.sublist(i2, complex2.length - 1) - : null); + component1.selector, + complex2.last.selector, + parents: component1.selector.hasComplicatedSuperselectorSemantics + ? complex2.sublist(i2, complex2.length - 1) + : null, + ); } } @@ -687,10 +761,13 @@ bool complexIsSuperselector(List complex1, while (true) { var component2 = complex2[endOfSubselector]; if (component2.combinators.length > 1) return false; - if (compoundIsSuperselector(component1.selector, component2.selector, - parents: component1.selector.hasComplicatedSuperselectorSemantics - ? complex2.sublist(i2, endOfSubselector) - : null)) { + if (compoundIsSuperselector( + component1.selector, + component2.selector, + parents: component1.selector.hasComplicatedSuperselectorSemantics + ? complex2.sublist(i2, endOfSubselector) + : null, + )) { break; } @@ -705,7 +782,9 @@ bool complexIsSuperselector(List complex1, } if (!_compatibleWithPreviousCombinator( - previousCombinator, complex2.take(endOfSubselector).skip(i2))) { + previousCombinator, + complex2.take(endOfSubselector).skip(i2), + )) { return false; } @@ -724,9 +803,12 @@ bool complexIsSuperselector(List complex1, if (combinator1?.value == Combinator.followingSibling) { // The selector `.foo ~ .bar` is only a superselector of selectors that // *exclusively* contain subcombinators of `~`. - if (!complex2.take(complex2.length - 1).skip(i2).every((component) => - _isSupercombinator( - combinator1, component.combinators.firstOrNull))) { + if (!complex2.take(complex2.length - 1).skip(i2).every( + (component) => _isSupercombinator( + combinator1, + component.combinators.firstOrNull, + ), + )) { return false; } } else if (combinator1 != null) { @@ -741,8 +823,10 @@ bool complexIsSuperselector(List complex1, /// Returns whether [parents] are valid intersitial components between one /// complex superselector and another, given that the earlier complex /// superselector had the combinator [previous]. -bool _compatibleWithPreviousCombinator(CssValue? previous, - Iterable parents) { +bool _compatibleWithPreviousCombinator( + CssValue? previous, + Iterable parents, +) { if (parents.isEmpty) return true; if (previous == null) return true; @@ -752,16 +836,21 @@ bool _compatibleWithPreviousCombinator(CssValue? previous, // The following sibling combinator does allow intermediate components, but // only if they're all siblings. - return parents.every((component) => - component.combinators.firstOrNull?.value == Combinator.followingSibling || - component.combinators.firstOrNull?.value == Combinator.nextSibling); + return parents.every( + (component) => + component.combinators.firstOrNull?.value == + Combinator.followingSibling || + component.combinators.firstOrNull?.value == Combinator.nextSibling, + ); } /// Returns whether [combinator1] is a supercombinator of [combinator2]. /// /// That is, whether `X combinator1 Y` is a superselector of `X combinator2 Y`. bool _isSupercombinator( - CssValue? combinator1, CssValue? combinator2) => + CssValue? combinator1, + CssValue? combinator2, +) => combinator1 == combinator2 || (combinator1 == null && combinator2?.value == Combinator.child) || (combinator1?.value == Combinator.followingSibling && @@ -776,13 +865,16 @@ bool _isSupercombinator( /// relevant for pseudo selectors with selector arguments, where we may need to /// know if the parent selectors in the selector argument match [parents]. bool compoundIsSuperselector( - CompoundSelector compound1, CompoundSelector compound2, - {Iterable? parents}) { + CompoundSelector compound1, + CompoundSelector compound2, { + Iterable? parents, +}) { if (!compound1.hasComplicatedSuperselectorSemantics && !compound2.hasComplicatedSuperselectorSemantics) { if (compound1.components.length > compound2.components.length) return false; - return compound1.components - .every((simple1) => compound2.components.any(simple1.isSuperselector)); + return compound1.components.every( + (simple1) => compound2.components.any(simple1.isSuperselector), + ); } // Pseudo elements effectively change the target of a compound selector rather @@ -794,16 +886,20 @@ bool compoundIsSuperselector( // before them must switch (( _findPseudoElementIndexed(compound1), - _findPseudoElementIndexed(compound2) + _findPseudoElementIndexed(compound2), )) { case ((var pseudo1, var index1), (var pseudo2, var index2)): return pseudo1.isSuperselector(pseudo2) && - _compoundComponentsIsSuperselector(compound1.components.take(index1), - compound2.components.take(index2), parents: parents) && _compoundComponentsIsSuperselector( - compound1.components.skip(index1 + 1), - compound2.components.skip(index2 + 1), - parents: parents); + compound1.components.take(index1), + compound2.components.take(index2), + parents: parents, + ) && + _compoundComponentsIsSuperselector( + compound1.components.skip(index1 + 1), + compound2.components.skip(index2 + 1), + parents: parents, + ); case (_?, _) || (_, _?): return false; @@ -813,8 +909,11 @@ bool compoundIsSuperselector( // [compound2.components]. for (var simple1 in compound1.components) { if (simple1 case PseudoSelector(selector: _?)) { - if (!_selectorPseudoIsSuperselector(simple1, compound2, - parents: parents)) { + if (!_selectorPseudoIsSuperselector( + simple1, + compound2, + parents: parents, + )) { return false; } } else if (!compound2.components.any(simple1.isSuperselector)) { @@ -841,15 +940,19 @@ bool compoundIsSuperselector( /// The [compound1] and [compound2] are expected to have efficient /// [Iterable.length] fields. bool _compoundComponentsIsSuperselector( - Iterable compound1, Iterable compound2, - {Iterable? parents}) { + Iterable compound1, + Iterable compound2, { + Iterable? parents, +}) { if (compound1.isEmpty) return true; if (compound2.isEmpty) { compound2 = [UniversalSelector(bogusSpan, namespace: '*')]; } - return compoundIsSuperselector(CompoundSelector(compound1, bogusSpan), - CompoundSelector(compound2, bogusSpan), - parents: parents); + return compoundIsSuperselector( + CompoundSelector(compound1, bogusSpan), + CompoundSelector(compound2, bogusSpan), + parents: parents, + ); } /// Returns whether [pseudo1] is a superselector of [compound2]. @@ -863,8 +966,10 @@ bool _compoundComponentsIsSuperselector( /// relevant for pseudo selectors with selector arguments, where we may need to /// know if the parent selectors in the selector argument match [parents]. bool _selectorPseudoIsSuperselector( - PseudoSelector pseudo1, CompoundSelector compound2, - {Iterable? parents}) { + PseudoSelector pseudo1, + CompoundSelector compound2, { + Iterable? parents, +}) { var selector1 = pseudo1.selector; if (selector1 == null) { throw ArgumentError("Selector $pseudo1 must have a selector argument."); @@ -876,44 +981,58 @@ bool _selectorPseudoIsSuperselector( case 'any': case 'where': var selectors = _selectorPseudoArgs(compound2, pseudo1.name); - return selectors - .any((selector2) => selector1.isSuperselector(selector2)) || - selector1.components.any((complex1) => - complex1.leadingCombinators.isEmpty && - complexIsSuperselector(complex1.components, [ - ...?parents, - ComplexSelectorComponent(compound2, const [], compound2.span) - ])); + return selectors.any( + (selector2) => selector1.isSuperselector(selector2), + ) || + selector1.components.any( + (complex1) => + complex1.leadingCombinators.isEmpty && + complexIsSuperselector(complex1.components, [ + ...?parents, + ComplexSelectorComponent(compound2, const [], compound2.span), + ]), + ); case 'has': case 'host': case 'host-context': - return _selectorPseudoArgs(compound2, pseudo1.name) - .any((selector2) => selector1.isSuperselector(selector2)); + return _selectorPseudoArgs( + compound2, + pseudo1.name, + ).any((selector2) => selector1.isSuperselector(selector2)); case 'slotted': - return _selectorPseudoArgs(compound2, pseudo1.name, isClass: false) - .any((selector2) => selector1.isSuperselector(selector2)); + return _selectorPseudoArgs( + compound2, + pseudo1.name, + isClass: false, + ).any((selector2) => selector1.isSuperselector(selector2)); case 'not': return selector1.components.every((complex) { if (complex.isBogus) return false; - return compound2.components.any((simple2) => switch (simple2) { - TypeSelector() => complex.components.last.selector.components.any( - (simple1) => simple1 is TypeSelector && simple1 != simple2), - IDSelector() => complex.components.last.selector.components.any( - (simple1) => simple1 is IDSelector && simple1 != simple2), - PseudoSelector(selector: var selector2?) - when simple2.name == pseudo1.name => - listIsSuperselector(selector2.components, [complex]), - _ => false - }); + return compound2.components.any( + (simple2) => switch (simple2) { + TypeSelector() => complex.components.last.selector.components.any( + (simple1) => simple1 is TypeSelector && simple1 != simple2, + ), + IDSelector() => complex.components.last.selector.components.any( + (simple1) => simple1 is IDSelector && simple1 != simple2, + ), + PseudoSelector(selector: var selector2?) + when simple2.name == pseudo1.name => + listIsSuperselector(selector2.components, [complex]), + _ => false, + }, + ); }); case 'current': - return _selectorPseudoArgs(compound2, pseudo1.name) - .any((selector2) => selector1 == selector2); + return _selectorPseudoArgs( + compound2, + pseudo1.name, + ).any((selector2) => selector1 == selector2); case 'nth-child': case 'nth-last-child': @@ -934,7 +1053,10 @@ bool _selectorPseudoIsSuperselector( /// Returns all the selector arguments of pseudo selectors in [compound] with /// the given [name]. Iterable _selectorPseudoArgs( - CompoundSelector compound, String name, {bool isClass = true}) => + CompoundSelector compound, + String name, { + bool isClass = true, +}) => compound.components .whereType() .where((pseudo) => pseudo.isClass == isClass && pseudo.name == name) diff --git a/lib/src/extend/merged_extension.dart b/lib/src/extend/merged_extension.dart index b089379b1..cce0887a1 100644 --- a/lib/src/extend/merged_extension.dart +++ b/lib/src/extend/merged_extension.dart @@ -35,10 +35,11 @@ final class MergedExtension extends Extension { right.mediaContext != null && !listEquals(left.mediaContext, right.mediaContext)) { throw SassException( - "From ${left.span.message('')}\n" - "You may not @extend the same selector from within different media " - "queries.", - right.span); + "From ${left.span.message('')}\n" + "You may not @extend the same selector from within different media " + "queries.", + right.span, + ); } // If one extension is optional and doesn't add a special media context, it @@ -50,9 +51,13 @@ final class MergedExtension extends Extension { } MergedExtension._(this.left, this.right) - : super(left.extender.selector, left.target, left.span, - mediaContext: left.mediaContext ?? right.mediaContext, - optional: true); + : super( + left.extender.selector, + left.target, + left.span, + mediaContext: left.mediaContext ?? right.mediaContext, + optional: true, + ); /// Returns all leaf-node [Extension]s in the tree of [MergedExtension]s. Iterable unmerge() sync* { diff --git a/lib/src/functions.dart b/lib/src/functions.dart index d29de59a7..450e40ba1 100644 --- a/lib/src/functions.dart +++ b/lib/src/functions.dart @@ -30,8 +30,11 @@ final List globalFunctions = UnmodifiableListView([ // This is only invoked using `call()`. Hand-authored `if()`s are parsed as // [IfExpression]s. - BuiltInCallable.function("if", r"$condition, $if-true, $if-false", - (arguments) => arguments[0].isTruthy ? arguments[1] : arguments[2]), + BuiltInCallable.function( + "if", + r"$condition, $if-true, $if-false", + (arguments) => arguments[0].isTruthy ? arguments[1] : arguments[2], + ), ]); /// Sass's core library modules. @@ -44,5 +47,5 @@ final coreModules = UnmodifiableListView([ map.module, math.module, selector.module, - string.module + string.module, ]); diff --git a/lib/src/functions/color.dart b/lib/src/functions/color.dart index abbf9d00a..1cb3640c0 100644 --- a/lib/src/functions/color.dart +++ b/lib/src/functions/color.dart @@ -30,29 +30,48 @@ const _specialCommaSpaces = {ColorSpace.rgb, ColorSpace.hsl}; /// The global definitions of Sass color functions. final global = UnmodifiableListView([ // ### RGB - _channelFunction("red", ColorSpace.rgb, (color) => color.red, global: true) - .withDeprecationWarning("color"), - _channelFunction("green", ColorSpace.rgb, (color) => color.green, - global: true) - .withDeprecationWarning("color"), - _channelFunction("blue", ColorSpace.rgb, (color) => color.blue, global: true) - .withDeprecationWarning("color"), + _channelFunction( + "red", + ColorSpace.rgb, + (color) => color.red, + global: true, + ).withDeprecationWarning("color"), + _channelFunction( + "green", + ColorSpace.rgb, + (color) => color.green, + global: true, + ).withDeprecationWarning("color"), + _channelFunction( + "blue", + ColorSpace.rgb, + (color) => color.blue, + global: true, + ).withDeprecationWarning("color"), _mix.withDeprecationWarning("color"), BuiltInCallable.overloadedFunction("rgb", { r"$red, $green, $blue, $alpha": (arguments) => _rgb("rgb", arguments), r"$red, $green, $blue": (arguments) => _rgb("rgb", arguments), r"$color, $alpha": (arguments) => _rgbTwoArg("rgb", arguments), - r"$channels": (arguments) => _parseChannels("rgb", arguments[0], - space: ColorSpace.rgb, name: 'channels') + r"$channels": (arguments) => _parseChannels( + "rgb", + arguments[0], + space: ColorSpace.rgb, + name: 'channels', + ), }), BuiltInCallable.overloadedFunction("rgba", { r"$red, $green, $blue, $alpha": (arguments) => _rgb("rgba", arguments), r"$red, $green, $blue": (arguments) => _rgb("rgba", arguments), r"$color, $alpha": (arguments) => _rgbTwoArg("rgba", arguments), - r"$channels": (arguments) => _parseChannels('rgba', arguments[0], - space: ColorSpace.rgb, name: 'channels') + r"$channels": (arguments) => _parseChannels( + 'rgba', + arguments[0], + space: ColorSpace.rgb, + name: 'channels', + ), }), _function("invert", r"$color, $weight: 100%, $space: null", (arguments) { @@ -63,15 +82,27 @@ final global = UnmodifiableListView([ }), // ### HSL - _channelFunction("hue", ColorSpace.hsl, (color) => color.hue, - unit: 'deg', global: true) - .withDeprecationWarning("color"), - _channelFunction("saturation", ColorSpace.hsl, (color) => color.saturation, - unit: '%', global: true) - .withDeprecationWarning("color"), - _channelFunction("lightness", ColorSpace.hsl, (color) => color.lightness, - unit: '%', global: true) - .withDeprecationWarning("color"), + _channelFunction( + "hue", + ColorSpace.hsl, + (color) => color.hue, + unit: 'deg', + global: true, + ).withDeprecationWarning("color"), + _channelFunction( + "saturation", + ColorSpace.hsl, + (color) => color.saturation, + unit: '%', + global: true, + ).withDeprecationWarning("color"), + _channelFunction( + "lightness", + ColorSpace.hsl, + (color) => color.lightness, + unit: '%', + global: true, + ).withDeprecationWarning("color"), BuiltInCallable.overloadedFunction("hsl", { r"$hue, $saturation, $lightness, $alpha": (arguments) => @@ -86,8 +117,12 @@ final global = UnmodifiableListView([ throw SassScriptException(r"Missing argument $lightness."); } }, - r"$channels": (arguments) => _parseChannels('hsl', arguments[0], - space: ColorSpace.hsl, name: 'channels') + r"$channels": (arguments) => _parseChannels( + 'hsl', + arguments[0], + space: ColorSpace.hsl, + name: 'channels', + ), }), BuiltInCallable.overloadedFunction("hsla", { @@ -101,8 +136,12 @@ final global = UnmodifiableListView([ throw SassScriptException(r"Missing argument $lightness."); } }, - r"$channels": (arguments) => _parseChannels('hsla', arguments[0], - space: ColorSpace.hsl, name: 'channels') + r"$channels": (arguments) => _parseChannels( + 'hsla', + arguments[0], + space: ColorSpace.hsl, + name: 'channels', + ), }), _function("grayscale", r"$color", (arguments) { @@ -122,18 +161,20 @@ final global = UnmodifiableListView([ if (!color.isLegacy) { throw SassScriptException( - "adjust-hue() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "adjust-hue() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } var suggestedValue = SassNumber(degrees, 'deg'); warnForDeprecation( - "adjust-hue() is deprecated. Suggestion:\n" - "\n" - "color.adjust(\$color, \$hue: $suggestedValue)\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "adjust-hue() is deprecated. Suggestion:\n" + "\n" + "color.adjust(\$color, \$hue: $suggestedValue)\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return color.changeHsl(hue: color.hue + degrees); }).withDeprecationWarning('color', 'adjust'), @@ -143,20 +184,26 @@ final global = UnmodifiableListView([ var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "lighten() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "lighten() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } var result = color.changeHsl( - lightness: clampLikeCss( - color.lightness + amount.valueInRange(0, 100, "amount"), 0, 100)); + lightness: clampLikeCss( + color.lightness + amount.valueInRange(0, 100, "amount"), + 0, + 100, + ), + ); warnForDeprecation( - "lighten() is deprecated. " - "${_suggestScaleAndAdjust(color, amount.value, 'lightness')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "lighten() is deprecated. " + "${_suggestScaleAndAdjust(color, amount.value, 'lightness')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; }).withDeprecationWarning('color', 'adjust'), @@ -165,20 +212,26 @@ final global = UnmodifiableListView([ var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "darken() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "darken() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } var result = color.changeHsl( - lightness: clampLikeCss( - color.lightness - amount.valueInRange(0, 100, "amount"), 0, 100)); + lightness: clampLikeCss( + color.lightness - amount.valueInRange(0, 100, "amount"), + 0, + 100, + ), + ); warnForDeprecation( - "darken() is deprecated. " - "${_suggestScaleAndAdjust(color, -amount.value, 'lightness')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "darken() is deprecated. " + "${_suggestScaleAndAdjust(color, -amount.value, 'lightness')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; }).withDeprecationWarning('color', 'adjust'), @@ -197,24 +250,28 @@ final global = UnmodifiableListView([ var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "saturate() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "saturate() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } var result = color.changeHsl( - saturation: clampLikeCss( - color.saturation + amount.valueInRange(0, 100, "amount"), - 0, - 100)); + saturation: clampLikeCss( + color.saturation + amount.valueInRange(0, 100, "amount"), + 0, + 100, + ), + ); warnForDeprecation( - "saturate() is deprecated. " - "${_suggestScaleAndAdjust(color, amount.value, 'saturation')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "saturate() is deprecated. " + "${_suggestScaleAndAdjust(color, amount.value, 'saturation')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; - } + }, }), _function("desaturate", r"$color, $amount", (arguments) { @@ -222,36 +279,50 @@ final global = UnmodifiableListView([ var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "desaturate() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "desaturate() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } var result = color.changeHsl( - saturation: clampLikeCss( - color.saturation - amount.valueInRange(0, 100, "amount"), 0, 100)); + saturation: clampLikeCss( + color.saturation - amount.valueInRange(0, 100, "amount"), + 0, + 100, + ), + ); warnForDeprecation( - "desaturate() is deprecated. " - "${_suggestScaleAndAdjust(color, -amount.value, 'saturation')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "desaturate() is deprecated. " + "${_suggestScaleAndAdjust(color, -amount.value, 'saturation')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; }).withDeprecationWarning('color', 'adjust'), // ### Opacity - _function("opacify", r"$color, $amount", - (arguments) => _opacify("opacify", arguments)) - .withDeprecationWarning('color', 'adjust'), - _function("fade-in", r"$color, $amount", - (arguments) => _opacify("fade-in", arguments)) - .withDeprecationWarning('color', 'adjust'), - _function("transparentize", r"$color, $amount", - (arguments) => _transparentize("transparentize", arguments)) - .withDeprecationWarning('color', 'adjust'), - _function("fade-out", r"$color, $amount", - (arguments) => _transparentize("fade-out", arguments)) - .withDeprecationWarning('color', 'adjust'), + _function( + "opacify", + r"$color, $amount", + (arguments) => _opacify("opacify", arguments), + ).withDeprecationWarning('color', 'adjust'), + _function( + "fade-in", + r"$color, $amount", + (arguments) => _opacify("fade-in", arguments), + ).withDeprecationWarning('color', 'adjust'), + _function( + "transparentize", + r"$color, $amount", + (arguments) => _transparentize("transparentize", arguments), + ).withDeprecationWarning('color', 'adjust'), + _function( + "fade-out", + r"$color, $amount", + (arguments) => _transparentize("fade-out", arguments), + ).withDeprecationWarning('color', 'adjust'), BuiltInCallable.overloadedFunction("alpha", { r"$color": (arguments) { @@ -262,8 +333,9 @@ final global = UnmodifiableListView([ return _functionString("alpha", arguments); case SassColor(isLegacy: false): throw SassScriptException( - "alpha() is only supported for legacy colors. Please use " - "color.channel() instead."); + "alpha() is only supported for legacy colors. Please use " + "color.channel() instead.", + ); case var argument: warnForGlobalBuiltIn('color', 'alpha'); return SassNumber(argument.assertColor("color").alpha); @@ -272,10 +344,12 @@ final global = UnmodifiableListView([ r"$args...": (arguments) { var argList = arguments[0].asList; if (argList.isNotEmpty && - argList.every((argument) => - argument is SassString && - !argument.hasQuotes && - argument.text.contains(_microsoftFilterStart))) { + argList.every( + (argument) => + argument is SassString && + !argument.hasQuotes && + argument.text.contains(_microsoftFilterStart), + )) { // Support the proprietary Microsoft alpha() function. return _functionString("alpha", arguments); } @@ -285,9 +359,10 @@ final global = UnmodifiableListView([ throw SassScriptException("Missing argument \$color."); } else { throw SassScriptException( - "Only 1 argument allowed, but ${argList.length} were passed."); + "Only 1 argument allowed, but ${argList.length} were passed.", + ); } - } + }, }), _function("opacity", r"$color", (arguments) { @@ -302,42 +377,66 @@ final global = UnmodifiableListView([ }), // ### Color Spaces - _function( - "color", - r"$description", - (arguments) => - _parseChannels("color", arguments[0], name: 'description')), + "color", + r"$description", + (arguments) => _parseChannels("color", arguments[0], name: 'description'), + ), _function( + "hwb", + r"$channels", + (arguments) => _parseChannels( "hwb", - r"$channels", - (arguments) => _parseChannels("hwb", arguments[0], - space: ColorSpace.hwb, name: 'channels')), + arguments[0], + space: ColorSpace.hwb, + name: 'channels', + ), + ), _function( + "lab", + r"$channels", + (arguments) => _parseChannels( "lab", - r"$channels", - (arguments) => _parseChannels("lab", arguments[0], - space: ColorSpace.lab, name: 'channels')), + arguments[0], + space: ColorSpace.lab, + name: 'channels', + ), + ), _function( + "lch", + r"$channels", + (arguments) => _parseChannels( "lch", - r"$channels", - (arguments) => _parseChannels("lch", arguments[0], - space: ColorSpace.lch, name: 'channels')), + arguments[0], + space: ColorSpace.lch, + name: 'channels', + ), + ), _function( + "oklab", + r"$channels", + (arguments) => _parseChannels( "oklab", - r"$channels", - (arguments) => _parseChannels("oklab", arguments[0], - space: ColorSpace.oklab, name: 'channels')), + arguments[0], + space: ColorSpace.oklab, + name: 'channels', + ), + ), _function( + "oklch", + r"$channels", + (arguments) => _parseChannels( "oklch", - r"$channels", - (arguments) => _parseChannels("oklch", arguments[0], - space: ColorSpace.oklch, name: 'channels')), + arguments[0], + space: ColorSpace.oklch, + name: 'channels', + ), + ), _complement.withDeprecationWarning("color"), @@ -345,336 +444,419 @@ final global = UnmodifiableListView([ _ieHexStr, _adjust.withDeprecationWarning('color').withName("adjust-color"), _scale.withDeprecationWarning('color').withName("scale-color"), - _change.withDeprecationWarning('color').withName("change-color") + _change.withDeprecationWarning('color').withName("change-color"), ]); /// The Sass color module. -final module = BuiltInModule("color", functions: [ - // ### RGB - _channelFunction("red", ColorSpace.rgb, (color) => color.red), - _channelFunction("green", ColorSpace.rgb, (color) => color.green), - _channelFunction("blue", ColorSpace.rgb, (color) => color.blue), - _mix, - - _function("invert", r"$color, $weight: 100%, $space: null", (arguments) { - var result = _invert(arguments); - if (result is SassString) { - warnForDeprecation( +final module = BuiltInModule( + "color", + functions: [ + // ### RGB + _channelFunction("red", ColorSpace.rgb, (color) => color.red), + _channelFunction("green", ColorSpace.rgb, (color) => color.green), + _channelFunction("blue", ColorSpace.rgb, (color) => color.blue), + _mix, + + _function("invert", r"$color, $weight: 100%, $space: null", (arguments) { + var result = _invert(arguments); + if (result is SassString) { + warnForDeprecation( "Passing a number (${arguments[0]}) to color.invert() is " "deprecated.\n" "\n" "Recommendation: $result", - Deprecation.colorModuleCompat); - } - return result; - }), - - // ### HSL - _channelFunction("hue", ColorSpace.hsl, (color) => color.hue, unit: 'deg'), - _channelFunction("saturation", ColorSpace.hsl, (color) => color.saturation, - unit: '%'), - _channelFunction("lightness", ColorSpace.hsl, (color) => color.lightness, - unit: '%'), - _removedColorFunction("adjust-hue", "hue"), - _removedColorFunction("lighten", "lightness"), - _removedColorFunction("darken", "lightness", negative: true), - _removedColorFunction("saturate", "saturation"), - _removedColorFunction("desaturate", "saturation", negative: true), + Deprecation.colorModuleCompat, + ); + } + return result; + }), - _function("grayscale", r"$color", (arguments) { - if (arguments[0] is SassNumber) { - var result = _functionString("grayscale", arguments.take(1)); - warnForDeprecation( + // ### HSL + _channelFunction("hue", ColorSpace.hsl, (color) => color.hue, unit: 'deg'), + _channelFunction( + "saturation", + ColorSpace.hsl, + (color) => color.saturation, + unit: '%', + ), + _channelFunction( + "lightness", + ColorSpace.hsl, + (color) => color.lightness, + unit: '%', + ), + _removedColorFunction("adjust-hue", "hue"), + _removedColorFunction("lighten", "lightness"), + _removedColorFunction("darken", "lightness", negative: true), + _removedColorFunction("saturate", "saturation"), + _removedColorFunction("desaturate", "saturation", negative: true), + + _function("grayscale", r"$color", (arguments) { + if (arguments[0] is SassNumber) { + var result = _functionString("grayscale", arguments.take(1)); + warnForDeprecation( "Passing a number (${arguments[0]}) to color.grayscale() is " "deprecated.\n" "\n" "Recommendation: $result", - Deprecation.colorModuleCompat); - return result; - } - - return _grayscale(arguments[0]); - }), - - // ### HWB - BuiltInCallable.overloadedFunction("hwb", { - r"$hue, $whiteness, $blackness, $alpha: 1": (arguments) => _parseChannels( - 'hwb', - SassList([ - SassList( - [arguments[0], arguments[1], arguments[2]], ListSeparator.space), - arguments[3] - ], ListSeparator.slash), - space: ColorSpace.hwb), - r"$channels": (arguments) => _parseChannels('hwb', arguments[0], - space: ColorSpace.hwb, name: 'channels') - }), - - _channelFunction("whiteness", ColorSpace.hwb, (color) => color.whiteness, - unit: '%'), - _channelFunction("blackness", ColorSpace.hwb, (color) => color.blackness, - unit: '%'), - - // ### Opacity - _removedColorFunction("opacify", "alpha"), - _removedColorFunction("fade-in", "alpha"), - _removedColorFunction("transparentize", "alpha", negative: true), - _removedColorFunction("fade-out", "alpha", negative: true), + Deprecation.colorModuleCompat, + ); + return result; + } - BuiltInCallable.overloadedFunction("alpha", { - r"$color": (arguments) { - switch (arguments[0]) { - // Support the proprietary Microsoft alpha() function. - case SassString(hasQuotes: false, :var text) - when text.contains(_microsoftFilterStart): - var result = _functionString("alpha", arguments); - warnForDeprecation( + return _grayscale(arguments[0]); + }), + + // ### HWB + BuiltInCallable.overloadedFunction("hwb", { + r"$hue, $whiteness, $blackness, $alpha: 1": (arguments) => _parseChannels( + 'hwb', + SassList([ + SassList([ + arguments[0], + arguments[1], + arguments[2], + ], ListSeparator.space), + arguments[3], + ], ListSeparator.slash), + space: ColorSpace.hwb, + ), + r"$channels": (arguments) => _parseChannels( + 'hwb', + arguments[0], + space: ColorSpace.hwb, + name: 'channels', + ), + }), + + _channelFunction( + "whiteness", + ColorSpace.hwb, + (color) => color.whiteness, + unit: '%', + ), + _channelFunction( + "blackness", + ColorSpace.hwb, + (color) => color.blackness, + unit: '%', + ), + + // ### Opacity + _removedColorFunction("opacify", "alpha"), + _removedColorFunction("fade-in", "alpha"), + _removedColorFunction("transparentize", "alpha", negative: true), + _removedColorFunction("fade-out", "alpha", negative: true), + + BuiltInCallable.overloadedFunction("alpha", { + r"$color": (arguments) { + switch (arguments[0]) { + // Support the proprietary Microsoft alpha() function. + case SassString(hasQuotes: false, :var text) + when text.contains(_microsoftFilterStart): + var result = _functionString("alpha", arguments); + warnForDeprecation( "Using color.alpha() for a Microsoft filter is deprecated.\n" "\n" "Recommendation: $result", - Deprecation.colorModuleCompat); - return result; + Deprecation.colorModuleCompat, + ); + return result; - case SassColor(isLegacy: false): - throw SassScriptException( + case SassColor(isLegacy: false): + throw SassScriptException( "color.alpha() is only supported for legacy colors. Please use " - "color.channel() instead."); + "color.channel() instead.", + ); - case var argument: - return SassNumber(argument.assertColor("color").alpha); - } - }, - r"$args...": (arguments) { - if (arguments[0].asList.every((argument) => - argument is SassString && - !argument.hasQuotes && - argument.text.contains(_microsoftFilterStart))) { - // Support the proprietary Microsoft alpha() function. - var result = _functionString("alpha", arguments); - warnForDeprecation( + case var argument: + return SassNumber(argument.assertColor("color").alpha); + } + }, + r"$args...": (arguments) { + if (arguments[0].asList.every( + (argument) => + argument is SassString && + !argument.hasQuotes && + argument.text.contains(_microsoftFilterStart), + )) { + // Support the proprietary Microsoft alpha() function. + var result = _functionString("alpha", arguments); + warnForDeprecation( "Using color.alpha() for a Microsoft filter is deprecated.\n" "\n" "Recommendation: $result", - Deprecation.colorModuleCompat); - return result; - } - - assert(arguments.length != 1); - throw SassScriptException( - "Only 1 argument allowed, but ${arguments.length} were passed."); - } - }), + Deprecation.colorModuleCompat, + ); + return result; + } - _function("opacity", r"$color", (arguments) { - if (arguments[0] is SassNumber) { - var result = _functionString("opacity", arguments); - warnForDeprecation( + assert(arguments.length != 1); + throw SassScriptException( + "Only 1 argument allowed, but ${arguments.length} were passed.", + ); + }, + }), + + _function("opacity", r"$color", (arguments) { + if (arguments[0] is SassNumber) { + var result = _functionString("opacity", arguments); + warnForDeprecation( "Passing a number (${arguments[0]} to color.opacity() is " "deprecated.\n" "\n" "Recommendation: $result", - Deprecation.colorModuleCompat); - return result; - } + Deprecation.colorModuleCompat, + ); + return result; + } - var color = arguments[0].assertColor("color"); - return SassNumber(color.alpha); - }), + var color = arguments[0].assertColor("color"); + return SassNumber(color.alpha); + }), - // ### Color Spaces - _function( + // ### Color Spaces + _function( "space", r"$color", - (arguments) => SassString(arguments.first.assertColor("color").space.name, - quotes: false)), - - // `color.to-space()` never returns missing channels for legacy color spaces - // because they're less compatible and users are probably using a legacy space - // because they want a highly compatible color. - _function( + (arguments) => SassString( + arguments.first.assertColor("color").space.name, + quotes: false, + ), + ), + + // `color.to-space()` never returns missing channels for legacy color spaces + // because they're less compatible and users are probably using a legacy space + // because they want a highly compatible color. + _function( "to-space", r"$color, $space", (arguments) => - _colorInSpace(arguments[0], arguments[1], legacyMissing: false)), + _colorInSpace(arguments[0], arguments[1], legacyMissing: false), + ), - _function("is-legacy", r"$color", - (arguments) => SassBoolean(arguments[0].assertColor("color").isLegacy)), + _function( + "is-legacy", + r"$color", + (arguments) => SassBoolean(arguments[0].assertColor("color").isLegacy), + ), - _function( + _function( "is-missing", r"$color, $channel", - (arguments) => SassBoolean(arguments[0] - .assertColor("color") - .isChannelMissing(_channelName(arguments[1]), - colorName: "color", channelName: "channel"))), + (arguments) => SassBoolean( + arguments[0].assertColor("color").isChannelMissing( + _channelName(arguments[1]), + colorName: "color", + channelName: "channel", + ), + ), + ), - _function( + _function( "is-in-gamut", r"$color, $space: null", (arguments) => - SassBoolean(_colorInSpace(arguments[0], arguments[1]).isInGamut)), + SassBoolean(_colorInSpace(arguments[0], arguments[1]).isInGamut), + ), - _function("to-gamut", r"$color, $space: null, $method: null", (arguments) { - var color = arguments[0].assertColor("color"); - var space = _spaceOrDefault(color, arguments[1], "space"); - if (arguments[2] == sassNull) { - throw SassScriptException( + _function("to-gamut", r"$color, $space: null, $method: null", (arguments) { + var color = arguments[0].assertColor("color"); + var space = _spaceOrDefault(color, arguments[1], "space"); + if (arguments[2] == sassNull) { + throw SassScriptException( "color.to-gamut() requires a \$method argument for forwards-" "compatibility with changes in the CSS spec. Suggestion:\n" "\n" "\$method: local-minde", - "method"); - } - - // Assign this before checking [space.isBounded] so that invalid method - // names consistently produce errors. - var method = GamutMapMethod.fromName( - (arguments[2].assertString("method")..assertUnquoted("method")).text); - if (!space.isBounded) return color; - - return color - .toSpace(space) - .toGamut(method) - .toSpace(color.space, legacyMissing: false); - }), - - _function("channel", r"$color, $channel, $space: null", (arguments) { - var color = _colorInSpace(arguments[0], arguments[2]); - var channelName = _channelName(arguments[1]); - if (channelName == "alpha") return SassNumber(color.alpha); - - var channelIndex = color.space.channels - .indexWhere((channel) => channel.name == channelName); - if (channelIndex == -1) { - throw SassScriptException( - "Color $color has no channel named $channelName.", "channel"); - } - - var channelInfo = color.space.channels[channelIndex]; - var channelValue = color.channels[channelIndex]; - var unit = channelInfo.associatedUnit; - if (unit == '%') { - channelValue = channelValue * 100 / (channelInfo as LinearChannel).max; - } + "method", + ); + } - return SassNumber(channelValue, unit); - }), + // Assign this before checking [space.isBounded] so that invalid method + // names consistently produce errors. + var method = GamutMapMethod.fromName( + (arguments[2].assertString("method")..assertUnquoted("method")).text, + ); + if (!space.isBounded) return color; + + return color + .toSpace(space) + .toGamut(method) + .toSpace(color.space, legacyMissing: false); + }), + + _function("channel", r"$color, $channel, $space: null", (arguments) { + var color = _colorInSpace(arguments[0], arguments[2]); + var channelName = _channelName(arguments[1]); + if (channelName == "alpha") return SassNumber(color.alpha); + + var channelIndex = color.space.channels.indexWhere( + (channel) => channel.name == channelName, + ); + if (channelIndex == -1) { + throw SassScriptException( + "Color $color has no channel named $channelName.", + "channel", + ); + } - _function("same", r"$color1, $color2", (arguments) { - var color1 = arguments[0].assertColor('color1'); - var color2 = arguments[1].assertColor('color2'); - - /// Converts [color] to the xyz-d65 space without any mising channels. - SassColor toXyzNoMissing(SassColor color) => switch (color) { - SassColor(space: ColorSpace.xyzD65, hasMissingChannel: false) => - color, - SassColor( - space: ColorSpace.xyzD65, - :var channel0, - :var channel1, - :var channel2, - :var alpha - ) => - SassColor.xyzD65(channel0, channel1, channel2, alpha), - SassColor( - :var space, - :var channel0, - :var channel1, - :var channel2, - :var alpha - ) => - // Use [ColorSpace.convert] manually so that we can convert missing - // channels to 0 without having to create new intermediate color - // objects. - space.convert( - ColorSpace.xyzD65, channel0, channel1, channel2, alpha) - }; - - return SassBoolean(color1.space == color2.space - ? fuzzyEquals(color1.channel0, color2.channel0) && - fuzzyEquals(color1.channel1, color2.channel1) && - fuzzyEquals(color1.channel2, color2.channel2) && - fuzzyEquals(color1.alpha, color2.alpha) - : toXyzNoMissing(color1) == toXyzNoMissing(color2)); - }), + var channelInfo = color.space.channels[channelIndex]; + var channelValue = color.channels[channelIndex]; + var unit = channelInfo.associatedUnit; + if (unit == '%') { + channelValue = channelValue * 100 / (channelInfo as LinearChannel).max; + } - _function( + return SassNumber(channelValue, unit); + }), + + _function("same", r"$color1, $color2", (arguments) { + var color1 = arguments[0].assertColor('color1'); + var color2 = arguments[1].assertColor('color2'); + + /// Converts [color] to the xyz-d65 space without any mising channels. + SassColor toXyzNoMissing(SassColor color) => switch (color) { + SassColor(space: ColorSpace.xyzD65, hasMissingChannel: false) => + color, + SassColor( + space: ColorSpace.xyzD65, + :var channel0, + :var channel1, + :var channel2, + :var alpha, + ) => + SassColor.xyzD65(channel0, channel1, channel2, alpha), + SassColor( + :var space, + :var channel0, + :var channel1, + :var channel2, + :var alpha, + ) => + // Use [ColorSpace.convert] manually so that we can convert missing + // channels to 0 without having to create new intermediate color + // objects. + space.convert( + ColorSpace.xyzD65, channel0, channel1, channel2, alpha), + }; + + return SassBoolean( + color1.space == color2.space + ? fuzzyEquals(color1.channel0, color2.channel0) && + fuzzyEquals(color1.channel1, color2.channel1) && + fuzzyEquals(color1.channel2, color2.channel2) && + fuzzyEquals(color1.alpha, color2.alpha) + : toXyzNoMissing(color1) == toXyzNoMissing(color2), + ); + }), + + _function( "is-powerless", r"$color, $channel, $space: null", - (arguments) => SassBoolean(_colorInSpace(arguments[0], arguments[2]) - .isChannelPowerless(_channelName(arguments[1]), - colorName: "color", channelName: "channel"))), + (arguments) => SassBoolean( + _colorInSpace(arguments[0], arguments[2]).isChannelPowerless( + _channelName(arguments[1]), + colorName: "color", + channelName: "channel", + ), + ), + ), - _complement, + _complement, - // Miscellaneous - _adjust, _scale, _change, _ieHexStr -]); + // Miscellaneous + _adjust, _scale, _change, _ieHexStr, + ], +); // ### RGB -final _mix = _function("mix", r"$color1, $color2, $weight: 50%, $method: null", - (arguments) { - var color1 = arguments[0].assertColor("color1"); - var color2 = arguments[1].assertColor("color2"); - var weight = arguments[2].assertNumber("weight"); - - if (arguments[3] != sassNull) { - return color1.interpolate( - color2, InterpolationMethod.fromValue(arguments[3], "method"), +final _mix = _function( + "mix", + r"$color1, $color2, $weight: 50%, $method: null", + (arguments) { + var color1 = arguments[0].assertColor("color1"); + var color2 = arguments[1].assertColor("color2"); + var weight = arguments[2].assertNumber("weight"); + + if (arguments[3] != sassNull) { + return color1.interpolate( + color2, + InterpolationMethod.fromValue(arguments[3], "method"), weight: weight.valueInRangeWithUnit(0, 100, "weight", "%") / 100, - legacyMissing: false); - } + legacyMissing: false, + ); + } - _checkPercent(weight, "weight"); - if (!color1.isLegacy) { - throw SassScriptException( + _checkPercent(weight, "weight"); + if (!color1.isLegacy) { + throw SassScriptException( "To use color.mix() with non-legacy color $color1, you must provide a " "\$method.", - "color1"); - } else if (!color2.isLegacy) { - throw SassScriptException( + "color1", + ); + } else if (!color2.isLegacy) { + throw SassScriptException( "To use color.mix() with non-legacy color $color2, you must provide a " "\$method.", - "color2"); - } + "color2", + ); + } - return _mixLegacy(color1, color2, weight); -}); + return _mixLegacy(color1, color2, weight); + }, +); // ### Color Spaces -final _complement = - _function("complement", r"$color, $space: null", (arguments) { +final _complement = _function("complement", r"$color, $space: null", ( + arguments, +) { var color = arguments[0].assertColor("color"); var space = color.isLegacy && arguments[1] == sassNull ? ColorSpace.hsl : ColorSpace.fromName( (arguments[1].assertString("space")..assertUnquoted("space")).text, - "space"); + "space", + ); if (!space.isPolar) { throw SassScriptException( - "Color space $space doesn't have a hue channel.", 'space'); + "Color space $space doesn't have a hue channel.", + 'space', + ); } - var colorInSpace = - color.toSpace(space, legacyMissing: arguments[1] != sassNull); + var colorInSpace = color.toSpace( + space, + legacyMissing: arguments[1] != sassNull, + ); return (space.isLegacy ? SassColor.forSpaceInternal( space, - _adjustChannel(colorInSpace, space.channels[0], - colorInSpace.channel0OrNull, SassNumber(180)), + _adjustChannel( + colorInSpace, + space.channels[0], + colorInSpace.channel0OrNull, + SassNumber(180), + ), colorInSpace.channel1OrNull, colorInSpace.channel2OrNull, - colorInSpace.alphaOrNull) + colorInSpace.alphaOrNull, + ) : SassColor.forSpaceInternal( space, colorInSpace.channel0OrNull, colorInSpace.channel1OrNull, - _adjustChannel(colorInSpace, space.channels[2], - colorInSpace.channel2OrNull, SassNumber(180)), - colorInSpace.alphaOrNull)) + _adjustChannel( + colorInSpace, + space.channels[2], + colorInSpace.channel2OrNull, + SassNumber(180), + ), + colorInSpace.alphaOrNull, + )) .toSpace(color.space, legacyMissing: false); }); @@ -698,28 +880,31 @@ Value _invert(List arguments, {bool global = false}) { if (arguments[2] == sassNull) { if (!color.isLegacy) { throw SassScriptException( - "To use color.invert() with non-legacy color $color, you must provide " - "a \$space.", - "color"); + "To use color.invert() with non-legacy color $color, you must provide " + "a \$space.", + "color", + ); } _checkPercent(weightNumber, "weight"); var rgb = color.toSpace(ColorSpace.rgb); var [channel0, channel1, channel2] = ColorSpace.rgb.channels; return _mixLegacy( - SassColor.rgb( - _invertChannel(rgb, channel0, rgb.channel0OrNull), - _invertChannel(rgb, channel1, rgb.channel1OrNull), - _invertChannel(rgb, channel2, rgb.channel2OrNull), - color.alphaOrNull), - color, - weightNumber) - .toSpace(color.space); + SassColor.rgb( + _invertChannel(rgb, channel0, rgb.channel0OrNull), + _invertChannel(rgb, channel1, rgb.channel1OrNull), + _invertChannel(rgb, channel2, rgb.channel2OrNull), + color.alphaOrNull, + ), + color, + weightNumber, + ).toSpace(color.space); } var space = ColorSpace.fromName( - (arguments[2].assertString('space')..assertUnquoted('space')).text, - 'space'); + (arguments[2].assertString('space')..assertUnquoted('space')).text, + 'space', + ); var weight = weightNumber.valueInRangeWithUnit(0, 100, 'weight', '%') / 100; if (fuzzyEquals(weight, 0)) return color; @@ -729,30 +914,37 @@ Value _invert(List arguments, {bool global = false}) { _invertChannel(inSpace, space.channels[0], inSpace.channel0OrNull), inSpace.channel2OrNull, inSpace.channel1OrNull, - inSpace.alpha), + inSpace.alpha, + ), ColorSpace.hsl || ColorSpace.lch || ColorSpace.oklch => SassColor.forSpaceInternal( - space, - _invertChannel(inSpace, space.channels[0], inSpace.channel0OrNull), - inSpace.channel1OrNull, - _invertChannel(inSpace, space.channels[2], inSpace.channel2OrNull), - inSpace.alpha), + space, + _invertChannel(inSpace, space.channels[0], inSpace.channel0OrNull), + inSpace.channel1OrNull, + _invertChannel(inSpace, space.channels[2], inSpace.channel2OrNull), + inSpace.alpha, + ), ColorSpace(channels: [var channel0, var channel1, var channel2]) => SassColor.forSpaceInternal( - space, - _invertChannel(inSpace, channel0, inSpace.channel0OrNull), - _invertChannel(inSpace, channel1, inSpace.channel1OrNull), - _invertChannel(inSpace, channel2, inSpace.channel2OrNull), - inSpace.alpha), - _ => throw UnsupportedError("Unknown color space $space.") + space, + _invertChannel(inSpace, channel0, inSpace.channel0OrNull), + _invertChannel(inSpace, channel1, inSpace.channel1OrNull), + _invertChannel(inSpace, channel2, inSpace.channel2OrNull), + inSpace.alpha, + ), + _ => throw UnsupportedError("Unknown color space $space."), }; return fuzzyEquals(weight, 1) ? inverted.toSpace(color.space, legacyMissing: false) - : color.interpolate(inverted, InterpolationMethod(space), - weight: 1 - weight, legacyMissing: false); + : color.interpolate( + inverted, + InterpolationMethod(space), + weight: 1 - weight, + legacyMissing: false, + ); } /// Returns the inverse of the given [value] in a linear color channel. @@ -762,7 +954,7 @@ double _invertChannel(SassColor color, ColorChannel channel, double? value) { LinearChannel(min: < 0) => -value, LinearChannel(min: 0, :var max) => max - value, ColorChannel(isPolarAngle: true) => (value + 180) % 360, - _ => throw UnsupportedError("Unknown channel $channel.") + _ => throw UnsupportedError("Unknown channel $channel."), }; } @@ -773,26 +965,42 @@ Value _grayscale(Value colorArg) { if (color.isLegacy) { var hsl = color.toSpace(ColorSpace.hsl); - return SassColor.hsl(hsl.channel0OrNull, 0, hsl.channel2OrNull, hsl.alpha) - .toSpace(color.space, legacyMissing: false); + return SassColor.hsl( + hsl.channel0OrNull, + 0, + hsl.channel2OrNull, + hsl.alpha, + ).toSpace(color.space, legacyMissing: false); } else { var oklch = color.toSpace(ColorSpace.oklch); return SassColor.oklch( - oklch.channel0OrNull, 0, oklch.channel2OrNull, oklch.alpha) - .toSpace(color.space); + oklch.channel0OrNull, + 0, + oklch.channel2OrNull, + oklch.alpha, + ).toSpace(color.space); } } // Miscellaneous -final _adjust = _function("adjust", r"$color, $kwargs...", - (arguments) => _updateComponents(arguments, adjust: true)); +final _adjust = _function( + "adjust", + r"$color, $kwargs...", + (arguments) => _updateComponents(arguments, adjust: true), +); -final _scale = _function("scale", r"$color, $kwargs...", - (arguments) => _updateComponents(arguments, scale: true)); +final _scale = _function( + "scale", + r"$color, $kwargs...", + (arguments) => _updateComponents(arguments, scale: true), +); -final _change = _function("change", r"$color, $kwargs...", - (arguments) => _updateComponents(arguments, change: true)); +final _change = _function( + "change", + r"$color, $kwargs...", + (arguments) => _updateComponents(arguments, change: true), +); final _ieHexStr = _function("ie-hex-str", r"$color", (arguments) { var color = arguments[0] @@ -802,24 +1010,30 @@ final _ieHexStr = _function("ie-hex-str", r"$color", (arguments) { String hexString(double component) => fuzzyRound(component).toRadixString(16).padLeft(2, '0').toUpperCase(); return SassString( - "#${hexString(color.alpha * 255)}${hexString(color.channel0)}" - "${hexString(color.channel1)}${hexString(color.channel2)}", - quotes: false); + "#${hexString(color.alpha * 255)}${hexString(color.channel0)}" + "${hexString(color.channel1)}${hexString(color.channel2)}", + quotes: false, + ); }); /// Implementation for `color.change`, `color.adjust`, and `color.scale`. /// /// Exactly one of [change], [adjust], and [scale] must be true to determine /// which function should be executed. -SassColor _updateComponents(List arguments, - {bool change = false, bool adjust = false, bool scale = false}) { +SassColor _updateComponents( + List arguments, { + bool change = false, + bool adjust = false, + bool scale = false, +}) { assert([change, adjust, scale].where((x) => x).length == 1); var argumentList = arguments[1] as SassArgumentList; if (argumentList.asList.isNotEmpty) { throw SassScriptException( - "Only one positional argument is allowed. All other arguments must " - "be passed by name."); + "Only one positional argument is allowed. All other arguments must " + "be passed by name.", + ); } var keywords = Map.of(argumentList.keywords); @@ -831,13 +1045,13 @@ SassColor _updateComponents(List arguments, // For backwards-compatibility, we allow legacy colors to modify channels in // any legacy color space and we their powerless channels as 0. - var color = spaceKeyword == null && - originalColor.isLegacy && - keywords.isNotEmpty - ? _sniffLegacyColorSpace(keywords).andThen( - (space) => originalColor.toSpace(space, legacyMissing: false)) ?? - originalColor - : _colorInSpace(originalColor, spaceKeyword ?? sassNull); + var color = + spaceKeyword == null && originalColor.isLegacy && keywords.isNotEmpty + ? _sniffLegacyColorSpace(keywords).andThen( + (space) => originalColor.toSpace(space, legacyMissing: false), + ) ?? + originalColor + : _colorInSpace(originalColor, spaceKeyword ?? sassNull); var oldChannels = color.channels; var channelArgs = List.filled(oldChannels.length, null); @@ -846,8 +1060,9 @@ SassColor _updateComponents(List arguments, var channelIndex = channelInfo.indexWhere((info) => name == info.name); if (channelIndex == -1) { throw SassScriptException( - "Color space ${color.space} doesn't have a channel with this name.", - name); + "Color space ${color.space} doesn't have a channel with this name.", + name, + ); } channelArgs[channelIndex] = value; @@ -859,7 +1074,7 @@ SassColor _updateComponents(List arguments, } else { var channelNumbers = [ for (var i = 0; i < channelInfo.length; i++) - channelArgs[i]?.assertNumber(channelInfo[i].name) + channelArgs[i]?.assertNumber(channelInfo[i].name), ]; var alphaNumber = alphaArg?.assertNumber("alpha"); result = scale @@ -873,34 +1088,41 @@ SassColor _updateComponents(List arguments, /// Returns a copy of [color] with its channel values replaced by those in /// [channelArgs] and [alphaArg], if specified. SassColor _changeColor( - SassColor color, List channelArgs, Value? alphaArg) => + SassColor color, + List channelArgs, + Value? alphaArg, +) => _colorFromChannels( - color.space, - _channelForChange(channelArgs[0], color, 0), - _channelForChange(channelArgs[1], color, 1), - _channelForChange(channelArgs[2], color, 2), - switch (alphaArg) { - null => color.alpha, - _ when _isNone(alphaArg) => null, - SassNumber(hasUnits: false) => alphaArg.valueInRange(0, 1, "alpha"), - SassNumber() when alphaArg.hasUnit('%') => - alphaArg.valueInRangeWithUnit(0, 100, "alpha", "%") / 100, - SassNumber() => () { - warnForDeprecation( - "\$alpha: Passing a unit other than % ($alphaArg) is " - "deprecated.\n" - "\n" - "To preserve current behavior: " - "${alphaArg.unitSuggestion('alpha')}\n" - "\n" - "See https://sass-lang.com/d/function-units", - Deprecation.functionUnits); - return alphaArg.valueInRange(0, 1, "alpha"); - }(), - _ => throw SassScriptException( - '$alphaArg is not a number or unquoted "none".', 'alpha') - }, - clamp: false); + color.space, + _channelForChange(channelArgs[0], color, 0), + _channelForChange(channelArgs[1], color, 1), + _channelForChange(channelArgs[2], color, 2), + switch (alphaArg) { + null => color.alpha, + _ when _isNone(alphaArg) => null, + SassNumber(hasUnits: false) => alphaArg.valueInRange(0, 1, "alpha"), + SassNumber() when alphaArg.hasUnit('%') => + alphaArg.valueInRangeWithUnit(0, 100, "alpha", "%") / 100, + SassNumber() => () { + warnForDeprecation( + "\$alpha: Passing a unit other than % ($alphaArg) is " + "deprecated.\n" + "\n" + "To preserve current behavior: " + "${alphaArg.unitSuggestion('alpha')}\n" + "\n" + "See https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); + return alphaArg.valueInRange(0, 1, "alpha"); + }(), + _ => throw SassScriptException( + '$alphaArg is not a number or unquoted "none".', + 'alpha', + ), + }, + clamp: false, + ); /// Returns the value for a single channel in `color.change()`. /// @@ -914,34 +1136,57 @@ SassNumber? _channelForChange(Value? channelArg, SassColor color, int channel) { (color.space == ColorSpace.hsl || color.space == ColorSpace.hwb) && channel > 0 ? '%' - : null), - _ => null + : null, + ), + _ => null, }; } if (_isNone(channelArg)) return null; if (channelArg is SassNumber) return channelArg; - throw SassScriptException('$channelArg is not a number or unquoted "none".', - color.space.channels[channel].name); + throw SassScriptException( + '$channelArg is not a number or unquoted "none".', + color.space.channels[channel].name, + ); } /// Returns a copy of [color] with its channel values scaled by the values in /// [channelArgs] and [alphaArg], if specified. SassColor _scaleColor( - SassColor color, List channelArgs, SassNumber? alphaArg) => + SassColor color, + List channelArgs, + SassNumber? alphaArg, +) => SassColor.forSpaceInternal( - color.space, - _scaleChannel(color, color.space.channels[0], color.channel0OrNull, - channelArgs[0]), - _scaleChannel(color, color.space.channels[1], color.channel1OrNull, - channelArgs[1]), - _scaleChannel(color, color.space.channels[2], color.channel2OrNull, - channelArgs[2]), - _scaleChannel(color, ColorChannel.alpha, color.alphaOrNull, alphaArg)); + color.space, + _scaleChannel( + color, + color.space.channels[0], + color.channel0OrNull, + channelArgs[0], + ), + _scaleChannel( + color, + color.space.channels[1], + color.channel1OrNull, + channelArgs[1], + ), + _scaleChannel( + color, + color.space.channels[2], + color.channel2OrNull, + channelArgs[2], + ), + _scaleChannel(color, ColorChannel.alpha, color.alphaOrNull, alphaArg), + ); /// Returns [oldValue] scaled by [factorArg] according to the definition in /// [channel]. -double? _scaleChannel(SassColor color, ColorChannel channel, double? oldValue, - SassNumber? factorArg) { +double? _scaleChannel( + SassColor color, + ColorChannel channel, + double? oldValue, + SassNumber? factorArg, +) { if (factorArg == null) return oldValue; if (channel is! LinearChannel) { throw SassScriptException("Channel isn't scalable.", channel.name); @@ -949,8 +1194,12 @@ double? _scaleChannel(SassColor color, ColorChannel channel, double? oldValue, if (oldValue == null) _missingChannelError(color, channel.name); - var factor = (factorArg..assertUnit('%', channel.name)) - .valueInRangeWithUnit(-100, 100, channel.name, '%') / + var factor = (factorArg..assertUnit('%', channel.name)).valueInRangeWithUnit( + -100, + 100, + channel.name, + '%', + ) / 100; return switch (factor) { 0 => oldValue, @@ -959,31 +1208,55 @@ double? _scaleChannel(SassColor color, ColorChannel channel, double? oldValue, : oldValue + (channel.max - oldValue) * factor, _ => oldValue <= channel.min ? oldValue - : oldValue + (oldValue - channel.min) * factor + : oldValue + (oldValue - channel.min) * factor, }; } /// Returns a copy of [color] with its channel values adjusted by the values in /// [channelArgs] and [alphaArg], if specified. SassColor _adjustColor( - SassColor color, List channelArgs, SassNumber? alphaArg) => + SassColor color, + List channelArgs, + SassNumber? alphaArg, +) => SassColor.forSpaceInternal( - color.space, - _adjustChannel(color, color.space.channels[0], color.channel0OrNull, - channelArgs[0]), - _adjustChannel(color, color.space.channels[1], color.channel1OrNull, - channelArgs[1]), - _adjustChannel(color, color.space.channels[2], color.channel2OrNull, - channelArgs[2]), - // The color space doesn't matter for alpha, as long as it's not - // strictly bounded. - _adjustChannel(color, ColorChannel.alpha, color.alphaOrNull, alphaArg) - .andThen((alpha) => clampLikeCss(alpha, 0, 1))); + color.space, + _adjustChannel( + color, + color.space.channels[0], + color.channel0OrNull, + channelArgs[0], + ), + _adjustChannel( + color, + color.space.channels[1], + color.channel1OrNull, + channelArgs[1], + ), + _adjustChannel( + color, + color.space.channels[2], + color.channel2OrNull, + channelArgs[2], + ), + // The color space doesn't matter for alpha, as long as it's not + // strictly bounded. + _adjustChannel( + color, + ColorChannel.alpha, + color.alphaOrNull, + alphaArg, + ).andThen((alpha) => clampLikeCss(alpha, 0, 1)), + ); /// Returns [oldValue] adjusted by [adjustmentArg] according to the definition /// in [color]'s space's [channel]. -double? _adjustChannel(SassColor color, ColorChannel channel, double? oldValue, - SassNumber? adjustmentArg) { +double? _adjustChannel( + SassColor color, + ColorChannel channel, + double? oldValue, + SassNumber? adjustmentArg, +) { if (adjustmentArg == null) return oldValue; if (oldValue == null) _missingChannelError(color, channel.name); @@ -1007,14 +1280,15 @@ double? _adjustChannel(SassColor color, ColorChannel channel, double? oldValue, // still in the deprecation period where we allow other values (and // interpret `%` as unitless) so we have to handle that ahead-of-time. warnForDeprecation( - "\$alpha: Passing a number with unit ${adjustmentArg.unitString} is " - "deprecated.\n" - "\n" - "To preserve current behavior: " - "${adjustmentArg.unitSuggestion('alpha')}\n" - "\n" - "More info: https://sass-lang.com/d/function-units", - Deprecation.functionUnits); + "\$alpha: Passing a number with unit ${adjustmentArg.unitString} is " + "deprecated.\n" + "\n" + "To preserve current behavior: " + "${adjustmentArg.unitSuggestion('alpha')}\n" + "\n" + "More info: https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); adjustmentArg = SassNumber(adjustmentArg.value); } @@ -1025,7 +1299,7 @@ double? _adjustChannel(SassColor color, ColorChannel channel, double? oldValue, oldValue < min ? math.max(oldValue, result) : min, LinearChannel(upperClamped: true, :var max) when result > max => oldValue > max ? math.min(oldValue, result) : max, - _ => result + _ => result, }; } @@ -1055,26 +1329,31 @@ ColorSpace? _sniffLegacyColorSpace(Map keywords) { /// it were a plain CSS function. SassString _functionString(String name, Iterable arguments) => SassString( - "$name(" + - arguments.map((argument) => argument.toCssString()).join(', ') + - ")", - quotes: false); + "$name(" + + arguments.map((argument) => argument.toCssString()).join(', ') + + ")", + quotes: false, + ); /// Returns a [_function] that throws an error indicating that /// `color.adjust()` should be used instead. /// /// This prints a suggested `color.adjust()` call that passes the adjustment /// value to [argument], with a leading minus sign if [negative] is `true`. -BuiltInCallable _removedColorFunction(String name, String argument, - {bool negative = false}) => +BuiltInCallable _removedColorFunction( + String name, + String argument, { + bool negative = false, +}) => _function(name, r"$color, $amount", (arguments) { throw SassScriptException( - "The function $name() isn't in the sass:color module.\n" - "\n" - "Recommendation: color.adjust(${arguments[0]}, \$$argument: " - "${negative ? '-' : ''}${arguments[1]})\n" - "\n" - "More info: https://sass-lang.com/documentation/functions/color#$name"); + "The function $name() isn't in the sass:color module.\n" + "\n" + "Recommendation: color.adjust(${arguments[0]}, \$$argument: " + "${negative ? '-' : ''}${arguments[1]})\n" + "\n" + "More info: https://sass-lang.com/documentation/functions/color#$name", + ); }); /// The implementation of the three- and four-argument `rgb()` and `rgba()` @@ -1089,16 +1368,20 @@ Value _rgb(String name, List arguments) { } return _colorFromChannels( - ColorSpace.rgb, - arguments[0].assertNumber("red"), - arguments[1].assertNumber("green"), - arguments[2].assertNumber("blue"), - alpha.andThen((alpha) => clampLikeCss( - _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"), - 0, - 1)) ?? - 1, - fromRgbFunction: true); + ColorSpace.rgb, + arguments[0].assertNumber("red"), + arguments[1].assertNumber("green"), + arguments[2].assertNumber("blue"), + alpha.andThen( + (alpha) => clampLikeCss( + _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"), + 0, + 1, + ), + ) ?? + 1, + fromRgbFunction: true, + ); } /// The implementation of the two-argument `rgb()` and `rgba()` functions. @@ -1114,10 +1397,11 @@ Value _rgbTwoArg(String name, List arguments) { var color = first.assertColor("color"); if (!color.isLegacy) { throw SassScriptException( - 'Expected $color to be in the legacy RGB, HSL, or HWB color space.\n' - '\n' - 'Recommendation: color.change($color, \$alpha: $second)', - name); + 'Expected $color to be in the legacy RGB, HSL, or HWB color space.\n' + '\n' + 'Recommendation: color.change($color, \$alpha: $second)', + name, + ); } color.assertLegacy("color"); @@ -1127,13 +1411,14 @@ Value _rgbTwoArg(String name, List arguments) { SassNumber(color.channel('red')), SassNumber(color.channel('green')), SassNumber(color.channel('blue')), - arguments[1] + arguments[1], ]); } var alpha = arguments[1].assertNumber("alpha"); return color.changeAlpha( - clampLikeCss(_percentageOrUnitless(alpha, 1, "alpha"), 0, 1)); + clampLikeCss(_percentageOrUnitless(alpha, 1, "alpha"), 0, 1), + ); } /// The implementation of the three- and four-argument `hsl()` and `hsla()` @@ -1148,15 +1433,19 @@ Value _hsl(String name, List arguments) { } return _colorFromChannels( - ColorSpace.hsl, - arguments[0].assertNumber("hue"), - arguments[1].assertNumber("saturation"), - arguments[2].assertNumber("lightness"), - alpha.andThen((alpha) => clampLikeCss( - _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"), - 0, - 1)) ?? - 1); + ColorSpace.hsl, + arguments[0].assertNumber("hue"), + arguments[1].assertNumber("saturation"), + arguments[2].assertNumber("lightness"), + alpha.andThen( + (alpha) => clampLikeCss( + _percentageOrUnitless(alpha.assertNumber("alpha"), 1, "alpha"), + 0, + 1, + ), + ) ?? + 1, + ); } /// Asserts that [angle] is a number and returns its value in degrees. @@ -1167,12 +1456,13 @@ double _angleValue(Value angleValue, String name) { if (angle.compatibleWithUnit('deg')) return angle.coerceValueToUnit('deg'); warnForDeprecation( - "\$$name: Passing a unit other than deg ($angle) is deprecated.\n" - "\n" - "To preserve current behavior: ${angle.unitSuggestion(name)}\n" - "\n" - "See https://sass-lang.com/d/function-units", - Deprecation.functionUnits); + "\$$name: Passing a unit other than deg ($angle) is deprecated.\n" + "\n" + "To preserve current behavior: ${angle.unitSuggestion(name)}\n" + "\n" + "See https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); return angle.value; } @@ -1181,12 +1471,13 @@ void _checkPercent(SassNumber number, String name) { if (number.hasUnit('%')) return; warnForDeprecation( - "\$$name: Passing a number without unit % ($number) is deprecated.\n" - "\n" - "To preserve current behavior: ${number.unitSuggestion(name, '%')}\n" - "\n" - "More info: https://sass-lang.com/d/function-units", - Deprecation.functionUnits); + "\$$name: Passing a number without unit % ($number) is deprecated.\n" + "\n" + "To preserve current behavior: ${number.unitSuggestion(name, '%')}\n" + "\n" + "More info: https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); } /// Asserts that [number] is a percentage or has no units, and normalizes the @@ -1205,7 +1496,9 @@ double _percentageOrUnitless(SassNumber number, double max, [String? name]) { value = max * number.value / 100; } else { throw SassScriptException( - 'Expected $number to have unit "%" or no units.', name); + 'Expected $number to have unit "%" or no units.', + name, + ); } return value; @@ -1252,10 +1545,11 @@ SassColor _mixLegacy(SassColor color1, SassColor color2, SassNumber weight) { var weight2 = 1 - weight1; return SassColor.rgb( - rgb1.channel0 * weight1 + rgb2.channel0 * weight2, - rgb1.channel1 * weight1 + rgb2.channel1 * weight2, - rgb1.channel2 * weight1 + rgb2.channel2 * weight2, - rgb1.alpha * weightScale + rgb2.alpha * (1 - weightScale)); + rgb1.channel0 * weight1 + rgb2.channel0 * weight2, + rgb1.channel1 * weight1 + rgb2.channel1 * weight2, + rgb1.channel2 * weight1 + rgb2.channel2 * weight2, + rgb1.alpha * weightScale + rgb2.alpha * (1 - weightScale), + ); } /// The definition of the `opacify()` and `fade-in()` functions. @@ -1264,19 +1558,26 @@ SassColor _opacify(String name, List arguments) { var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "$name() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "$name() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } - var result = color.changeAlpha(clampLikeCss( - (color.alpha + amount.valueInRangeWithUnit(0, 1, "amount", "")), 0, 1)); + var result = color.changeAlpha( + clampLikeCss( + (color.alpha + amount.valueInRangeWithUnit(0, 1, "amount", "")), + 0, + 1, + ), + ); warnForDeprecation( - "$name() is deprecated. " - "${_suggestScaleAndAdjust(color, amount.value, 'alpha')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "$name() is deprecated. " + "${_suggestScaleAndAdjust(color, amount.value, 'alpha')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; } @@ -1286,19 +1587,26 @@ SassColor _transparentize(String name, List arguments) { var amount = arguments[1].assertNumber("amount"); if (!color.isLegacy) { throw SassScriptException( - "$name() is only supported for legacy colors. Please use " - "color.adjust() instead with an explicit \$space argument."); + "$name() is only supported for legacy colors. Please use " + "color.adjust() instead with an explicit \$space argument.", + ); } - var result = color.changeAlpha(clampLikeCss( - (color.alpha - amount.valueInRangeWithUnit(0, 1, "amount", "")), 0, 1)); + var result = color.changeAlpha( + clampLikeCss( + (color.alpha - amount.valueInRangeWithUnit(0, 1, "amount", "")), + 0, + 1, + ), + ); warnForDeprecation( - "$name() is deprecated. " - "${_suggestScaleAndAdjust(color, -amount.value, 'alpha')}\n" - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "$name() is deprecated. " + "${_suggestScaleAndAdjust(color, -amount.value, 'alpha')}\n" + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; } @@ -1311,16 +1619,21 @@ SassColor _transparentize(String name, List arguments) { /// Throws a [SassScriptException] if either argument isn't the expected type or /// if [spaceUntyped] isn't the name of a color space. If [spaceUntyped] is /// `sassNull`, it defaults to the color's existing space. -SassColor _colorInSpace(Value colorUntyped, Value spaceUntyped, - {bool legacyMissing = true}) { +SassColor _colorInSpace( + Value colorUntyped, + Value spaceUntyped, { + bool legacyMissing = true, +}) { var color = colorUntyped.assertColor("color"); if (spaceUntyped == sassNull) return color; return color.toSpace( - ColorSpace.fromName( - (spaceUntyped.assertString("space")..assertUnquoted("space")).text, - "space"), - legacyMissing: legacyMissing); + ColorSpace.fromName( + (spaceUntyped.assertString("space")..assertUnquoted("space")).text, + "space", + ), + legacyMissing: legacyMissing, + ); } /// Returns the color space named by [space], or throws a [SassScriptException] @@ -1334,7 +1647,9 @@ ColorSpace _spaceOrDefault(SassColor color, Value space, [String? name]) => space == sassNull ? color.space : ColorSpace.fromName( - (space.assertString(name)..assertUnquoted(name)).text, name); + (space.assertString(name)..assertUnquoted(name)).text, + name, + ); /// Parses the color components specified by [input] into a [SassColor], or /// returns an unquoted [SassString] representing the plain CSS function call if @@ -1347,8 +1662,12 @@ ColorSpace _spaceOrDefault(SassColor color, Value space, [String? name]) => /// Throws a [SassScriptException] if [input] is invalid. If [input] came from a /// function argument, [name] is the argument name (without the `$`). It's used /// for error reporting. -Value _parseChannels(String functionName, Value input, - {ColorSpace? space, String? name}) { +Value _parseChannels( + String functionName, + Value input, { + ColorSpace? space, + String? name, +}) { if (input.isVar) return _functionString(functionName, [input]); var parsedSlash = _parseSlashChannels(input, name: name); @@ -1384,9 +1703,10 @@ Value _parseChannels(String functionName, Value input, ColorSpace.oklab || ColorSpace.oklch) { throw SassScriptException( - "The color() function doesn't support the color space $space. Use " - "the $space() function instead.", - name); + "The color() function doesn't support the color space $space. Use " + "the $space() function instead.", + name, + ); } } else { channels = componentList; @@ -1403,7 +1723,9 @@ Value _parseChannels(String functionName, Value input, .andThen((name) => '$name channel') ?? 'channel ${i + 1}'; throw SassScriptException( - 'Expected $channelName to be a number, was $channel.', name); + 'Expected $channelName to be a number, was $channel.', + name, + ); } } @@ -1422,10 +1744,10 @@ Value _parseChannels(String functionName, Value input, null => 1.0, SassString(hasQuotes: false, text: 'none') => null, _ => clampLikeCss( - _percentageOrUnitless(alphaValue.assertNumber(name), 1, 'alpha'), - 0, - 1) - .toDouble() + _percentageOrUnitless(alphaValue.assertNumber(name), 1, 'alpha'), + 0, + 1, + ).toDouble(), }; // `space` will be null if either `components` or `spaceName` is a `var()`. @@ -1434,26 +1756,30 @@ Value _parseChannels(String functionName, Value input, if (space == null) return _functionString(functionName, [input]); if (channels.any((channel) => channel.isSpecialNumber)) { return channels.length == 3 && _specialCommaSpaces.contains(space) - ? _functionString( - functionName, [...channels, if (alphaValue != null) alphaValue]) + ? _functionString(functionName, [ + ...channels, + if (alphaValue != null) alphaValue, + ]) : _functionString(functionName, [input]); } if (channels.length != 3) { throw SassScriptException( - 'The $space color space has 3 channels but $input has ' - '${channels.length}.', - name); + 'The $space color space has 3 channels but $input has ' + '${channels.length}.', + name, + ); } return _colorFromChannels( - space, - // If a channel isn't a number, it must be `none`. - castOrNull(channels[0]), - castOrNull(channels[1]), - castOrNull(channels[2]), - alpha, - fromRgbFunction: space == ColorSpace.rgb); + space, + // If a channel isn't a number, it must be `none`. + castOrNull(channels[0]), + castOrNull(channels[1]), + castOrNull(channels[2]), + alpha, + fromRgbFunction: space == ColorSpace.rgb, + ); } /// Parses [input]'s slash-separated third number and alpha value, if one @@ -1466,32 +1792,37 @@ Value _parseChannels(String functionName, Value input, /// Throws a [SassScriptException] if [input] is invalid. If [input] came from a /// function argument, [name] is the argument name (without the `$`). It's used /// for error reporting. -(Value components, Value? alpha)? _parseSlashChannels(Value input, - {String? name}) => +(Value components, Value? alpha)? _parseSlashChannels( + Value input, { + String? name, +}) => switch (input.assertCommonListStyle(name, allowSlash: true)) { [var components, var alphaValue] when input.separator == ListSeparator.slash => (components, alphaValue), var inputList when input.separator == ListSeparator.slash => throw SassScriptException( - "Only 2 slash-separated elements allowed, but ${inputList.length} " - "${pluralize('was', inputList.length, plural: 'were')} passed.", - name), + "Only 2 slash-separated elements allowed, but ${inputList.length} " + "${pluralize('was', inputList.length, plural: 'were')} passed.", + name, + ), [...var initial, SassString(hasQuotes: false, :var text)] => switch ( text.split('/')) { [_] => (input, null), [var channel3, var alpha] => ( - SassList([...initial, _parseNumberOrString(channel3)], - ListSeparator.space), - _parseNumberOrString(alpha) + SassList([ + ...initial, + _parseNumberOrString(channel3), + ], ListSeparator.space), + _parseNumberOrString(alpha), ), - _ => null + _ => null, }, [...var initial, SassNumber(asSlash: (var before, var after))] => ( SassList([...initial, before], ListSeparator.space), - after + after, ), - _ => (input, null) + _ => (input, null), }; /// Parses [text] as either a Sass number or an unquoted Sass string. @@ -1507,20 +1838,33 @@ Value _parseNumberOrString(String text) { /// or throws a [SassScriptException] if the channel values are invalid. /// /// If [clamp] is true, this will clamp any clamped channels. -SassColor _colorFromChannels(ColorSpace space, SassNumber? channel0, - SassNumber? channel1, SassNumber? channel2, double? alpha, - {bool clamp = true, bool fromRgbFunction = false}) { +SassColor _colorFromChannels( + ColorSpace space, + SassNumber? channel0, + SassNumber? channel1, + SassNumber? channel2, + double? alpha, { + bool clamp = true, + bool fromRgbFunction = false, +}) { switch (space) { case ColorSpace.hsl: if (channel1 != null) _checkPercent(channel1, 'saturation'); if (channel2 != null) _checkPercent(channel2, 'lightness'); return SassColor.hsl( - channel0.andThen((channel0) => _angleValue(channel0, 'hue')), - _channelFromValue(space.channels[1], _forcePercent(channel1), - clamp: clamp), - _channelFromValue(space.channels[2], _forcePercent(channel2), - clamp: clamp), - alpha); + channel0.andThen((channel0) => _angleValue(channel0, 'hue')), + _channelFromValue( + space.channels[1], + _forcePercent(channel1), + clamp: clamp, + ), + _channelFromValue( + space.channels[2], + _forcePercent(channel2), + clamp: clamp, + ), + alpha, + ); case ColorSpace.hwb: channel1?.assertUnit('%', 'whiteness'); @@ -1537,26 +1881,29 @@ SassColor _colorFromChannels(ColorSpace space, SassNumber? channel0, } return SassColor.hwb( - channel0.andThen((channel0) => _angleValue(channel0, 'hue')), - whiteness, - blackness, - alpha); + channel0.andThen((channel0) => _angleValue(channel0, 'hue')), + whiteness, + blackness, + alpha, + ); case ColorSpace.rgb: return SassColor.rgbInternal( - _channelFromValue(space.channels[0], channel0, clamp: clamp), - _channelFromValue(space.channels[1], channel1, clamp: clamp), - _channelFromValue(space.channels[2], channel2, clamp: clamp), - alpha, - fromRgbFunction ? ColorFormat.rgbFunction : null); + _channelFromValue(space.channels[0], channel0, clamp: clamp), + _channelFromValue(space.channels[1], channel1, clamp: clamp), + _channelFromValue(space.channels[2], channel2, clamp: clamp), + alpha, + fromRgbFunction ? ColorFormat.rgbFunction : null, + ); default: return SassColor.forSpaceInternal( - space, - _channelFromValue(space.channels[0], channel0, clamp: clamp), - _channelFromValue(space.channels[1], channel1, clamp: clamp), - _channelFromValue(space.channels[2], channel2, clamp: clamp), - alpha); + space, + _channelFromValue(space.channels[0], channel0, clamp: clamp), + _channelFromValue(space.channels[1], channel1, clamp: clamp), + _channelFromValue(space.channels[2], channel2, clamp: clamp), + alpha, + ); } } @@ -1564,7 +1911,7 @@ SassColor _colorFromChannels(ColorSpace space, SassNumber? channel0, SassNumber? _forcePercent(SassNumber? number) => switch (number) { null => null, SassNumber(numeratorUnits: ['%'], denominatorUnits: []) => number, - _ => SassNumber(number.value, '%') + _ => SassNumber(number.value, '%'), }; /// Converts a channel value from a [SassNumber] into a [double] according to @@ -1572,22 +1919,33 @@ SassNumber? _forcePercent(SassNumber? number) => switch (number) { /// /// If [clamp] is true, this clamps [value] according to [channel]'s clamping /// rules. -double? _channelFromValue(ColorChannel channel, SassNumber? value, - {bool clamp = true}) => - value.andThen((value) => switch (channel) { - LinearChannel(requiresPercent: true) when !value.hasUnit('%') => - throw SassScriptException( - 'Expected $value to have unit "%".', channel.name), - LinearChannel(lowerClamped: false, upperClamped: false) => - _percentageOrUnitless(value, channel.max, channel.name), - LinearChannel() when !clamp => +double? _channelFromValue( + ColorChannel channel, + SassNumber? value, { + bool clamp = true, +}) => + value.andThen( + (value) => switch (channel) { + LinearChannel(requiresPercent: true) when !value.hasUnit('%') => + throw SassScriptException( + 'Expected $value to have unit "%".', + channel.name, + ), + LinearChannel(lowerClamped: false, upperClamped: false) => + _percentageOrUnitless(value, channel.max, channel.name), + LinearChannel() when !clamp => _percentageOrUnitless( + value, + channel.max, + channel.name, + ), + LinearChannel(:var lowerClamped, :var upperClamped) => clampLikeCss( _percentageOrUnitless(value, channel.max, channel.name), - LinearChannel(:var lowerClamped, :var upperClamped) => clampLikeCss( - _percentageOrUnitless(value, channel.max, channel.name), - lowerClamped ? channel.min : double.negativeInfinity, - upperClamped ? channel.max : double.infinity), - _ => value.coerceValueToUnit('deg', channel.name) % 360 - }); + lowerClamped ? channel.min : double.negativeInfinity, + upperClamped ? channel.max : double.infinity, + ), + _ => value.coerceValueToUnit('deg', channel.name) % 360, + }, + ); /// Returns whether [value] is an unquoted string case-insensitively equal to /// "none". @@ -1602,18 +1960,23 @@ bool _isNone(Value value) => /// If [unit] is passed, the channel is returned with that unit. The [global] /// parameter indicates whether this was called using the legacy global syntax. BuiltInCallable _channelFunction( - String name, ColorSpace space, num Function(SassColor color) getter, - {String? unit, bool global = false}) { + String name, + ColorSpace space, + num Function(SassColor color) getter, { + String? unit, + bool global = false, +}) { return _function(name, r"$color", (arguments) { var result = SassNumber(getter(arguments.first.assertColor("color")), unit); warnForDeprecation( - "${global ? '' : 'color.'}$name() is deprecated. Suggestion:\n" - "\n" - 'color.channel(\$color, "$name", \$space: $space)\n' - "\n" - "More info: https://sass-lang.com/d/color-functions", - Deprecation.colorFunctions); + "${global ? '' : 'color.'}$name() is deprecated. Suggestion:\n" + "\n" + 'color.channel(\$color, "$name", \$space: $space)\n' + "\n" + "More info: https://sass-lang.com/d/color-functions", + Deprecation.colorFunctions, + ); return result; }); @@ -1625,13 +1988,16 @@ BuiltInCallable _channelFunction( /// [original] is the color that was passed in, [adjustment] is the requested /// change, and [channelName] is the name of the modified channel. String _suggestScaleAndAdjust( - SassColor original, double adjustment, String channelName) { + SassColor original, + double adjustment, + String channelName, +) { assert(original.isLegacy); var channel = channelName == 'alpha' ? ColorChannel.alpha - : ColorSpace.hsl.channels - .firstWhere((channel) => channel.name == channelName) - as LinearChannel; + : ColorSpace.hsl.channels.firstWhere( + (channel) => channel.name == channelName, + ) as LinearChannel; var oldValue = channel == ColorChannel.alpha ? original.alpha @@ -1658,8 +2024,10 @@ String _suggestScaleAndAdjust( suggestion += ":\n\n"; } - var difference = - SassNumber(adjustment, channel == ColorChannel.alpha ? null : '%'); + var difference = SassNumber( + adjustment, + channel == ColorChannel.alpha ? null : '%', + ); return suggestion + "color.adjust(\$color, \$$channelName: $difference)"; } @@ -1667,10 +2035,11 @@ String _suggestScaleAndAdjust( /// modified. Never _missingChannelError(SassColor color, String channel) => throw SassScriptException( - "Because the CSS working group is still deciding on the best behavior, " - "Sass doesn't currently support modifying missing channels (color: " - "$color).", - channel); + "Because the CSS working group is still deciding on the best behavior, " + "Sass doesn't currently support modifying missing channels (color: " + "$color).", + channel, + ); /// Asserts that `value` is an unquoted string and throws an error if it's not. /// @@ -1681,5 +2050,8 @@ String _channelName(Value value) => /// Like [BuiltInCallable.function], but always sets the URL to /// `sass:color`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:color"); diff --git a/lib/src/functions/list.dart b/lib/src/functions/list.dart index c0fd0ac2c..d8003c098 100644 --- a/lib/src/functions/list.dart +++ b/lib/src/functions/list.dart @@ -21,17 +21,23 @@ final global = UnmodifiableListView([ _zip.withDeprecationWarning('list'), _index.withDeprecationWarning('list'), _isBracketed.withDeprecationWarning('list'), - _separator.withDeprecationWarning('list').withName("list-separator") + _separator.withDeprecationWarning('list').withName("list-separator"), ]); /// The Sass list module. -final module = BuiltInModule("list", functions: [ - _length, _nth, _setNth, _join, _append, _zip, _index, _isBracketed, // - _separator, _slash -]); +final module = BuiltInModule( + "list", + functions: [ + _length, _nth, _setNth, _join, _append, _zip, _index, _isBracketed, // + _separator, _slash, + ], +); final _length = _function( - "length", r"$list", (arguments) => SassNumber(arguments[0].asList.length)); + "length", + r"$list", + (arguments) => SassNumber(arguments[0].asList.length), +); final _nth = _function("nth", r"$list, $n", (arguments) { var list = arguments[0]; @@ -49,37 +55,43 @@ final _setNth = _function("set-nth", r"$list, $n, $value", (arguments) { }); final _join = _function( - "join", r"$list1, $list2, $separator: auto, $bracketed: auto", (arguments) { - var list1 = arguments[0]; - var list2 = arguments[1]; - var separatorParam = arguments[2].assertString("separator"); - var bracketedParam = arguments[3]; - - var separator = switch (separatorParam.text) { - "auto" => switch ((list1.separator, list2.separator)) { - (ListSeparator.undecided, ListSeparator.undecided) => - ListSeparator.space, - (ListSeparator.undecided, var separator) || - (var separator, _) => - separator - }, - "space" => ListSeparator.space, - "comma" => ListSeparator.comma, - "slash" => ListSeparator.slash, - _ => throw SassScriptException( - '\$separator: Must be "space", "comma", "slash", or "auto".') - }; - - var bracketed = bracketedParam is SassString && bracketedParam.text == 'auto' - ? list1.hasBrackets - : bracketedParam.isTruthy; - - var newList = [...list1.asList, ...list2.asList]; - return SassList(newList, separator, brackets: bracketed); -}); - -final _append = - _function("append", r"$list, $val, $separator: auto", (arguments) { + "join", + r"$list1, $list2, $separator: auto, $bracketed: auto", + (arguments) { + var list1 = arguments[0]; + var list2 = arguments[1]; + var separatorParam = arguments[2].assertString("separator"); + var bracketedParam = arguments[3]; + + var separator = switch (separatorParam.text) { + "auto" => switch ((list1.separator, list2.separator)) { + (ListSeparator.undecided, ListSeparator.undecided) => + ListSeparator.space, + (ListSeparator.undecided, var separator) || + (var separator, _) => + separator, + }, + "space" => ListSeparator.space, + "comma" => ListSeparator.comma, + "slash" => ListSeparator.slash, + _ => throw SassScriptException( + '\$separator: Must be "space", "comma", "slash", or "auto".', + ), + }; + + var bracketed = + bracketedParam is SassString && bracketedParam.text == 'auto' + ? list1.hasBrackets + : bracketedParam.isTruthy; + + var newList = [...list1.asList, ...list2.asList]; + return SassList(newList, separator, brackets: bracketed); + }, +); + +final _append = _function("append", r"$list, $val, $separator: auto", ( + arguments, +) { var list = arguments[0]; var value = arguments[1]; var separatorParam = arguments[2].assertString("separator"); @@ -92,7 +104,8 @@ final _append = "comma" => ListSeparator.comma, "slash" => ListSeparator.slash, _ => throw SassScriptException( - '\$separator: Must be "space", "comma", "slash", or "auto".') + '\$separator: Must be "space", "comma", "slash", or "auto".', + ), }; var newList = [...list.asList, value]; @@ -123,16 +136,20 @@ final _index = _function("index", r"$list, $value", (arguments) { }); final _separator = _function( - "separator", - r"$list", - (arguments) => switch (arguments[0].separator) { - ListSeparator.comma => SassString("comma", quotes: false), - ListSeparator.slash => SassString("slash", quotes: false), - _ => SassString("space", quotes: false) - }); - -final _isBracketed = _function("is-bracketed", r"$list", - (arguments) => SassBoolean(arguments[0].hasBrackets)); + "separator", + r"$list", + (arguments) => switch (arguments[0].separator) { + ListSeparator.comma => SassString("comma", quotes: false), + ListSeparator.slash => SassString("slash", quotes: false), + _ => SassString("space", quotes: false), + }, +); + +final _isBracketed = _function( + "is-bracketed", + r"$list", + (arguments) => SassBoolean(arguments[0].hasBrackets), +); final _slash = _function("slash", r"$elements...", (arguments) { var list = arguments[0].asList; @@ -145,5 +162,8 @@ final _slash = _function("slash", r"$elements...", (arguments) { /// Like [BuiltInCallable.function], but always sets the URL to `sass:list`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:list"); diff --git a/lib/src/functions/map.dart b/lib/src/functions/map.dart index 099f20a72..a3f982d24 100644 --- a/lib/src/functions/map.dart +++ b/lib/src/functions/map.dart @@ -20,21 +20,24 @@ final global = UnmodifiableListView([ _remove.withDeprecationWarning('map').withName("map-remove"), _keys.withDeprecationWarning('map').withName("map-keys"), _values.withDeprecationWarning('map').withName("map-values"), - _hasKey.withDeprecationWarning('map').withName("map-has-key") + _hasKey.withDeprecationWarning('map').withName("map-has-key"), ]); /// The Sass map module. -final module = BuiltInModule("map", functions: [ - _get, - _set, - _merge, - _remove, - _keys, - _values, - _hasKey, - _deepMerge, - _deepRemove -]); +final module = BuiltInModule( + "map", + functions: [ + _get, + _set, + _merge, + _remove, + _keys, + _values, + _hasKey, + _deepMerge, + _deepRemove, + ], +); final _get = _function("get", r"$map, $key, $keys...", (arguments) { var map = arguments[0].assertMap("map"); @@ -115,8 +118,9 @@ final _deepMerge = _function("deep-merge", r"$map1, $map2", (arguments) { return _deepMergeImpl(map1, map2); }); -final _deepRemove = - _function("deep-remove", r"$map, $key, $keys...", (arguments) { +final _deepRemove = _function("deep-remove", r"$map, $key, $keys...", ( + arguments, +) { var map = arguments[0].assertMap("map"); var keys = [arguments[1], ...arguments[2].asList]; return _modify(map, keys.exceptLast, (value) { @@ -144,20 +148,26 @@ final _remove = BuiltInCallable.overloadedFunction("remove", { mutableMap.remove(key); } return SassMap(mutableMap); - } + }, }); final _keys = _function( - "keys", - r"$map", - (arguments) => SassList( - arguments[0].assertMap("map").contents.keys, ListSeparator.comma)); + "keys", + r"$map", + (arguments) => SassList( + arguments[0].assertMap("map").contents.keys, + ListSeparator.comma, + ), +); final _values = _function( - "values", - r"$map", - (arguments) => SassList( - arguments[0].assertMap("map").contents.values, ListSeparator.comma)); + "values", + r"$map", + (arguments) => SassList( + arguments[0].assertMap("map").contents.values, + ListSeparator.comma, + ), +); final _hasKey = _function("has-key", r"$map, $key, $keys...", (arguments) { var map = arguments[0].assertMap("map"); @@ -184,8 +194,12 @@ final _hasKey = _function("has-key", r"$map, $key, $keys...", (arguments) { /// /// If no keys are provided, this passes [map] directly to modify and returns /// the result. -Value _modify(SassMap map, Iterable keys, Value modify(Value old), - {bool addNesting = true}) { +Value _modify( + SassMap map, + Iterable keys, + Value modify(Value old), { + bool addNesting = true, +}) { var keyIterator = keys.iterator; SassMap modifyNestedMap(SassMap map) { var mutableMap = Map.of(map.contents); @@ -217,7 +231,10 @@ SassMap _deepMergeImpl(SassMap map1, SassMap map2) { var result = Map.of(map1.contents); for (var (key, value) in map2.contents.pairs) { if ((result[key]?.tryMap(), value.tryMap()) - case (var resultMap?, var valueMap?)) { + case ( + var resultMap?, + var valueMap?, + )) { var merged = _deepMergeImpl(resultMap, valueMap); if (identical(merged, resultMap)) continue; result[key] = merged; @@ -231,5 +248,8 @@ SassMap _deepMergeImpl(SassMap map1, SassMap map2) { /// Like [BuiltInCallable.function], but always sets the URL to `sass:map`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:map"); diff --git a/lib/src/functions/math.dart b/lib/src/functions/math.dart index 3f3e3bbbe..6cb1540ef 100644 --- a/lib/src/functions/math.dart +++ b/lib/src/functions/math.dart @@ -21,21 +21,24 @@ final global = UnmodifiableListView([ var number = arguments[0].assertNumber("number"); if (number.hasUnit("%")) { warnForDeprecation( - "Passing percentage units to the global abs() function is " - "deprecated.\n" - "In the future, this will emit a CSS abs() function to be resolved " - "by the browser.\n" - "To preserve current behavior: math.abs($number)" - "\n" - "To emit a CSS abs() now: abs(#{$number})\n" - "More info: https://sass-lang.com/d/abs-percent", - Deprecation.absPercent); + "Passing percentage units to the global abs() function is " + "deprecated.\n" + "In the future, this will emit a CSS abs() function to be resolved " + "by the browser.\n" + "To preserve current behavior: math.abs($number)" + "\n" + "To emit a CSS abs() now: abs(#{$number})\n" + "More info: https://sass-lang.com/d/abs-percent", + Deprecation.absPercent, + ); } else { warnForGlobalBuiltIn('math', 'abs'); } - return SassNumber.withUnits(number.value.abs(), - numeratorUnits: number.numeratorUnits, - denominatorUnits: number.denominatorUnits); + return SassNumber.withUnits( + number.value.abs(), + numeratorUnits: number.numeratorUnits, + denominatorUnits: number.denominatorUnits, + ); }), _ceil.withDeprecationWarning('math'), _floor.withDeprecationWarning('math'), @@ -50,20 +53,24 @@ final global = UnmodifiableListView([ ]); /// The Sass math module. -final module = BuiltInModule("math", functions: [ - _numberFunction("abs", (value) => value.abs()), - _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, _floor, // - _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, _randomFunction, - _round, _sin, _sqrt, _tan, _unit, _div -], variables: { - "e": SassNumber(math.e), - "pi": SassNumber(math.pi), - "epsilon": SassNumber(2.220446049250313e-16), - "max-safe-integer": SassNumber(9007199254740991), - "min-safe-integer": SassNumber(-9007199254740991), - "max-number": SassNumber(double.maxFinite), - "min-number": SassNumber(double.minPositive), -}); +final module = BuiltInModule( + "math", + functions: [ + _numberFunction("abs", (value) => value.abs()), + _acos, _asin, _atan, _atan2, _ceil, _clamp, _cos, _compatible, _floor, // + _hypot, _isUnitless, _log, _max, _min, _percentage, _pow, _randomFunction, + _round, _sin, _sqrt, _tan, _unit, _div, + ], + variables: { + "e": SassNumber(math.e), + "pi": SassNumber(math.pi), + "epsilon": SassNumber(2.220446049250313e-16), + "max-safe-integer": SassNumber(9007199254740991), + "min-safe-integer": SassNumber(-9007199254740991), + "max-number": SassNumber(double.maxFinite), + "min-number": SassNumber(double.minPositive), + }, +); /// /// Bounding functions @@ -127,12 +134,17 @@ final _hypot = _function("hypot", r"$numbers...", (arguments) { for (var i = 0; i < numbers.length; i++) { var number = numbers[i]; var value = number.convertValueToMatch( - numbers[0], "numbers[${i + 1}]", "numbers[1]"); + numbers[0], + "numbers[${i + 1}]", + "numbers[1]", + ); subtotal += math.pow(value, 2); } - return SassNumber.withUnits(math.sqrt(subtotal), - numeratorUnits: numbers[0].numeratorUnits, - denominatorUnits: numbers[0].denominatorUnits); + return SassNumber.withUnits( + math.sqrt(subtotal), + numeratorUnits: numbers[0].numeratorUnits, + denominatorUnits: numbers[0].denominatorUnits, + ); }); /// @@ -223,17 +235,18 @@ final _randomFunction = _function("random", r"$limit: null", (arguments) { if (limit.hasUnits) { warnForDeprecation( - "math.random() will no longer ignore \$limit units ($limit) in a " - "future release.\n" - "\n" - "Recommendation: " - "math.random(math.div(\$limit, 1${limit.unitString})) * 1${limit.unitString}\n" - "\n" - "To preserve current behavior: " - "math.random(math.div(\$limit, 1${limit.unitString}))\n" - "\n" - "More info: https://sass-lang.com/d/function-units", - Deprecation.functionUnits); + "math.random() will no longer ignore \$limit units ($limit) in a " + "future release.\n" + "\n" + "Recommendation: " + "math.random(math.div(\$limit, 1${limit.unitString})) * 1${limit.unitString}\n" + "\n" + "To preserve current behavior: " + "math.random(math.div(\$limit, 1${limit.unitString}))\n" + "\n" + "More info: https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); } var limitScalar = limit.assertInt("limit"); @@ -248,8 +261,10 @@ final _div = _function("div", r"$number1, $number2", (arguments) { var number2 = arguments[1]; if (number1 is! SassNumber || number2 is! SassNumber) { - warn("math.div() will only support number arguments in a future release.\n" - "Use list.slash() instead for a slash separator."); + warn( + "math.div() will only support number arguments in a future release.\n" + "Use list.slash() instead for a slash separator.", + ); } return number1.dividedBy(number2); @@ -262,7 +277,9 @@ final _div = _function("div", r"$number1, $number2", (arguments) { /// Returns a [Callable] named [name] that calls a single argument /// math function. BuiltInCallable _singleArgumentMathFunc( - String name, SassNumber mathFunc(SassNumber value)) { + String name, + SassNumber mathFunc(SassNumber value), +) { return _function(name, r"$number", (arguments) { var number = arguments[0].assertNumber("number"); return mathFunc(number); @@ -274,13 +291,18 @@ BuiltInCallable _singleArgumentMathFunc( BuiltInCallable _numberFunction(String name, double transform(double value)) { return _function(name, r"$number", (arguments) { var number = arguments[0].assertNumber("number"); - return SassNumber.withUnits(transform(number.value), - numeratorUnits: number.numeratorUnits, - denominatorUnits: number.denominatorUnits); + return SassNumber.withUnits( + transform(number.value), + numeratorUnits: number.numeratorUnits, + denominatorUnits: number.denominatorUnits, + ); }); } /// Like [_function.function], but always sets the URL to `sass:math`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:math"); diff --git a/lib/src/functions/meta.dart b/lib/src/functions/meta.dart index 03cfe4c87..01b58fabb 100644 --- a/lib/src/functions/meta.dart +++ b/lib/src/functions/meta.dart @@ -20,7 +20,7 @@ final _features = { "extend-selector-pseudoclass", "units-level-3", "at-error", - "custom-property" + "custom-property", }; /// Sass introspection functions that exist as both global functions and in the @@ -34,53 +34,59 @@ final _shared = UnmodifiableListView([ // runtime. _function("feature-exists", r"$feature", (arguments) { warnForDeprecation( - "The feature-exists() function is deprecated.\n\n" - "More info: https://sass-lang.com/d/feature-exists", - Deprecation.featureExists); + "The feature-exists() function is deprecated.\n\n" + "More info: https://sass-lang.com/d/feature-exists", + Deprecation.featureExists, + ); var feature = arguments[0].assertString("feature"); return SassBoolean(_features.contains(feature.text)); }), _function( - "inspect", - r"$value", - (arguments) => SassString(serializeValue(arguments.first, inspect: true), - quotes: false)), + "inspect", + r"$value", + (arguments) => SassString( + serializeValue(arguments.first, inspect: true), + quotes: false, + ), + ), _function( - "type-of", - r"$value", - (arguments) => SassString( - switch (arguments[0]) { - SassArgumentList() => "arglist", - SassBoolean() => "bool", - SassColor() => "color", - SassList() => "list", - SassMap() => "map", - sassNull => "null", - SassNumber() => "number", - SassFunction() => "function", - SassMixin() => "mixin", - SassCalculation() => "calculation", - SassString() => "string", - _ => throw "[BUG] Unknown value type ${arguments[0]}" - }, - quotes: false)), + "type-of", + r"$value", + (arguments) => SassString( + switch (arguments[0]) { + SassArgumentList() => "arglist", + SassBoolean() => "bool", + SassColor() => "color", + SassList() => "list", + SassMap() => "map", + sassNull => "null", + SassNumber() => "number", + SassFunction() => "function", + SassMixin() => "mixin", + SassCalculation() => "calculation", + SassString() => "string", + _ => throw "[BUG] Unknown value type ${arguments[0]}", + }, + quotes: false), + ), _function("keywords", r"$args", (arguments) { if (arguments[0] case SassArgumentList(:var keywords)) { return SassMap({ for (var (key, value) in keywords.pairs) - SassString(key, quotes: false): value + SassString(key, quotes: false): value, }); } else { throw "\$args: ${arguments[0]} is not an argument list."; } - }) + }), ]); /// The global definitions of Sass introspection functions. -final global = UnmodifiableListView( - [for (var function in _shared) function.withDeprecationWarning('meta')]); +final global = UnmodifiableListView([ + for (var function in _shared) function.withDeprecationWarning('meta'), +]); /// The versions of Sass introspection functions defined in the `sass:meta` /// module. @@ -93,10 +99,13 @@ final moduleFunctions = UnmodifiableListView([ _function("calc-args", r"$calc", (arguments) { var calculation = arguments[0].assertCalculation("calc"); return SassList( - calculation.arguments.map((argument) => argument is Value + calculation.arguments.map( + (argument) => argument is Value ? argument - : SassString(argument.toString(), quotes: false)), - ListSeparator.comma); + : SassString(argument.toString(), quotes: false), + ), + ListSeparator.comma, + ); }), _function("accepts-content", r"$mixin", (arguments) { var mixin = arguments[0].assertMixin("mixin"); @@ -106,12 +115,15 @@ final moduleFunctions = UnmodifiableListView([ acceptsContent, UserDefinedCallable(declaration: MixinRule(hasContent: var hasContent)) => hasContent, - _ => throw UnsupportedError("Unknown callable type $mixin.") + _ => throw UnsupportedError("Unknown callable type $mixin."), }); - }) + }), ]); /// Like [BuiltInCallable.function], but always sets the URL to `sass:meta`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:meta"); diff --git a/lib/src/functions/selector.dart b/lib/src/functions/selector.dart index 7a32af1a2..cd499e4fe 100644 --- a/lib/src/functions/selector.dart +++ b/lib/src/functions/selector.dart @@ -23,26 +23,30 @@ final global = UnmodifiableListView([ _append.withDeprecationWarning('selector').withName("selector-append"), _extend.withDeprecationWarning('selector').withName("selector-extend"), _replace.withDeprecationWarning('selector').withName("selector-replace"), - _unify.withDeprecationWarning('selector').withName("selector-unify") + _unify.withDeprecationWarning('selector').withName("selector-unify"), ]); /// The Sass selector module. -final module = BuiltInModule("selector", functions: [ - _isSuperselector, - _simpleSelectors, - _parse, - _nest, - _append, - _extend, - _replace, - _unify -]); +final module = BuiltInModule( + "selector", + functions: [ + _isSuperselector, + _simpleSelectors, + _parse, + _nest, + _append, + _extend, + _replace, + _unify, + ], +); final _nest = _function("nest", r"$selectors...", (arguments) { var selectors = arguments[0].asList; if (selectors.isEmpty) { throw SassScriptException( - "\$selectors: At least one selector must be passed."); + "\$selectors: At least one selector must be passed.", + ); } var first = true; @@ -60,35 +64,40 @@ final _append = _function("append", r"$selectors...", (arguments) { var selectors = arguments[0].asList; if (selectors.isEmpty) { throw SassScriptException( - "\$selectors: At least one selector must be passed."); + "\$selectors: At least one selector must be passed.", + ); } var span = EvaluationContext.current.currentCallableSpan; - return selectors - .map((selector) => selector.assertSelector()) - .reduce((parent, child) { - return SelectorList(child.components.map((complex) { - if (complex.leadingCombinators.isNotEmpty) { - throw SassScriptException("Can't append $complex to $parent."); - } - - var [component, ...rest] = complex.components; - var newCompound = _prependParent(component.selector); - if (newCompound == null) { - throw SassScriptException("Can't append $complex to $parent."); - } - - return ComplexSelector(const [], [ - ComplexSelectorComponent(newCompound, component.combinators, span), - ...rest - ], span); - }), span) - .nestWithin(parent); + return selectors.map((selector) => selector.assertSelector()).reduce(( + parent, + child, + ) { + return SelectorList( + child.components.map((complex) { + if (complex.leadingCombinators.isNotEmpty) { + throw SassScriptException("Can't append $complex to $parent."); + } + + var [component, ...rest] = complex.components; + var newCompound = _prependParent(component.selector); + if (newCompound == null) { + throw SassScriptException("Can't append $complex to $parent."); + } + + return ComplexSelector(const [], [ + ComplexSelectorComponent(newCompound, component.combinators, span), + ...rest, + ], span); + }), + span, + ).nestWithin(parent); }).asSassList; }); -final _extend = - _function("extend", r"$selector, $extendee, $extender", (arguments) { +final _extend = _function("extend", r"$selector, $extendee, $extender", ( + arguments, +) { var selector = arguments[0].assertSelector(name: "selector") ..assertNotBogus(name: "selector"); var target = arguments[1].assertSelector(name: "extendee") @@ -96,13 +105,17 @@ final _extend = var source = arguments[2].assertSelector(name: "extender") ..assertNotBogus(name: "extender"); - return ExtensionStore.extend(selector, source, target, - EvaluationContext.current.currentCallableSpan) - .asSassList; + return ExtensionStore.extend( + selector, + source, + target, + EvaluationContext.current.currentCallableSpan, + ).asSassList; }); -final _replace = - _function("replace", r"$selector, $original, $replacement", (arguments) { +final _replace = _function("replace", r"$selector, $original, $replacement", ( + arguments, +) { var selector = arguments[0].assertSelector(name: "selector") ..assertNotBogus(name: "selector"); var target = arguments[1].assertSelector(name: "original") @@ -110,9 +123,12 @@ final _replace = var source = arguments[2].assertSelector(name: "replacement") ..assertNotBogus(name: "replacement"); - return ExtensionStore.replace(selector, source, target, - EvaluationContext.current.currentCallableSpan) - .asSassList; + return ExtensionStore.replace( + selector, + source, + target, + EvaluationContext.current.currentCallableSpan, + ).asSassList; }); final _unify = _function("unify", r"$selector1, $selector2", (arguments) { @@ -124,8 +140,9 @@ final _unify = _function("unify", r"$selector1, $selector2", (arguments) { return selector1.unify(selector2)?.asSassList ?? sassNull; }); -final _isSuperselector = - _function("is-superselector", r"$super, $sub", (arguments) { +final _isSuperselector = _function("is-superselector", r"$super, $sub", ( + arguments, +) { var selector1 = arguments[0].assertSelector(name: "super") ..assertNotBogus(name: "super"); var selector2 = arguments[1].assertSelector(name: "sub") @@ -134,18 +151,24 @@ final _isSuperselector = return SassBoolean(selector1.isSuperselector(selector2)); }); -final _simpleSelectors = - _function("simple-selectors", r"$selector", (arguments) { +final _simpleSelectors = _function("simple-selectors", r"$selector", ( + arguments, +) { var selector = arguments[0].assertCompoundSelector(name: "selector"); return SassList( - selector.components - .map((simple) => SassString(simple.toString(), quotes: false)), - ListSeparator.comma); + selector.components.map( + (simple) => SassString(simple.toString(), quotes: false), + ), + ListSeparator.comma, + ); }); -final _parse = _function("parse", r"$selector", - (arguments) => arguments[0].assertSelector(name: "selector").asSassList); +final _parse = _function( + "parse", + r"$selector", + (arguments) => arguments[0].assertSelector(name: "selector").asSassList, +); /// Adds a [ParentSelector] to the beginning of [compound], or returns `null` if /// that wouldn't produce a valid selector. @@ -154,15 +177,22 @@ CompoundSelector? _prependParent(CompoundSelector compound) { return switch (compound.components) { [UniversalSelector(), ...] => null, [TypeSelector type, ...] when type.name.namespace != null => null, - [TypeSelector type, ...var rest] => CompoundSelector( - [ParentSelector(span, suffix: type.name.name), ...rest], span), - var components => - CompoundSelector([ParentSelector(span), ...components], span) + [TypeSelector type, ...var rest] => CompoundSelector([ + ParentSelector(span, suffix: type.name.name), + ...rest, + ], span), + var components => CompoundSelector([ + ParentSelector(span), + ...components, + ], span), }; } /// Like [BuiltInCallable.function], but always sets the URL to /// `sass:selector`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:selector"); diff --git a/lib/src/functions/string.dart b/lib/src/functions/string.dart index 100337de9..49e9da4c7 100644 --- a/lib/src/functions/string.dart +++ b/lib/src/functions/string.dart @@ -30,52 +30,62 @@ final global = UnmodifiableListView([ _length.withDeprecationWarning('string').withName("str-length"), _insert.withDeprecationWarning('string').withName("str-insert"), _index.withDeprecationWarning('string').withName("str-index"), - _slice.withDeprecationWarning('string').withName("str-slice") + _slice.withDeprecationWarning('string').withName("str-slice"), ]); /// The Sass string module. -final module = BuiltInModule("string", functions: [ - _unquote, _quote, _toUpperCase, _toLowerCase, _length, _insert, _index, // - _slice, _uniqueId, - - _function("split", r"$string, $separator, $limit: null", (arguments) { - var string = arguments[0].assertString("string"); - var separator = arguments[1].assertString("separator"); - var limit = arguments[2].realNull?.assertNumber("limit").assertInt("limit"); - - if (limit != null && limit < 1) { - throw SassScriptException("\$limit: Must be 1 or greater, was $limit."); - } - - if (string.text.isEmpty) { - return const SassList.empty( - separator: ListSeparator.comma, brackets: true); - } else if (separator.text.isEmpty) { - return SassList( - string.text.runes.map((rune) => - SassString(String.fromCharCode(rune), quotes: string.hasQuotes)), +final module = BuiltInModule( + "string", + functions: [ + _unquote, _quote, _toUpperCase, _toLowerCase, _length, _insert, _index, // + _slice, _uniqueId, + + _function("split", r"$string, $separator, $limit: null", (arguments) { + var string = arguments[0].assertString("string"); + var separator = arguments[1].assertString("separator"); + var limit = + arguments[2].realNull?.assertNumber("limit").assertInt("limit"); + + if (limit != null && limit < 1) { + throw SassScriptException("\$limit: Must be 1 or greater, was $limit."); + } + + if (string.text.isEmpty) { + return const SassList.empty( + separator: ListSeparator.comma, + brackets: true, + ); + } else if (separator.text.isEmpty) { + return SassList( + string.text.runes.map( + (rune) => + SassString(String.fromCharCode(rune), quotes: string.hasQuotes), + ), ListSeparator.comma, - brackets: true); - } - - var i = 0; - var lastEnd = 0; - var chunks = []; - for (var match in separator.text.allMatches(string.text)) { - chunks.add(string.text.substring(lastEnd, match.start)); - lastEnd = match.end; - - i++; - if (i == limit) break; - } - chunks.add(string.text.substring(lastEnd)); - - return SassList( + brackets: true, + ); + } + + var i = 0; + var lastEnd = 0; + var chunks = []; + for (var match in separator.text.allMatches(string.text)) { + chunks.add(string.text.substring(lastEnd, match.start)); + lastEnd = match.end; + + i++; + if (i == limit) break; + } + chunks.add(string.text.substring(lastEnd)); + + return SassList( chunks.map((chunk) => SassString(chunk, quotes: string.hasQuotes)), ListSeparator.comma, - brackets: true); - }), -]); + brackets: true, + ); + }), + ], +); final _unquote = _function("unquote", r"$string", (arguments) { var string = arguments[0].assertString("string"); @@ -113,11 +123,14 @@ final _insert = _function("insert", r"$string, $insert, $index", (arguments) { } var codepointIndex = _codepointForIndex(indexInt, string.sassLength); - var codeUnitIndex = - codepointIndexToCodeUnitIndex(string.text, codepointIndex); + var codeUnitIndex = codepointIndexToCodeUnitIndex( + string.text, + codepointIndex, + ); return SassString( - string.text.replaceRange(codeUnitIndex, codeUnitIndex, insert.text), - quotes: string.hasQuotes); + string.text.replaceRange(codeUnitIndex, codeUnitIndex, insert.text), + quotes: string.hasQuotes, + ); }); final _index = _function("index", r"$string, $substring", (arguments) { @@ -126,13 +139,16 @@ final _index = _function("index", r"$string, $substring", (arguments) { var codeUnitIndex = string.text.indexOf(substring.text); if (codeUnitIndex == -1) return sassNull; - var codepointIndex = - codeUnitIndexToCodepointIndex(string.text, codeUnitIndex); + var codepointIndex = codeUnitIndexToCodepointIndex( + string.text, + codeUnitIndex, + ); return SassNumber(codepointIndex + 1); }); -final _slice = - _function("slice", r"$string, $start-at, $end-at: -1", (arguments) { +final _slice = _function("slice", r"$string, $start-at, $end-at: -1", ( + arguments, +) { var string = arguments[0].assertString("string"); var start = arguments[1].assertNumber("start-at"); var end = arguments[2].assertNumber("end-at"); @@ -146,20 +162,27 @@ final _slice = var endInt = end.assertInt(); if (endInt == 0) return SassString.empty(quotes: string.hasQuotes); - var startCodepoint = - _codepointForIndex(start.assertInt(), lengthInCodepoints); - var endCodepoint = - _codepointForIndex(endInt, lengthInCodepoints, allowNegative: true); + var startCodepoint = _codepointForIndex( + start.assertInt(), + lengthInCodepoints, + ); + var endCodepoint = _codepointForIndex( + endInt, + lengthInCodepoints, + allowNegative: true, + ); if (endCodepoint == lengthInCodepoints) endCodepoint -= 1; if (endCodepoint < startCodepoint) { return SassString.empty(quotes: string.hasQuotes); } return SassString( - string.text.substring( - codepointIndexToCodeUnitIndex(string.text, startCodepoint), - codepointIndexToCodeUnitIndex(string.text, endCodepoint + 1)), - quotes: string.hasQuotes); + string.text.substring( + codepointIndexToCodeUnitIndex(string.text, startCodepoint), + codepointIndexToCodeUnitIndex(string.text, endCodepoint + 1), + ), + quotes: string.hasQuotes, + ); }); final _toUpperCase = _function("to-upper-case", r"$string", (arguments) { @@ -188,8 +211,10 @@ final _uniqueId = _function("unique-id", "", (arguments) { } // The leading "u" ensures that the result is a valid identifier. - return SassString("u${_previousUniqueId.toRadixString(36).padLeft(6, '0')}", - quotes: false); + return SassString( + "u${_previousUniqueId.toRadixString(36).padLeft(6, '0')}", + quotes: false, + ); }); /// Converts a Sass string index into a codepoint index into a string whose @@ -202,8 +227,11 @@ final _uniqueId = _function("unique-id", "", (arguments) { /// If [index] is negative and it points before the beginning of /// [lengthInCodepoints], this will return `0` if [allowNegative] is `false` and /// the index if it's `true`. -int _codepointForIndex(int index, int lengthInCodepoints, - {bool allowNegative = false}) { +int _codepointForIndex( + int index, + int lengthInCodepoints, { + bool allowNegative = false, +}) { if (index == 0) return 0; if (index > 0) return math.min(index - 1, lengthInCodepoints); var result = lengthInCodepoints + index; @@ -214,5 +242,8 @@ int _codepointForIndex(int index, int lengthInCodepoints, /// Like [BuiltInCallable.function], but always sets the URL to /// `sass:string`. BuiltInCallable _function( - String name, String arguments, Value callback(List arguments)) => + String name, + String arguments, + Value callback(List arguments), +) => BuiltInCallable.function(name, arguments, callback, url: "sass:string"); diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index b6d38d15f..9900a163a 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 65a7c538299527be3240f0625a7c1cd4f8cd6824 +// Checksum: a0b4e091d7a729fbadceabdb1f51ab2631e2df4a // // ignore_for_file: unused_import @@ -91,11 +91,11 @@ final class ImportCache { /// this is a shorthand for adding a [PackageImporter] to [importers]. /// /// [`PackageConfig`]: https://pub.dev/documentation/package_config/latest/package_config.package_config/PackageConfig-class.html - ImportCache( - {Iterable? importers, - Iterable? loadPaths, - PackageConfig? packageConfig}) - : _importers = _toImporters(importers, loadPaths, packageConfig); + ImportCache({ + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + }) : _importers = _toImporters(importers, loadPaths, packageConfig); /// Creates an import cache without any globally-available importers. ImportCache.none() : _importers = const []; @@ -107,8 +107,11 @@ final class ImportCache { /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. - static List _toImporters(Iterable? importers, - Iterable? loadPaths, PackageConfig? packageConfig) { + static List _toImporters( + Iterable? importers, + Iterable? loadPaths, + PackageConfig? packageConfig, + ) { var sassPath = getEnvironmentVariable('SASS_PATH'); if (isBrowser) return [...?importers]; return [ @@ -118,7 +121,7 @@ final class ImportCache { if (sassPath != null) for (var path in sassPath.split(isWindows ? ';' : ':')) FilesystemImporter(path), - if (packageConfig != null) PackageImporter(packageConfig) + if (packageConfig != null) PackageImporter(packageConfig), ]; } @@ -137,8 +140,12 @@ final class ImportCache { /// If any importers understand [url], returns that importer as well as the /// canonicalized URL and the original URL (resolved relative to [baseUrl] if /// applicable). Otherwise, returns `null`. - CanonicalizeResult? canonicalize(Uri url, - {Importer? baseImporter, Uri? baseUrl, bool forImport = false}) { + CanonicalizeResult? canonicalize( + Uri url, { + Importer? baseImporter, + Uri? baseUrl, + bool forImport = false, + }) { if (isBrowser && (baseImporter == null || baseImporter is NoOpImporter) && _importers.isEmpty) { @@ -149,16 +156,24 @@ final class ImportCache { if (baseImporter != null && url.scheme == '') { var resolvedUrl = baseUrl?.resolveUri(url) ?? url; var key = (baseImporter, resolvedUrl, forImport: forImport); - var relativeResult = _perImporterCanonicalizeCache.putIfAbsent(key, () { - var (result, cacheable) = - _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport); - assert( + var relativeResult = _perImporterCanonicalizeCache.putIfAbsent( + key, + () { + var (result, cacheable) = _canonicalize( + baseImporter, + resolvedUrl, + baseUrl, + forImport, + ); + assert( cacheable, "Relative loads should always be cacheable because they never " - "provide access to the containing URL."); - if (baseUrl != null) _nonCanonicalRelativeUrls[key] = url; - return result; - }); + "provide access to the containing URL.", + ); + if (baseUrl != null) _nonCanonicalRelativeUrls[key] = url; + return result; + }, + ); if (relativeResult != null) return relativeResult; } @@ -200,7 +215,7 @@ final class ImportCache { _perImporterCanonicalizeCache[( _importers[j], url, - forImport: forImport + forImport: forImport, )] = null; } cacheable = false; @@ -220,15 +235,23 @@ final class ImportCache { /// This returns both the result of the call to `canonicalize()` and whether /// that result is cacheable at all. (CanonicalizeResult?, bool cacheable) _canonicalize( - Importer importer, Uri url, Uri? baseUrl, bool forImport) { + Importer importer, + Uri url, + Uri? baseUrl, + bool forImport, + ) { var passContainingUrl = baseUrl != null && (url.scheme == '' || importer.isNonCanonicalScheme(url.scheme)); - var canonicalizeContext = - CanonicalizeContext(passContainingUrl ? baseUrl : null, forImport); + var canonicalizeContext = CanonicalizeContext( + passContainingUrl ? baseUrl : null, + forImport, + ); var result = withCanonicalizeContext( - canonicalizeContext, () => importer.canonicalize(url)); + canonicalizeContext, + () => importer.canonicalize(url), + ); var cacheable = !passContainingUrl || !canonicalizeContext.wasContainingUrlAccessed; @@ -255,13 +278,24 @@ final class ImportCache { /// parsed stylesheet. Otherwise, returns `null`. /// /// Caches the result of the import and uses cached results if possible. - (Importer, Stylesheet)? import(Uri url, - {Importer? baseImporter, Uri? baseUrl, bool forImport = false}) { - if (canonicalize(url, - baseImporter: baseImporter, baseUrl: baseUrl, forImport: forImport) + (Importer, Stylesheet)? import( + Uri url, { + Importer? baseImporter, + Uri? baseUrl, + bool forImport = false, + }) { + if (canonicalize( + url, + baseImporter: baseImporter, + baseUrl: baseUrl, + forImport: forImport, + ) case (var importer, var canonicalUrl, :var originalUrl)) { - return importCanonical(importer, canonicalUrl, originalUrl: originalUrl) - .andThen((stylesheet) => (importer, stylesheet)); + return importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + ).andThen((stylesheet) => (importer, stylesheet)); } else { return null; } @@ -277,8 +311,11 @@ final class ImportCache { /// importers may return for legacy reasons. /// /// Caches the result of the import and uses cached results if possible. - Stylesheet? importCanonical(Importer importer, Uri canonicalUrl, - {Uri? originalUrl}) { + Stylesheet? importCanonical( + Importer importer, + Uri canonicalUrl, { + Uri? originalUrl, + }) { return _importCache.putIfAbsent(canonicalUrl, () { var loadTime = DateTime.now(); var result = importer.load(canonicalUrl); @@ -286,12 +323,15 @@ final class ImportCache { _loadTimes[canonicalUrl] = loadTime; _resultsCache[canonicalUrl] = result; - return Stylesheet.parse(result.contents, result.syntax, - // For backwards-compatibility, relative canonical URLs are resolved - // relative to [originalUrl]. - url: originalUrl == null - ? canonicalUrl - : originalUrl.resolveUri(canonicalUrl)); + return Stylesheet.parse( + result.contents, + result.syntax, + // For backwards-compatibility, relative canonical URLs are resolved + // relative to [originalUrl]. + url: originalUrl == null + ? canonicalUrl + : originalUrl.resolveUri(canonicalUrl), + ); }); } @@ -302,10 +342,11 @@ final class ImportCache { // If multiple original URLs canonicalize to the same thing, choose the // shortest one. minBy( - _canonicalizeCache.values.nonNulls - .where((result) => result.$2 == canonicalUrl) - .map((result) => result.originalUrl), - (url) => url.path.length) + _canonicalizeCache.values.nonNulls + .where((result) => result.$2 == canonicalUrl) + .map((result) => result.originalUrl), + (url) => url.path.length, + ) // Use the canonicalized basename so that we display e.g. // package:example/_example.scss rather than package:example/example // in stack traces. diff --git a/lib/src/importer/filesystem.dart b/lib/src/importer/filesystem.dart index 0c57c82d8..4968bb533 100644 --- a/lib/src/importer/filesystem.dart +++ b/lib/src/importer/filesystem.dart @@ -57,7 +57,8 @@ class FilesystemImporter extends Importer { /// load path doesn't matter, or `FilesystemImporter('.')` to explicitly /// preserve the existing behavior. @Deprecated( - "Use FilesystemImporter.noLoadPath or FilesystemImporter('.') instead.") + "Use FilesystemImporter.noLoadPath or FilesystemImporter('.') instead.", + ) static final cwd = FilesystemImporter._deprecated('.'); /// Creates an importer that _only_ loads absolute `file:` URLs and URLs @@ -75,10 +76,11 @@ class FilesystemImporter extends Importer { if (resolved != null && _loadPathDeprecated) { warnForDeprecation( - "Using the current working directory as an implicit load path is " - "deprecated. Either add it as an explicit load path or importer, or " - "load this stylesheet from a different URL.", - Deprecation.fsImporterCwd); + "Using the current working directory as an implicit load path is " + "deprecated. Either add it as an explicit load path or importer, or " + "load this stylesheet from a different URL.", + Deprecation.fsImporterCwd, + ); } } else { return null; @@ -89,8 +91,11 @@ class FilesystemImporter extends Importer { ImporterResult? load(Uri url) { var path = p.fromUri(url); - return ImporterResult(io.readFile(path), - sourceMapUrl: url, syntax: Syntax.forPath(path)); + return ImporterResult( + io.readFile(path), + sourceMapUrl: url, + syntax: Syntax.forPath(path), + ); } DateTime modificationTime(Uri url) => io.modificationTime(p.fromUri(url)); diff --git a/lib/src/importer/js_to_dart/async.dart b/lib/src/importer/js_to_dart/async.dart index c6d26cbe2..ad863446b 100644 --- a/lib/src/importer/js_to_dart/async.dart +++ b/lib/src/importer/js_to_dart/async.dart @@ -31,8 +31,10 @@ final class JSToDartAsyncImporter extends AsyncImporter { final Set _nonCanonicalSchemes; JSToDartAsyncImporter( - this._canonicalize, this._load, Iterable? nonCanonicalSchemes) - : _nonCanonicalSchemes = nonCanonicalSchemes == null + this._canonicalize, + this._load, + Iterable? nonCanonicalSchemes, + ) : _nonCanonicalSchemes = nonCanonicalSchemes == null ? const {} : Set.unmodifiable(nonCanonicalSchemes) { _nonCanonicalSchemes.forEach(validateUrlScheme); @@ -40,7 +42,8 @@ final class JSToDartAsyncImporter extends AsyncImporter { FutureOr canonicalize(Uri url) async { var result = wrapJSExceptions( - () => _canonicalize(url.toString(), canonicalizeContext)); + () => _canonicalize(url.toString(), canonicalizeContext), + ); if (isPromise(result)) result = await promiseToFuture(result as Promise); if (result == null) return null; @@ -57,19 +60,30 @@ final class JSToDartAsyncImporter extends AsyncImporter { result as JSImporterResult; var contents = result.contents; if (!isJsString(contents)) { - jsThrow(ArgumentError.value(contents, 'contents', - 'must be a string but was: ${jsType(contents)}')); + jsThrow( + ArgumentError.value( + contents, + 'contents', + 'must be a string but was: ${jsType(contents)}', + ), + ); } var syntax = result.syntax; if (contents == null || syntax == null) { - jsThrow(JsError("The load() function must return an object with contents " - "and syntax fields.")); + jsThrow( + JsError( + "The load() function must return an object with contents " + "and syntax fields.", + ), + ); } - return ImporterResult(contents, - syntax: parseSyntax(syntax), - sourceMapUrl: result.sourceMapUrl.andThen(jsToDartUrl)); + return ImporterResult( + contents, + syntax: parseSyntax(syntax), + sourceMapUrl: result.sourceMapUrl.andThen(jsToDartUrl), + ); } bool isNonCanonicalScheme(String scheme) => diff --git a/lib/src/importer/js_to_dart/async_file.dart b/lib/src/importer/js_to_dart/async_file.dart index 95b2af908..6522d976d 100644 --- a/lib/src/importer/js_to_dart/async_file.dart +++ b/lib/src/importer/js_to_dart/async_file.dart @@ -28,7 +28,8 @@ final class JSToDartAsyncFileImporter extends AsyncImporter { if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); var result = wrapJSExceptions( - () => _findFileUrl(url.toString(), canonicalizeContext)); + () => _findFileUrl(url.toString(), canonicalizeContext), + ); if (isPromise(result)) result = await promiseToFuture(result as Promise); if (result == null) return null; if (!isJSUrl(result)) { @@ -37,9 +38,12 @@ final class JSToDartAsyncFileImporter extends AsyncImporter { var resultUrl = jsToDartUrl(result as JSUrl); if (resultUrl.scheme != 'file') { - jsThrow(JsError( + jsThrow( + JsError( 'The findFileUrl() must return a URL with scheme file://, was ' - '"$url".')); + '"$url".', + ), + ); } return FilesystemImporter.cwd.canonicalize(resultUrl); diff --git a/lib/src/importer/js_to_dart/file.dart b/lib/src/importer/js_to_dart/file.dart index 555c9df16..f08bba2b5 100644 --- a/lib/src/importer/js_to_dart/file.dart +++ b/lib/src/importer/js_to_dart/file.dart @@ -23,22 +23,29 @@ final class JSToDartFileImporter extends Importer { if (url.scheme == 'file') return FilesystemImporter.cwd.canonicalize(url); var result = wrapJSExceptions( - () => _findFileUrl(url.toString(), canonicalizeContext)); + () => _findFileUrl(url.toString(), canonicalizeContext), + ); if (result == null) return null; if (isPromise(result)) { - jsThrow(JsError( + jsThrow( + JsError( "The findFileUrl() function can't return a Promise for synchron " - "compile functions.")); + "compile functions.", + ), + ); } else if (!isJSUrl(result)) { jsThrow(JsError("The findFileUrl() method must return a URL.")); } var resultUrl = jsToDartUrl(result as JSUrl); if (resultUrl.scheme != 'file') { - jsThrow(JsError( + jsThrow( + JsError( 'The findFileUrl() must return a URL with scheme file://, was ' - '"$url".')); + '"$url".', + ), + ); } return FilesystemImporter.cwd.canonicalize(resultUrl); diff --git a/lib/src/importer/js_to_dart/sync.dart b/lib/src/importer/js_to_dart/sync.dart index 06b91310a..cc34fad10 100644 --- a/lib/src/importer/js_to_dart/sync.dart +++ b/lib/src/importer/js_to_dart/sync.dart @@ -27,8 +27,10 @@ final class JSToDartImporter extends Importer { final Set _nonCanonicalSchemes; JSToDartImporter( - this._canonicalize, this._load, Iterable? nonCanonicalSchemes) - : _nonCanonicalSchemes = nonCanonicalSchemes == null + this._canonicalize, + this._load, + Iterable? nonCanonicalSchemes, + ) : _nonCanonicalSchemes = nonCanonicalSchemes == null ? const {} : Set.unmodifiable(nonCanonicalSchemes) { _nonCanonicalSchemes.forEach(validateUrlScheme); @@ -36,14 +38,18 @@ final class JSToDartImporter extends Importer { Uri? canonicalize(Uri url) { var result = wrapJSExceptions( - () => _canonicalize(url.toString(), canonicalizeContext)); + () => _canonicalize(url.toString(), canonicalizeContext), + ); if (result == null) return null; if (isJSUrl(result)) return jsToDartUrl(result as JSUrl); if (isPromise(result)) { - jsThrow(JsError( + jsThrow( + JsError( "The canonicalize() function can't return a Promise for synchronous " - "compile functions.")); + "compile functions.", + ), + ); } else { jsThrow(JsError("The canonicalize() method must return a URL.")); } @@ -54,27 +60,41 @@ final class JSToDartImporter extends Importer { if (result == null) return null; if (isPromise(result)) { - jsThrow(JsError( + jsThrow( + JsError( "The load() function can't return a Promise for synchronous compile " - "functions.")); + "functions.", + ), + ); } result as JSImporterResult; var contents = result.contents; if (!isJsString(contents)) { - jsThrow(ArgumentError.value(contents, 'contents', - 'must be a string but was: ${jsType(contents)}')); + jsThrow( + ArgumentError.value( + contents, + 'contents', + 'must be a string but was: ${jsType(contents)}', + ), + ); } var syntax = result.syntax; if (contents == null || syntax == null) { - jsThrow(JsError("The load() function must return an object with contents " - "and syntax fields.")); + jsThrow( + JsError( + "The load() function must return an object with contents " + "and syntax fields.", + ), + ); } - return ImporterResult(contents, - syntax: parseSyntax(syntax), - sourceMapUrl: result.sourceMapUrl.andThen(jsToDartUrl)); + return ImporterResult( + contents, + syntax: parseSyntax(syntax), + sourceMapUrl: result.sourceMapUrl.andThen(jsToDartUrl), + ); } bool isNonCanonicalScheme(String scheme) => diff --git a/lib/src/importer/js_to_dart/utils.dart b/lib/src/importer/js_to_dart/utils.dart index 6cd2106f0..b8dd23ce8 100644 --- a/lib/src/importer/js_to_dart/utils.dart +++ b/lib/src/importer/js_to_dart/utils.dart @@ -11,6 +11,7 @@ import '../utils.dart'; void validateUrlScheme(String scheme) { if (!isValidUrlScheme(scheme)) { jsThrow( - JsError('"$scheme" isn\'t a valid URL scheme (for example "file").')); + JsError('"$scheme" isn\'t a valid URL scheme (for example "file").'), + ); } } diff --git a/lib/src/importer/legacy_node/implementation.dart b/lib/src/importer/legacy_node/implementation.dart index 678d82138..055672f94 100644 --- a/lib/src/importer/legacy_node/implementation.dart +++ b/lib/src/importer/legacy_node/implementation.dart @@ -54,8 +54,10 @@ final class NodeImporter { final List _importers; NodeImporter( - this._options, Iterable includePaths, Iterable importers) - : _includePaths = List.unmodifiable(_addSassPath(includePaths)), + this._options, + Iterable includePaths, + Iterable importers, + ) : _includePaths = List.unmodifiable(_addSassPath(includePaths)), _importers = List.unmodifiable(importers.cast()); /// Returns [includePaths] followed by any paths loaded from the `SASS_PATH` @@ -75,7 +77,10 @@ final class NodeImporter { /// Returns the stylesheet at that path and the URL used to load it, or `null` /// if loading failed. (String contents, String url)? loadRelative( - String url, Uri? previous, bool forImport) { + String url, + Uri? previous, + bool forImport, + ) { if (p.url.isAbsolute(url)) { if (!url.startsWith('/') && !url.startsWith('file:')) return null; return _tryPath(p.fromUri(url), forImport); @@ -85,7 +90,9 @@ final class NodeImporter { // 1: Filesystem imports relative to the base file. return _tryPath( - p.join(p.dirname(p.fromUri(previous)), p.fromUri(url)), forImport); + p.join(p.dirname(p.fromUri(previous)), p.fromUri(url)), + forImport, + ); } /// Loads the stylesheet at [url] from an importer or load path. @@ -94,12 +101,16 @@ final class NodeImporter { /// appeared. Returns the contents of the stylesheet and the URL to use as /// [previous] for imports within the loaded stylesheet. (String contents, String url)? load( - String url, Uri? previous, bool forImport) { + String url, + Uri? previous, + bool forImport, + ) { // The previous URL is always an absolute file path for filesystem imports. var previousString = _previousToString(previous); for (var importer in _importers) { - if (wrapJSExceptions(() => - call2(importer, _renderContext(forImport), url, previousString)) + if (wrapJSExceptions( + () => call2(importer, _renderContext(forImport), url, previousString), + ) case var value?) { return _handleImportResult(url, previous, value, forImport); } @@ -115,7 +126,10 @@ final class NodeImporter { /// appeared. Returns the contents of the stylesheet and the URL to use as /// [previous] for imports within the loaded stylesheet. Future<(String contents, String url)?> loadAsync( - String url, Uri? previous, bool forImport) async { + String url, + Uri? previous, + bool forImport, + ) async { // The previous URL is always an absolute file path for filesystem imports. var previousString = _previousToString(previous); for (var importer in _importers) { @@ -132,7 +146,7 @@ final class NodeImporter { String _previousToString(Uri? previous) => switch (previous) { null => 'stdin', Uri(scheme: 'file') => p.fromUri(previous), - _ => previous.toString() + _ => previous.toString(), }; /// Tries to load a stylesheet at the given [url] from a load path (including @@ -170,23 +184,33 @@ final class NodeImporter { /// Returns the stylesheet at that path and the URL used to load it, or `null` /// if loading failed. (String, String)? _tryPath(String path, bool forImport) => (forImport - ? inImportRule(() => resolveImportPath(path)) - : resolveImportPath(path)) - .andThen( - (resolved) => (readFile(resolved), p.toUri(resolved).toString())); + ? inImportRule(() => resolveImportPath(path)) + : resolveImportPath(path)) + .andThen( + (resolved) => (readFile(resolved), p.toUri(resolved).toString()), + ); /// Converts an importer's return [value] to a (contents, url) pair that can /// be returned by [load]. (String, String)? _handleImportResult( - String url, Uri? previous, Object value, bool forImport) { + String url, + Uri? previous, + Object value, + bool forImport, + ) { if (isJSError(value)) throw value; if (value is! NodeImporterResult) return null; var file = value.file; var contents = value.contents; if (contents != null && !isJsString(contents)) { - jsThrow(ArgumentError.value(contents, 'contents', - 'must be a string but was: ${jsType(contents)}')); + jsThrow( + ArgumentError.value( + contents, + 'contents', + 'must be a string but was: ${jsType(contents)}', + ), + ); } if (file == null) { @@ -203,16 +227,23 @@ final class NodeImporter { } /// Calls an importer that may or may not be asynchronous. - Future _callImporterAsync(JSFunction importer, String url, - String previousString, bool forImport) async { + Future _callImporterAsync( + JSFunction importer, + String url, + String previousString, + bool forImport, + ) async { var completer = Completer(); - var result = wrapJSExceptions(() => call3( + var result = wrapJSExceptions( + () => call3( importer, _renderContext(forImport), url, previousString, - allowInterop(completer.complete))); + allowInterop(completer.complete), + ), + ); if (isUndefined(result)) return await completer.future; return result; } @@ -220,7 +251,9 @@ final class NodeImporter { /// Returns the [RenderContext] in which to invoke importers. RenderContext _renderContext(bool fromImport) { var context = RenderContext( - options: _options as RenderContextOptions, fromImport: fromImport); + options: _options as RenderContextOptions, + fromImport: fromImport, + ); context.options.context = context; return context; } diff --git a/lib/src/importer/legacy_node/interface.dart b/lib/src/importer/legacy_node/interface.dart index 0b6f5cb29..4ceab74d1 100644 --- a/lib/src/importer/legacy_node/interface.dart +++ b/lib/src/importer/legacy_node/interface.dart @@ -3,18 +3,30 @@ // https://opensource.org/licenses/MIT. final class NodeImporter { - NodeImporter(Object options, Iterable includePaths, - Iterable importers); + NodeImporter( + Object options, + Iterable includePaths, + Iterable importers, + ); (String contents, String url)? loadRelative( - String url, Uri? previous, bool forImport) => + String url, + Uri? previous, + bool forImport, + ) => throw ''; (String contents, String url)? load( - String url, Uri? previous, bool forImport) => + String url, + Uri? previous, + bool forImport, + ) => throw ''; Future<(String contents, String url)?> loadAsync( - String url, Uri? previous, bool forImport) => + String url, + Uri? previous, + bool forImport, + ) => throw ''; } diff --git a/lib/src/importer/node_package.dart b/lib/src/importer/node_package.dart index 1d142fa4d..3a1f4e2b4 100644 --- a/lib/src/importer/node_package.dart +++ b/lib/src/importer/node_package.dart @@ -73,7 +73,11 @@ class NodePackageImporter extends Importer { } if (_resolvePackageExports( - packageRoot, subpath, packageManifest, packageName) + packageRoot, + subpath, + packageManifest, + packageName, + ) case var resolved?) { if (_validExtensions.contains(p.extension(resolved))) { return p.toUri(p.canonicalize(resolved)); @@ -135,7 +139,9 @@ class NodePackageImporter extends Importer { /// Returns a file path specified by the `sass` or `style` values in a package /// manifest, or an `index` file relative to the package root. String? _resolvePackageRootValues( - String packageRoot, Map packageManifest) { + String packageRoot, + Map packageManifest, + ) { if (packageManifest['sass'] case String sassValue when _validExtensions.contains(p.url.extension(sassValue))) { return p.join(packageRoot, sassValue); @@ -152,13 +158,22 @@ class NodePackageImporter extends Importer { /// package.json. /// /// `packageName` is used for error reporting. - String? _resolvePackageExports(String packageRoot, String? subpath, - Map packageManifest, String packageName) { + String? _resolvePackageExports( + String packageRoot, + String? subpath, + Map packageManifest, + String packageName, + ) { var exports = packageManifest['exports'] as Object?; if (exports == null) return null; var subpathVariants = _exportsToCheck(subpath); if (_nodePackageExportsResolve( - packageRoot, subpathVariants, exports, subpath, packageName) + packageRoot, + subpathVariants, + exports, + subpath, + packageName, + ) case var path?) { return path; } @@ -167,7 +182,12 @@ class NodePackageImporter extends Importer { var subpathIndexVariants = _exportsToCheck(subpath, addIndex: true); if (_nodePackageExportsResolve( - packageRoot, subpathIndexVariants, exports, subpath, packageName) + packageRoot, + subpathIndexVariants, + exports, + subpath, + packageName, + ) case var path?) { return path; } @@ -184,11 +204,12 @@ class NodePackageImporter extends Importer { /// Implementation of `PACKAGE_EXPORTS_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _nodePackageExportsResolve( - String packageRoot, - List subpathVariants, - Object exports, - String? subpath, - String packageName) { + String packageRoot, + List subpathVariants, + Object exports, + String? subpath, + String packageName, + ) { if (exports is Map && exports.keys.any((key) => key.startsWith('.')) && exports.keys.any((key) => !key.startsWith('.'))) { @@ -201,8 +222,10 @@ class NodePackageImporter extends Importer { var matches = subpathVariants .map((String? variant) { if (variant == null) { - return _getMainExport(exports).andThen((mainExport) => - _packageTargetResolve(variant, mainExport, packageRoot)); + return _getMainExport(exports).andThen( + (mainExport) => + _packageTargetResolve(variant, mainExport, packageRoot), + ); } else if (exports is! Map || exports.keys.every((key) => !key.startsWith('.'))) { return null; @@ -212,12 +235,15 @@ class NodePackageImporter extends Importer { exports[matchKey] != null && !matchKey.contains('*')) { return _packageTargetResolve( - matchKey, exports[matchKey] as Object, packageRoot); + matchKey, + exports[matchKey] as Object, + packageRoot, + ); } var expansionKeys = [ for (var key in exports.keys) - if ('*'.allMatches(key).length == 1) key + if ('*'.allMatches(key).length == 1) key, ]..sort(_compareExpansionKeys); for (var expansionKey in expansionKeys) { @@ -230,9 +256,15 @@ class NodePackageImporter extends Importer { var target = exports[expansionKey] as Object?; if (target == null) continue; var patternMatch = matchKey.substring( - patternBase.length, matchKey.length - patternTrailer.length); + patternBase.length, + matchKey.length - patternTrailer.length, + ); return _packageTargetResolve( - variant, target, packageRoot, patternMatch); + variant, + target, + packageRoot, + patternMatch, + ); } } @@ -248,7 +280,7 @@ class NodePackageImporter extends Importer { throw "Unable to determine which of multiple potential resolutions " "found for ${subpath ?? 'root'} in $packageName should be used. " "\n\nFound:\n" - "${paths.join('\n')}" + "${paths.join('\n')}", }; } @@ -277,8 +309,11 @@ class NodePackageImporter extends Importer { /// Implementation of `PACKAGE_TARGET_RESOLVE` from the [Resolution Algorithm /// Specification](https://nodejs.org/api/esm.html#resolution-algorithm-specification). String? _packageTargetResolve( - String? subpath, Object exports, String packageRoot, - [String? patternMatch]) { + String? subpath, + Object exports, + String packageRoot, [ + String? patternMatch, + ]) { switch (exports) { case String string when !string.startsWith('./'): throw "Export '$string' must be a path relative to the package root at '$packageRoot'."; @@ -293,7 +328,11 @@ class NodePackageImporter extends Importer { if (!const {'sass', 'style', 'default'}.contains(key)) continue; if (value == null) continue; if (_packageTargetResolve( - subpath, value as Object, packageRoot, patternMatch) + subpath, + value as Object, + packageRoot, + patternMatch, + ) case var result?) { return result; } @@ -307,7 +346,11 @@ class NodePackageImporter extends Importer { for (var value in array) { if (value == null) continue; if (_packageTargetResolve( - subpath, value as Object, packageRoot, patternMatch) + subpath, + value as Object, + packageRoot, + patternMatch, + ) case var result?) { return result; } @@ -330,7 +373,7 @@ class NodePackageImporter extends Importer { when !map.keys.any((key) => key.startsWith('.')) => map, {'.': var export?} => export, - _ => null + _ => null, }; } @@ -352,12 +395,7 @@ class NodePackageImporter extends Importer { if (_validExtensions.contains(p.url.extension(subpath))) { paths.add(subpath); } else { - paths.addAll([ - subpath, - '$subpath.scss', - '$subpath.sass', - '$subpath.css', - ]); + paths.addAll([subpath, '$subpath.scss', '$subpath.sass', '$subpath.css']); } var basename = p.basename(subpath); var dirname = p.dirname(subpath); @@ -370,7 +408,7 @@ class NodePackageImporter extends Importer { if (dirname == '.') '_${p.basename(path)}' else - p.join(dirname, '_${p.basename(path)}') + p.join(dirname, '_${p.basename(path)}'), ]; } } diff --git a/lib/src/importer/package.dart b/lib/src/importer/package.dart index 39d09ac63..e1ac0d2a7 100644 --- a/lib/src/importer/package.dart +++ b/lib/src/importer/package.dart @@ -43,8 +43,10 @@ class PackageImporter extends Importer { bool couldCanonicalize(Uri url, Uri canonicalUrl) => (url.scheme == 'file' || url.scheme == 'package' || url.scheme == '') && - FilesystemImporter.cwd - .couldCanonicalize(Uri(path: url.path), canonicalUrl); + FilesystemImporter.cwd.couldCanonicalize( + Uri(path: url.path), + canonicalUrl, + ); String toString() => "package:..."; } diff --git a/lib/src/importer/result.dart b/lib/src/importer/result.dart index b5b979577..8cbb1e0d3 100644 --- a/lib/src/importer/result.dart +++ b/lib/src/importer/result.dart @@ -38,15 +38,19 @@ class ImporterResult { /// The [syntax] parameter must be passed. It's not marked as required only /// because old clients may still be passing the deprecated [indented] /// parameter instead. - ImporterResult(this.contents, - {Uri? sourceMapUrl, - Syntax? syntax, - @Deprecated("Use the syntax parameter instead.") bool? indented}) - : _sourceMapUrl = sourceMapUrl, + ImporterResult( + this.contents, { + Uri? sourceMapUrl, + Syntax? syntax, + @Deprecated("Use the syntax parameter instead.") bool? indented, + }) : _sourceMapUrl = sourceMapUrl, syntax = syntax ?? (indented == true ? Syntax.sass : Syntax.scss) { if (sourceMapUrl?.scheme == '') { throw ArgumentError.value( - sourceMapUrl, 'sourceMapUrl', 'must be absolute'); + sourceMapUrl, + 'sourceMapUrl', + 'must be absolute', + ); } else if (syntax == null && indented == null) { throw ArgumentError("The syntax parameter must be passed."); } else if (syntax != null && indented != null) { diff --git a/lib/src/importer/utils.dart b/lib/src/importer/utils.dart index 4c5c8106c..cb1a9bfe4 100644 --- a/lib/src/importer/utils.dart +++ b/lib/src/importer/utils.dart @@ -25,21 +25,26 @@ bool get fromImport => CanonicalizeContext get canonicalizeContext => switch (Zone.current[#_canonicalizeContext]) { null => throw StateError( - "canonicalizeContext may only be accessed within a call to canonicalize()."), + "canonicalizeContext may only be accessed within a call to canonicalize().", + ), CanonicalizeContext context => context, var value => throw StateError( - "Unexpected Zone.current[#_canonicalizeContext] value $value.") + "Unexpected Zone.current[#_canonicalizeContext] value $value.", + ), }; /// Runs [callback] in a context where [fromImport] returns `true` and /// [resolveImportPath] uses `@import` semantics rather than `@use` semantics. T inImportRule(T callback()) => switch (Zone.current[#_canonicalizeContext]) { - null => runZoned(callback, - zoneValues: {#_canonicalizeContext: CanonicalizeContext(null, true)}), + null => runZoned( + callback, + zoneValues: {#_canonicalizeContext: CanonicalizeContext(null, true)}, + ), CanonicalizeContext context => context.withFromImport(true, callback), var value => throw StateError( - "Unexpected Zone.current[#_canonicalizeContext] value $value.") + "Unexpected Zone.current[#_canonicalizeContext] value $value.", + ), }; /// Runs [callback] in the given context. @@ -53,13 +58,17 @@ T withCanonicalizeContext(CanonicalizeContext? context, T callback()) => String? resolveImportPath(String path) { var extension = p.extension(path); if (extension == '.sass' || extension == '.scss' || extension == '.css') { - return _ifInImport(() => _exactlyOne( - _tryPath('${p.withoutExtension(path)}.import$extension'))) ?? + return _ifInImport( + () => _exactlyOne( + _tryPath('${p.withoutExtension(path)}.import$extension'), + ), + ) ?? _exactlyOne(_tryPath(path)); } return _ifInImport( - () => _exactlyOne(_tryPathWithExtensions('$path.import'))) ?? + () => _exactlyOne(_tryPathWithExtensions('$path.import')), + ) ?? _exactlyOne(_tryPathWithExtensions(path)) ?? _tryPathAsDirectory(path); } @@ -86,8 +95,9 @@ List _tryPath(String path) { String? _tryPathAsDirectory(String path) { if (!dirExists(path)) return null; - return _ifInImport(() => - _exactlyOne(_tryPathWithExtensions(p.join(path, 'index.import')))) ?? + return _ifInImport( + () => _exactlyOne(_tryPathWithExtensions(p.join(path, 'index.import'))), + ) ?? _exactlyOne(_tryPathWithExtensions(p.join(path, 'index'))); } @@ -99,7 +109,7 @@ String? _exactlyOne(List paths) => switch (paths) { [] => null, [var path] => path, _ => throw "It's not clear which file to import. Found:\n" + - paths.map((path) => " " + p.prettyUri(p.toUri(path))).join("\n") + paths.map((path) => " " + p.prettyUri(p.toUri(path))).join("\n"), }; /// If [fromImport] is `true`, invokes callback and returns the result. diff --git a/lib/src/interpolation_buffer.dart b/lib/src/interpolation_buffer.dart index e80080a68..5be9393d6 100644 --- a/lib/src/interpolation_buffer.dart +++ b/lib/src/interpolation_buffer.dart @@ -87,8 +87,11 @@ final class InterpolationBuffer implements StringSink { /// Creates an [Interpolation] with the given [span] from the contents of this /// buffer. Interpolation interpolation(FileSpan span) { - return Interpolation([..._contents, if (_text.isNotEmpty) _text.toString()], - [..._spans, if (_text.isNotEmpty) null], span); + return Interpolation( + [..._contents, if (_text.isNotEmpty) _text.toString()], + [..._spans, if (_text.isNotEmpty) null], + span, + ); } String toString() { diff --git a/lib/src/interpolation_map.dart b/lib/src/interpolation_map.dart index ca244be23..9d48afec7 100644 --- a/lib/src/interpolation_map.dart +++ b/lib/src/interpolation_map.dart @@ -30,13 +30,15 @@ final class InterpolationMap { /// Each target location at index `i` corresponds to the character in the /// generated string after `interpolation.contents[i]`. InterpolationMap( - this._interpolation, Iterable targetLocations) - : _targetLocations = List.unmodifiable(targetLocations) { + this._interpolation, + Iterable targetLocations, + ) : _targetLocations = List.unmodifiable(targetLocations) { var expectedLocations = math.max(0, _interpolation.contents.length - 1); if (_targetLocations.length != expectedLocations) { throw ArgumentError( - "InterpolationMap must have $expectedLocations targetLocations if the " - "interpolation has ${_interpolation.contents.length} components."); + "InterpolationMap must have $expectedLocations targetLocations if the " + "interpolation has ${_interpolation.contents.length} components.", + ); } } @@ -48,7 +50,10 @@ final class InterpolationMap { if (_interpolation.contents.isEmpty) { return SourceSpanFormatException( - error.message, _interpolation.span, error.source); + error.message, + _interpolation.span, + error.source, + ); } var source = mapSpan(target); @@ -61,23 +66,37 @@ final class InterpolationMap { .any((content) => content is Expression)) { return SourceSpanFormatException(error.message, source, error.source); } else { - return MultiSourceSpanFormatException(error.message, source, "", - {target: "error in interpolated output"}, error.source); + return MultiSourceSpanFormatException( + error.message, + source, + "", + { + target: "error in interpolated output", + }, + error.source); } } /// Maps a span in the string generated from this interpolation to its /// original source. - FileSpan mapSpan(SourceSpan target) => - switch ((_mapLocation(target.start), _mapLocation(target.end))) { + FileSpan mapSpan(SourceSpan target) => switch (( + _mapLocation(target.start), + _mapLocation(target.end), + )) { (FileSpan start, FileSpan end) => start.expand(end), - (FileSpan start, FileLocation end) => _interpolation.span.file - .span(_expandInterpolationSpanLeft(start.start), end.offset), - (FileLocation start, FileSpan end) => _interpolation.span.file - .span(start.offset, _expandInterpolationSpanRight(end.end)), - (FileLocation start, FileLocation end) => - _interpolation.span.file.span(start.offset, end.offset), - _ => throw '[BUG] Unreachable' + (FileSpan start, FileLocation end) => _interpolation.span.file.span( + _expandInterpolationSpanLeft(start.start), + end.offset, + ), + (FileLocation start, FileSpan end) => _interpolation.span.file.span( + start.offset, + _expandInterpolationSpanRight(end.end), + ), + (FileLocation start, FileLocation end) => _interpolation.span.file.span( + start.offset, + end.offset, + ), + _ => throw '[BUG] Unreachable', }; /// Maps a location in the string generated from this interpolation to its @@ -97,16 +116,20 @@ final class InterpolationMap { var previousLocation = index == 0 ? _interpolation.span.start - : _interpolation.span.file.location(_expandInterpolationSpanRight( - (_interpolation.contents[index - 1] as Expression).span.end)); + : _interpolation.span.file.location( + _expandInterpolationSpanRight( + (_interpolation.contents[index - 1] as Expression).span.end, + ), + ); var offsetInString = target.offset - (index == 0 ? 0 : _targetLocations[index - 1].offset); // This produces slightly incorrect mappings if there are _unnecessary_ // escapes in the source file, but that's unlikely enough that it's probably // not worth doing a reparse here to fix it. - return previousLocation.file - .location(previousLocation.offset + offsetInString); + return previousLocation.file.location( + previousLocation.offset + offsetInString, + ); } /// Return the index in [_interpolation.contents] at which [target] points. diff --git a/lib/src/io.dart b/lib/src/io.dart index 029f45aec..c0cabd83e 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -58,7 +58,8 @@ String _realCasePath(String path) { try { var matches = listDir(realDirname) .where( - (realPath) => equalsIgnoreCase(p.basename(realPath), basename)) + (realPath) => equalsIgnoreCase(p.basename(realPath), basename), + ) .toList(); return switch (matches) { @@ -66,7 +67,7 @@ String _realCasePath(String path) { // If the file doesn't exist, or if there are multiple options // (meaning the filesystem isn't actually case-insensitive), use // `basename` as-is. - _ => p.join(realDirname, basename) + _ => p.join(realDirname, basename), }; } on FileSystemException catch (_) { // If there's an error listing a directory, it's likely because we're diff --git a/lib/src/io/js.dart b/lib/src/io/js.dart index ce730d142..d072ff401 100644 --- a/lib/src/io/js.dart +++ b/lib/src/io/js.dart @@ -80,7 +80,8 @@ void writeFile(String path, String contents) { throw UnsupportedError("writeFile() is only supported on Node.js"); } return _systemErrorToFileSystemException( - () => fs.writeFileSync(path, contents)); + () => fs.writeFileSync(path, contents), + ); } void deleteFile(String path) { @@ -103,19 +104,28 @@ Future readStdin() async { }); // Node defaults all buffers to 'utf8'. var sink = utf8.decoder.startChunkedConversion(innerSink); - process.stdin.on('data', allowInterop(([Object? chunk]) { - sink.add(chunk as List); - })); - process.stdin.on('end', allowInterop(([Object? arg]) { - // Callback for 'end' receives no args. - assert(arg == null); - sink.close(); - })); - process.stdin.on('error', allowInterop(([Object? e]) { - printError('Failed to read from stdin'); - printError(e); - completer.completeError(e!); - })); + process.stdin.on( + 'data', + allowInterop(([Object? chunk]) { + sink.add(chunk as List); + }), + ); + process.stdin.on( + 'end', + allowInterop(([Object? arg]) { + // Callback for 'end' receives no args. + assert(arg == null); + sink.close(); + }), + ); + process.stdin.on( + 'error', + allowInterop(([Object? e]) { + printError('Failed to read from stdin'); + printError(e); + completer.completeError(e!); + }), + ); return completer.future; } @@ -123,8 +133,10 @@ Future readStdin() async { String _cleanErrorMessage(JsSystemError error) { // The error message is of the form "$code: $text, $syscall '$path'". We just // want the text. - return error.message.substring("${error.code}: ".length, - error.message.length - ", ${error.syscall} '${error.path}'".length); + return error.message.substring( + "${error.code}: ".length, + error.message.length - ", ${error.syscall} '${error.path}'".length, + ); } bool fileExists(String path) { @@ -212,8 +224,10 @@ DateTime modificationTime(String path) { if (!isNodeJs) { throw UnsupportedError("modificationTime() is only supported on Node.js"); } - return _systemErrorToFileSystemException(() => - DateTime.fromMillisecondsSinceEpoch(fs.statSync(path).mtime.getTime())); + return _systemErrorToFileSystemException( + () => + DateTime.fromMillisecondsSinceEpoch(fs.statSync(path).mtime.getTime()), + ); } String? getEnvironmentVariable(String name) { @@ -258,8 +272,10 @@ Future> watchDir(String path, {bool poll = false}) async { // Chokidar will give us a bunch of add events for files that already exist. StreamController? controller; if (parcelWatcher case var parcel? when !poll) { - var subscription = await parcel.subscribe(path, - (Object? error, List events) { + var subscription = await parcel.subscribe(path, ( + Object? error, + List events, + ) { if (error != null) { controller?.addError(error); } else { @@ -276,37 +292,54 @@ Future> watchDir(String path, {bool poll = false}) async { } }); - return (controller = StreamController(onCancel: () { - subscription.unsubscribe(); - })) + return (controller = StreamController( + onCancel: () { + subscription.unsubscribe(); + }, + )) .stream; } else { var watcher = chokidar.watch(path, ChokidarOptions(usePolling: poll)); watcher ..on( - 'add', - allowInterop((String path, [void _]) => - controller?.add(WatchEvent(ChangeType.ADD, path)))) + 'add', + allowInterop( + (String path, [void _]) => + controller?.add(WatchEvent(ChangeType.ADD, path)), + ), + ) ..on( - 'change', - allowInterop((String path, [void _]) => - controller?.add(WatchEvent(ChangeType.MODIFY, path)))) + 'change', + allowInterop( + (String path, [void _]) => + controller?.add(WatchEvent(ChangeType.MODIFY, path)), + ), + ) ..on( - 'unlink', - allowInterop((String path) => - controller?.add(WatchEvent(ChangeType.REMOVE, path)))) + 'unlink', + allowInterop( + (String path) => controller?.add(WatchEvent(ChangeType.REMOVE, path)), + ), + ) ..on( - 'error', allowInterop((Object error) => controller?.addError(error))); + 'error', + allowInterop((Object error) => controller?.addError(error)), + ); var completer = Completer>(); - watcher.on('ready', allowInterop(() { - // dart-lang/sdk#45348 - var stream = (controller = StreamController(onCancel: () { - watcher.close(); - })) - .stream; - completer.complete(stream); - })); + watcher.on( + 'ready', + allowInterop(() { + // dart-lang/sdk#45348 + var stream = (controller = StreamController( + onCancel: () { + watcher.close(); + }, + )) + .stream; + completer.complete(stream); + }), + ); return completer.future; } diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index 1d5c7b561..bf33ab7f7 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -54,18 +54,23 @@ String readFile(String path) { try { return utf8.decode(bytes); } on FormatException catch (error, stackTrace) { - var decodedUntilError = - utf8.decode(bytes.sublist(0, error.offset), allowMalformed: true); + var decodedUntilError = utf8.decode( + bytes.sublist(0, error.offset), + allowMalformed: true, + ); var stringOffset = decodedUntilError.length; if (decodedUntilError.endsWith("�")) stringOffset--; var decoded = utf8.decode(bytes, allowMalformed: true); var sourceFile = SourceFile.fromString(decoded, url: p.toUri(path)); throwWithTrace( - SassException( - "Invalid UTF-8.", sourceFile.location(stringOffset).pointSpan()), - error, - stackTrace); + SassException( + "Invalid UTF-8.", + sourceFile.location(stringOffset).pointSpan(), + ), + error, + stackTrace, + ); } } @@ -107,9 +112,13 @@ Future> watchDir(String path, {bool poll = false}) async { // Wrap [stream] in a [SubscriptionStream] so that its `onListen` event // triggers but the caller can still listen at their leisure. - var stream = SubscriptionStream(watcher.events - .transform(const SingleSubscriptionTransformer()) - .listen(null)); + var stream = SubscriptionStream( + watcher.events + .transform( + const SingleSubscriptionTransformer(), + ) + .listen(null), + ); await watcher.ready; return stream; diff --git a/lib/src/js.dart b/lib/src/js.dart index 0dd47686f..d262c87e9 100644 --- a/lib/src/js.dart +++ b/lib/src/js.dart @@ -25,14 +25,20 @@ import 'value.dart'; /// This sets up exports that can be called from JS. void main() { exports.compile = allowInteropNamed('sass.compile', compile); - exports.compileString = - allowInteropNamed('sass.compileString', compileString); + exports.compileString = allowInteropNamed( + 'sass.compileString', + compileString, + ); exports.compileAsync = allowInteropNamed('sass.compileAsync', compileAsync); - exports.compileStringAsync = - allowInteropNamed('sass.compileStringAsync', compileStringAsync); + exports.compileStringAsync = allowInteropNamed( + 'sass.compileStringAsync', + compileStringAsync, + ); exports.initCompiler = allowInteropNamed('sass.initCompiler', initCompiler); - exports.initAsyncCompiler = - allowInteropNamed('sass.initAsyncCompiler', initAsyncCompiler); + exports.initAsyncCompiler = allowInteropNamed( + 'sass.initAsyncCompiler', + initAsyncCompiler, + ); exports.Compiler = compilerClass; exports.AsyncCompiler = asyncCompilerClass; exports.Value = valueClass; @@ -53,9 +59,11 @@ void main() { exports.sassFalse = sassFalse; exports.Exception = exceptionClass; exports.Logger = LoggerNamespace( - silent: JSLogger( - warn: allowInteropNamed('sass.Logger.silent.warn', (_, __) {}), - debug: allowInteropNamed('sass.Logger.silent.debug', (_, __) {}))); + silent: JSLogger( + warn: allowInteropNamed('sass.Logger.silent.warn', (_, __) {}), + debug: allowInteropNamed('sass.Logger.silent.debug', (_, __) {}), + ), + ); exports.NodePackageImporter = nodePackageImporterClass; exports.deprecations = jsify(deprecations); exports.Version = versionClass; @@ -75,14 +83,15 @@ void main() { exports.renderSync = allowInteropNamed('sass.renderSync', renderSync); exports.types = Types( - Boolean: legacyBooleanClass, - Color: legacyColorClass, - List: legacyListClass, - Map: legacyMapClass, - Null: legacyNullClass, - Number: legacyNumberClass, - String: legacyStringClass, - Error: jsErrorClass); + Boolean: legacyBooleanClass, + Color: legacyColorClass, + List: legacyListClass, + Map: legacyMapClass, + Null: legacyNullClass, + Number: legacyNumberClass, + String: legacyStringClass, + Error: jsErrorClass, + ); exports.NULL = sassNull; exports.TRUE = sassTrue; exports.FALSE = sassFalse; diff --git a/lib/src/js/compile.dart b/lib/src/js/compile.dart index 50016334e..fbb952d87 100644 --- a/lib/src/js/compile.dart +++ b/lib/src/js/compile.dart @@ -36,28 +36,42 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { } var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var logger = JSToDartLogger(options?.logger, Logger.stderr(color: color), - ascii: ascii); + var logger = JSToDartLogger( + options?.logger, + Logger.stderr(color: color), + ascii: ascii, + ); try { - var result = compileToResult(path, - color: color, - loadPaths: options?.loadPaths, - quietDeps: options?.quietDeps ?? false, - style: _parseOutputStyle(options?.style), - verbose: options?.verbose ?? false, - charset: options?.charset ?? true, - sourceMap: options?.sourceMap ?? false, - logger: logger, - importers: options?.importers?.map(_parseImporter), - functions: _parseFunctions(options?.functions).cast(), - fatalDeprecations: parseDeprecations(logger, options?.fatalDeprecations, - supportVersions: true), - silenceDeprecations: - parseDeprecations(logger, options?.silenceDeprecations), - futureDeprecations: - parseDeprecations(logger, options?.futureDeprecations)); - return _convertResult(result, - includeSourceContents: options?.sourceMapIncludeSources ?? false); + var result = compileToResult( + path, + color: color, + loadPaths: options?.loadPaths, + quietDeps: options?.quietDeps ?? false, + style: _parseOutputStyle(options?.style), + verbose: options?.verbose ?? false, + charset: options?.charset ?? true, + sourceMap: options?.sourceMap ?? false, + logger: logger, + importers: options?.importers?.map(_parseImporter), + functions: _parseFunctions(options?.functions).cast(), + fatalDeprecations: parseDeprecations( + logger, + options?.fatalDeprecations, + supportVersions: true, + ), + silenceDeprecations: parseDeprecations( + logger, + options?.silenceDeprecations, + ), + futureDeprecations: parseDeprecations( + logger, + options?.futureDeprecations, + ), + ); + return _convertResult( + result, + includeSourceContents: options?.sourceMapIncludeSources ?? false, + ); } on SassException catch (error, stackTrace) { throwNodeException(error, color: color, ascii: ascii, trace: stackTrace); } @@ -70,32 +84,46 @@ NodeCompileResult compile(String path, [CompileOptions? options]) { NodeCompileResult compileString(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var logger = JSToDartLogger(options?.logger, Logger.stderr(color: color), - ascii: ascii); + var logger = JSToDartLogger( + options?.logger, + Logger.stderr(color: color), + ascii: ascii, + ); try { - var result = compileStringToResult(text, - syntax: parseSyntax(options?.syntax), - url: options?.url.andThen(jsToDartUrl), - color: color, - loadPaths: options?.loadPaths, - quietDeps: options?.quietDeps ?? false, - style: _parseOutputStyle(options?.style), - verbose: options?.verbose ?? false, - charset: options?.charset ?? true, - sourceMap: options?.sourceMap ?? false, - logger: logger, - importers: options?.importers?.map(_parseImporter), - importer: options?.importer.andThen(_parseImporter) ?? - (options?.url == null ? NoOpImporter() : null), - functions: _parseFunctions(options?.functions).cast(), - fatalDeprecations: parseDeprecations(logger, options?.fatalDeprecations, - supportVersions: true), - silenceDeprecations: - parseDeprecations(logger, options?.silenceDeprecations), - futureDeprecations: - parseDeprecations(logger, options?.futureDeprecations)); - return _convertResult(result, - includeSourceContents: options?.sourceMapIncludeSources ?? false); + var result = compileStringToResult( + text, + syntax: parseSyntax(options?.syntax), + url: options?.url.andThen(jsToDartUrl), + color: color, + loadPaths: options?.loadPaths, + quietDeps: options?.quietDeps ?? false, + style: _parseOutputStyle(options?.style), + verbose: options?.verbose ?? false, + charset: options?.charset ?? true, + sourceMap: options?.sourceMap ?? false, + logger: logger, + importers: options?.importers?.map(_parseImporter), + importer: options?.importer.andThen(_parseImporter) ?? + (options?.url == null ? NoOpImporter() : null), + functions: _parseFunctions(options?.functions).cast(), + fatalDeprecations: parseDeprecations( + logger, + options?.fatalDeprecations, + supportVersions: true, + ), + silenceDeprecations: parseDeprecations( + logger, + options?.silenceDeprecations, + ), + futureDeprecations: parseDeprecations( + logger, + options?.futureDeprecations, + ), + ); + return _convertResult( + result, + includeSourceContents: options?.sourceMapIncludeSources ?? false, + ); } on SassException catch (error, stackTrace) { throwNodeException(error, color: color, ascii: ascii, trace: stackTrace); } @@ -111,10 +139,15 @@ Promise compileAsync(String path, [CompileOptions? options]) { } var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var logger = JSToDartLogger(options?.logger, Logger.stderr(color: color), - ascii: ascii); - return _wrapAsyncSassExceptions(futureToPromise(() async { - var result = await compileToResultAsync(path, + var logger = JSToDartLogger( + options?.logger, + Logger.stderr(color: color), + ascii: ascii, + ); + return _wrapAsyncSassExceptions( + futureToPromise(() async { + var result = await compileToResultAsync( + path, color: color, loadPaths: options?.loadPaths, quietDeps: options?.quietDeps ?? false, @@ -123,18 +156,32 @@ Promise compileAsync(String path, [CompileOptions? options]) { charset: options?.charset ?? true, sourceMap: options?.sourceMap ?? false, logger: logger, - importers: options?.importers - ?.map((importer) => _parseAsyncImporter(importer)), + importers: options?.importers?.map( + (importer) => _parseAsyncImporter(importer), + ), functions: _parseFunctions(options?.functions, asynch: true), - fatalDeprecations: parseDeprecations(logger, options?.fatalDeprecations, - supportVersions: true), - silenceDeprecations: - parseDeprecations(logger, options?.silenceDeprecations), - futureDeprecations: - parseDeprecations(logger, options?.futureDeprecations)); - return _convertResult(result, - includeSourceContents: options?.sourceMapIncludeSources ?? false); - }()), color: color, ascii: ascii); + fatalDeprecations: parseDeprecations( + logger, + options?.fatalDeprecations, + supportVersions: true, + ), + silenceDeprecations: parseDeprecations( + logger, + options?.silenceDeprecations, + ), + futureDeprecations: parseDeprecations( + logger, + options?.futureDeprecations, + ), + ); + return _convertResult( + result, + includeSourceContents: options?.sourceMapIncludeSources ?? false, + ); + }()), + color: color, + ascii: ascii, + ); } /// The JS API `compileString` function. @@ -144,10 +191,15 @@ Promise compileAsync(String path, [CompileOptions? options]) { Promise compileStringAsync(String text, [CompileStringOptions? options]) { var color = options?.alertColor ?? hasTerminal; var ascii = options?.alertAscii ?? glyph.ascii; - var logger = JSToDartLogger(options?.logger, Logger.stderr(color: color), - ascii: ascii); - return _wrapAsyncSassExceptions(futureToPromise(() async { - var result = await compileStringToResultAsync(text, + var logger = JSToDartLogger( + options?.logger, + Logger.stderr(color: color), + ascii: ascii, + ); + return _wrapAsyncSassExceptions( + futureToPromise(() async { + var result = await compileStringToResultAsync( + text, syntax: parseSyntax(options?.syntax), url: options?.url.andThen(jsToDartUrl), color: color, @@ -158,28 +210,46 @@ Promise compileStringAsync(String text, [CompileStringOptions? options]) { charset: options?.charset ?? true, sourceMap: options?.sourceMap ?? false, logger: logger, - importers: options?.importers - ?.map((importer) => _parseAsyncImporter(importer)), - importer: options?.importer - .andThen((importer) => _parseAsyncImporter(importer)) ?? + importers: options?.importers?.map( + (importer) => _parseAsyncImporter(importer), + ), + importer: options?.importer.andThen( + (importer) => _parseAsyncImporter(importer), + ) ?? (options?.url == null ? NoOpImporter() : null), functions: _parseFunctions(options?.functions, asynch: true), - fatalDeprecations: parseDeprecations(logger, options?.fatalDeprecations, - supportVersions: true), - silenceDeprecations: - parseDeprecations(logger, options?.silenceDeprecations), - futureDeprecations: - parseDeprecations(logger, options?.futureDeprecations)); - return _convertResult(result, - includeSourceContents: options?.sourceMapIncludeSources ?? false); - }()), color: color, ascii: ascii); + fatalDeprecations: parseDeprecations( + logger, + options?.fatalDeprecations, + supportVersions: true, + ), + silenceDeprecations: parseDeprecations( + logger, + options?.silenceDeprecations, + ), + futureDeprecations: parseDeprecations( + logger, + options?.futureDeprecations, + ), + ); + return _convertResult( + result, + includeSourceContents: options?.sourceMapIncludeSources ?? false, + ); + }()), + color: color, + ascii: ascii, + ); } /// Converts a Dart [CompileResult] into a JS API [NodeCompileResult]. -NodeCompileResult _convertResult(CompileResult result, - {required bool includeSourceContents}) { - var sourceMap = - result.sourceMap?.toJson(includeSourceContents: includeSourceContents); +NodeCompileResult _convertResult( + CompileResult result, { + required bool includeSourceContents, +}) { + var sourceMap = result.sourceMap?.toJson( + includeSourceContents: includeSourceContents, + ); if (sourceMap is Map && !sourceMap.containsKey('sources')) { // Dart's source map library can omit the sources key, but JS's type // declaration doesn't allow that. @@ -191,24 +261,33 @@ NodeCompileResult _convertResult(CompileResult result, // The JS API tests expects *no* source map here, not a null source map. ? NodeCompileResult(css: result.css, loadedUrls: loadedUrls) : NodeCompileResult( - css: result.css, loadedUrls: loadedUrls, sourceMap: jsify(sourceMap)); + css: result.css, + loadedUrls: loadedUrls, + sourceMap: jsify(sourceMap), + ); } /// Catches `SassException`s thrown by [promise] and rethrows them as JS API /// exceptions. -Promise _wrapAsyncSassExceptions(Promise promise, - {required bool color, required bool ascii}) => +Promise _wrapAsyncSassExceptions( + Promise promise, { + required bool color, + required bool ascii, +}) => promise.then( - null, - allowInterop((error) => error is SassException + null, + allowInterop( + (error) => error is SassException ? throwNodeException(error, color: color, ascii: ascii) - : jsThrow(error as Object))); + : jsThrow(error as Object), + ), + ); /// Converts an output style string to an instance of [OutputStyle]. OutputStyle _parseOutputStyle(String? style) => switch (style) { null || 'expanded' => OutputStyle.expanded, 'compressed' => OutputStyle.compressed, - _ => jsThrow(JsError('Unknown output style "$style".')) + _ => jsThrow(JsError('Unknown output style "$style".')), }; /// Converts [importer] into an [AsyncImporter] that can be used with @@ -224,18 +303,27 @@ AsyncImporter _parseAsyncImporter(Object? importer) { if (importer.findFileUrl case var findFileUrl?) { if (canonicalize != null || load != null) { jsThrow( - JsError("An importer may not have a findFileUrl method as well as " - "canonicalize and load methods.")); + JsError( + "An importer may not have a findFileUrl method as well as " + "canonicalize and load methods.", + ), + ); } else { return JSToDartAsyncFileImporter(findFileUrl); } } else if (canonicalize == null || load == null) { - jsThrow(JsError( + jsThrow( + JsError( "An importer must have either canonicalize and load methods, or a " - "findFileUrl method.")); + "findFileUrl method.", + ), + ); } else { - return JSToDartAsyncImporter(canonicalize, load, - _normalizeNonCanonicalSchemes(importer.nonCanonicalScheme)); + return JSToDartAsyncImporter( + canonicalize, + load, + _normalizeNonCanonicalSchemes(importer.nonCanonicalScheme), + ); } } @@ -251,18 +339,27 @@ Importer _parseImporter(Object? importer) { if (importer.findFileUrl case var findFileUrl?) { if (canonicalize != null || load != null) { jsThrow( - JsError("An importer may not have a findFileUrl method as well as " - "canonicalize and load methods.")); + JsError( + "An importer may not have a findFileUrl method as well as " + "canonicalize and load methods.", + ), + ); } else { return JSToDartFileImporter(findFileUrl); } } else if (canonicalize == null || load == null) { - jsThrow(JsError( + jsThrow( + JsError( "An importer must have either canonicalize and load methods, or a " - "findFileUrl method.")); + "findFileUrl method.", + ), + ); } else { - return JSToDartImporter(canonicalize, load, - _normalizeNonCanonicalSchemes(importer.nonCanonicalScheme)); + return JSToDartImporter( + canonicalize, + load, + _normalizeNonCanonicalSchemes(importer.nonCanonicalScheme), + ); } } @@ -274,8 +371,11 @@ List? _normalizeNonCanonicalSchemes(Object? schemes) => List schemes => schemes.cast(), null => null, _ => jsThrow( - JsError('nonCanonicalScheme must be a string or list of strings, was ' - '"$schemes"')) + JsError( + 'nonCanonicalScheme must be a string or list of strings, was ' + '"$schemes"', + ), + ), }; /// Implements the simplification algorithm for custom function return `Value`s. @@ -286,13 +386,16 @@ Value _simplifyValue(Value value) => switch (value) { value.name, // ...the calculation name value.arguments // ...and simplified arguments .map(_simplifyCalcArg) - .toList() + .toList(), )) { ('calc', [var first]) => first as Value, ('calc', _) => throw ArgumentError('calc() requires exactly one argument.'), - ('clamp', [var min, var value, var max]) => - SassCalculation.clamp(min, value, max), + ('clamp', [var min, var value, var max]) => SassCalculation.clamp( + min, + value, + max, + ), ('clamp', _) => throw ArgumentError('clamp() requires exactly 3 arguments.'), ('min', var args) => SassCalculation.min(args), @@ -307,8 +410,11 @@ Value _simplifyValue(Value value) => switch (value) { /// Value instances. Object _simplifyCalcArg(Object value) => switch (value) { SassCalculation() => _simplifyValue(value), - CalculationOperation() => SassCalculation.operate(value.operator, - _simplifyCalcArg(value.left), _simplifyCalcArg(value.right)), + CalculationOperation() => SassCalculation.operate( + value.operator, + _simplifyCalcArg(value.left), + _simplifyCalcArg(value.right), + ), _ => value, }; @@ -326,7 +432,8 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { late Callable callable; callable = Callable.fromSignature(signature, (arguments) { var result = wrapJSExceptions( - () => (callback as Function)(toJSArray(arguments))); + () => (callback as Function)(toJSArray(arguments)), + ); if (result is Value) return _simplifyValue(result); if (isPromise(result)) { throw 'Invalid return value for custom function ' @@ -343,7 +450,8 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { late AsyncCallable callable; callable = AsyncCallable.fromSignature(signature, (arguments) async { var result = wrapJSExceptions( - () => (callback as Function)(toJSArray(arguments))); + () => (callback as Function)(toJSArray(arguments)), + ); if (isPromise(result)) { result = await promiseToFuture(result as Promise); } @@ -362,14 +470,16 @@ List _parseFunctions(Object? functions, {bool asynch = false}) { /// `importers` option to enable loading `pkg:` URLs from `node_modules`. final JSClass nodePackageImporterClass = () { var jsClass = createJSClass( - 'sass.NodePackageImporter', - (Object self, [String? entrypointDirectory]) => NodePackageImporter( - switch ((entrypointDirectory, entrypointFilename)) { - ((var directory?, _)) => directory, - (_, var filename?) => p.dirname(filename), - _ => throw "The Node package importer cannot determine an entry " - "point because `require.main.filename` is not defined. Please " - "provide an `entryPointDirectory` to the `NodePackageImporter`." - })); + 'sass.NodePackageImporter', + (Object self, [String? entrypointDirectory]) => NodePackageImporter( + switch ((entrypointDirectory, entrypointFilename)) { + ((var directory?, _)) => directory, + (_, var filename?) => p.dirname(filename), + _ => throw "The Node package importer cannot determine an entry " + "point because `require.main.filename` is not defined. Please " + "provide an `entryPointDirectory` to the `NodePackageImporter`.", + }, + ), + ); return jsClass; }(); diff --git a/lib/src/js/compile_result.dart b/lib/src/js/compile_result.dart index 81a17fea0..cf4bf5836 100644 --- a/lib/src/js/compile_result.dart +++ b/lib/src/js/compile_result.dart @@ -13,6 +13,9 @@ class NodeCompileResult { external Object? get sourceMap; external JSArray get loadedUrls; - external factory NodeCompileResult( - {required String css, Object? sourceMap, required JSArray loadedUrls}); + external factory NodeCompileResult({ + required String css, + Object? sourceMap, + required JSArray loadedUrls, + }); } diff --git a/lib/src/js/compiler.dart b/lib/src/js/compiler.dart index ab1886b3f..8326cf1a5 100644 --- a/lib/src/js/compiler.dart +++ b/lib/src/js/compiler.dart @@ -46,19 +46,27 @@ class AsyncCompiler extends Compiler { /// The JavaScript `Compiler` class. final JSClass compilerClass = () { var jsClass = createJSClass( - 'sass.Compiler', - (Object self) => { - jsThrow(JsError(("Compiler can not be directly constructed. " - "Please use `sass.initCompiler()` instead."))) - }); + 'sass.Compiler', + (Object self) => { + jsThrow( + JsError( + ("Compiler can not be directly constructed. " + "Please use `sass.initCompiler()` instead."), + ), + ), + }, + ); jsClass.defineMethods({ 'compile': (Compiler self, String path, [CompileOptions? options]) { self._throwIfDisposed(); return compile(path, options); }, - 'compileString': (Compiler self, String source, - [CompileStringOptions? options]) { + 'compileString': ( + Compiler self, + String source, [ + CompileStringOptions? options, + ]) { self._throwIfDisposed(); return compileString(source, options); }, @@ -76,22 +84,33 @@ Compiler initCompiler() => Compiler(); /// The JavaScript `AsyncCompiler` class. final JSClass asyncCompilerClass = () { var jsClass = createJSClass( - 'sass.AsyncCompiler', - (Object self) => { - jsThrow(JsError(("AsyncCompiler can not be directly constructed. " - "Please use `sass.initAsyncCompiler()` instead."))) - }); + 'sass.AsyncCompiler', + (Object self) => { + jsThrow( + JsError( + ("AsyncCompiler can not be directly constructed. " + "Please use `sass.initAsyncCompiler()` instead."), + ), + ), + }, + ); jsClass.defineMethods({ - 'compileAsync': (AsyncCompiler self, String path, - [CompileOptions? options]) { + 'compileAsync': ( + AsyncCompiler self, + String path, [ + CompileOptions? options, + ]) { self._throwIfDisposed(); var compilation = compileAsync(path, options); self.addCompilation(compilation); return compilation; }, - 'compileStringAsync': (AsyncCompiler self, String source, - [CompileStringOptions? options]) { + 'compileStringAsync': ( + AsyncCompiler self, + String source, [ + CompileStringOptions? options, + ]) { self._throwIfDisposed(); var compilation = compileStringAsync(source, options); self.addCompilation(compilation); @@ -99,11 +118,13 @@ final JSClass asyncCompilerClass = () { }, 'dispose': (AsyncCompiler self) { self._disposed = true; - return futureToPromise((() async { - self.compilations.close(); - await self.compilations.future; - })()); - } + return futureToPromise( + (() async { + self.compilations.close(); + await self.compilations.future; + })(), + ); + }, }); getJSClass(AsyncCompiler()).injectSuperclass(jsClass); diff --git a/lib/src/js/deprecations.dart b/lib/src/js/deprecations.dart index e26fe9ea3..acde8b6e6 100644 --- a/lib/src/js/deprecations.dart +++ b/lib/src/js/deprecations.dart @@ -18,12 +18,13 @@ class Deprecation { external Version? get deprecatedIn; external Version? get obsoleteIn; - external factory Deprecation( - {required String id, - required String status, - String? description, - Version? deprecatedIn, - Version? obsoleteIn}); + external factory Deprecation({ + required String id, + required String status, + String? description, + Version? deprecatedIn, + Version? obsoleteIn, + }); } final Map deprecations = { @@ -32,17 +33,17 @@ final Map deprecations = { // in the JS API. if (deprecation != dart.Deprecation.calcInterp) deprecation.id: Deprecation( - id: deprecation.id, - status: (() => switch (deprecation) { - dart.Deprecation(isFuture: true) => 'future', - dart.Deprecation(deprecatedIn: null, obsoleteIn: null) => - 'user', - dart.Deprecation(obsoleteIn: null) => 'active', - _ => 'obsolete' - })(), - description: deprecation.description, - deprecatedIn: deprecation.deprecatedIn, - obsoleteIn: deprecation.deprecatedIn), + id: deprecation.id, + status: (() => switch (deprecation) { + dart.Deprecation(isFuture: true) => 'future', + dart.Deprecation(deprecatedIn: null, obsoleteIn: null) => 'user', + dart.Deprecation(obsoleteIn: null) => 'active', + _ => 'obsolete', + })(), + description: deprecation.description, + deprecatedIn: deprecation.deprecatedIn, + obsoleteIn: deprecation.deprecatedIn, + ), }; /// Parses a list of [deprecations] from JS into an list of Dart [Deprecation] @@ -51,8 +52,10 @@ final Map deprecations = { /// [deprecations] can contain deprecation IDs, JS Deprecation objects, and /// (if [supportVersions] is true) [Version]s. Iterable? parseDeprecations( - JSToDartLogger logger, List? deprecations, - {bool supportVersions = false}) { + JSToDartLogger logger, + List? deprecations, { + bool supportVersions = false, +}) { if (deprecations == null) return null; return () sync* { for (var item in deprecations) { @@ -80,8 +83,12 @@ Iterable? parseDeprecations( /// The JavaScript `Version` class. final JSClass versionClass = () { - var jsClass = createJSClass('sass.Version', - (Object self, int major, int minor, int patch) { + var jsClass = createJSClass('sass.Version', ( + Object self, + int major, + int minor, + int patch, + ) { return Version(major, minor, patch); }); @@ -89,7 +96,8 @@ final JSClass versionClass = () { var v = Version.parse(version); if (v.isPreRelease || v.build.isNotEmpty) { throw FormatException( - 'Build identifiers and prerelease versions not supported.'); + 'Build identifiers and prerelease versions not supported.', + ); } return v; }); diff --git a/lib/src/js/exception.dart b/lib/src/js/exception.dart index 982df9458..6cde9924e 100644 --- a/lib/src/js/exception.dart +++ b/lib/src/js/exception.dart @@ -51,7 +51,7 @@ final JSClass exceptionClass = () { exception._dartException.message, 'sassStack': (_NodeException exception) => exception._dartException.trace.toString(), - 'span': (_NodeException exception) => exception._dartException.span + 'span': (_NodeException exception) => exception._dartException.span, }); return jsClass; @@ -66,14 +66,18 @@ final JSClass exceptionClass = () { /// stringification. /// /// If [trace] is passed, it's used as the stack trace for the JS exception. -Never throwNodeException(SassException exception, - {required bool color, required bool ascii, StackTrace? trace}) { +Never throwNodeException( + SassException exception, { + required bool color, + required bool ascii, + StackTrace? trace, +}) { var wasAscii = glyph.ascii; glyph.ascii = ascii; try { var jsException = exceptionClass.construct([ exception, - exception.toString(color: color).replaceFirst('Error: ', '') + exception.toString(color: color).replaceFirst('Error: ', ''), ]) as _NodeException; trace = getTrace(exception) ?? trace; if (trace != null) attachJsStack(jsException, trace); diff --git a/lib/src/js/immutable.dart b/lib/src/js/immutable.dart index b9dfc6f28..62b0b1f96 100644 --- a/lib/src/js/immutable.dart +++ b/lib/src/js/immutable.dart @@ -47,8 +47,10 @@ ImmutableMap dartMapToImmutableMap(Map dartMap) { /// Converts an [ImmutableMap] into an equivalent Dart map. Map immutableMapToDartMap(ImmutableMap immutableMap) { var dartMap = {}; - immutableMap.forEach(allowInterop((value, key, _) { - dartMap[key] = value; - })); + immutableMap.forEach( + allowInterop((value, key, _) { + dartMap[key] = value; + }), + ); return dartMap; } diff --git a/lib/src/js/legacy.dart b/lib/src/js/legacy.dart index 65ae4ca48..12e9e1d67 100644 --- a/lib/src/js/legacy.dart +++ b/lib/src/js/legacy.dart @@ -42,32 +42,43 @@ import 'utils.dart'; /// /// [render]: https://github.com/sass/node-sass#options void render( - RenderOptions options, void callback(Object? error, RenderResult? result)) { + RenderOptions options, + void callback(Object? error, RenderResult? result), +) { if (!isNodeJs) { jsThrow(JsError("The render() method is only available in Node.js.")); } if (options.fiber case var fiber?) { - fiber.call(allowInterop(() { - try { - callback(null, renderSync(options)); - } catch (error) { - callback(error, null); - } - return null; - })).run(); + fiber.call( + allowInterop(() { + try { + callback(null, renderSync(options)); + } catch (error) { + callback(error, null); + } + return null; + }), + ).run(); } else { - _renderAsync(options).then((result) { - callback(null, result); - }, onError: (Object error, StackTrace stackTrace) { - if (error is SassException) { - callback(_wrapException(error, stackTrace), null); - } else { - callback( - _newRenderError(error.toString(), getTrace(error) ?? stackTrace, - status: 3), - null); - } - }); + _renderAsync(options).then( + (result) { + callback(null, result); + }, + onError: (Object error, StackTrace stackTrace) { + if (error is SassException) { + callback(_wrapException(error, stackTrace), null); + } else { + callback( + _newRenderError( + error.toString(), + getTrace(error) ?? stackTrace, + status: 3, + ), + null, + ); + } + }, + ); } } @@ -77,51 +88,65 @@ Future _renderAsync(RenderOptions options) async { CompileResult result; var file = options.file.andThen(p.absolute); - var logger = - JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal)); + var logger = JSToDartLogger( + options.logger, + Logger.stderr(color: hasTerminal), + ); if (options.data case var data?) { - result = await compileStringAsync(data, - nodeImporter: _parseImporter(options, start), - importCache: _parsePackageImportersAsync(options, start), - functions: _parseFunctions(options, start, asynch: true), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - url: file == null ? 'stdin' : p.toUri(file).toString(), - quietDeps: options.quietDeps ?? false, - fatalDeprecations: parseDeprecations(logger, options.fatalDeprecations, - supportVersions: true), - futureDeprecations: - parseDeprecations(logger, options.futureDeprecations), - silenceDeprecations: - parseDeprecations(logger, options.silenceDeprecations), - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: logger); + result = await compileStringAsync( + data, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImportersAsync(options, start), + functions: _parseFunctions(options, start, asynch: true), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + url: file == null ? 'stdin' : p.toUri(file).toString(), + quietDeps: options.quietDeps ?? false, + fatalDeprecations: parseDeprecations( + logger, + options.fatalDeprecations, + supportVersions: true, + ), + futureDeprecations: parseDeprecations(logger, options.futureDeprecations), + silenceDeprecations: parseDeprecations( + logger, + options.silenceDeprecations, + ), + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: logger, + ); } else if (file != null) { - result = await compileAsync(file, - nodeImporter: _parseImporter(options, start), - importCache: _parsePackageImportersAsync(options, start), - functions: _parseFunctions(options, start, asynch: true), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - quietDeps: options.quietDeps ?? false, - fatalDeprecations: parseDeprecations(logger, options.fatalDeprecations, - supportVersions: true), - futureDeprecations: - parseDeprecations(logger, options.futureDeprecations), - silenceDeprecations: - parseDeprecations(logger, options.silenceDeprecations), - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: logger); + result = await compileAsync( + file, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImportersAsync(options, start), + functions: _parseFunctions(options, start, asynch: true), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + quietDeps: options.quietDeps ?? false, + fatalDeprecations: parseDeprecations( + logger, + options.fatalDeprecations, + supportVersions: true, + ), + futureDeprecations: parseDeprecations(logger, options.futureDeprecations), + silenceDeprecations: parseDeprecations( + logger, + options.silenceDeprecations, + ), + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: logger, + ); } else { throw ArgumentError("Either options.data or options.file must be set."); } @@ -144,51 +169,71 @@ RenderResult renderSync(RenderOptions options) { CompileResult result; var file = options.file.andThen(p.absolute); - var logger = - JSToDartLogger(options.logger, Logger.stderr(color: hasTerminal)); + var logger = JSToDartLogger( + options.logger, + Logger.stderr(color: hasTerminal), + ); if (options.data case var data?) { - result = compileString(data, - nodeImporter: _parseImporter(options, start), - importCache: _parsePackageImporters(options, start), - functions: _parseFunctions(options, start).cast(), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - url: file == null ? 'stdin' : p.toUri(file).toString(), - quietDeps: options.quietDeps ?? false, - fatalDeprecations: parseDeprecations( - logger, options.fatalDeprecations, supportVersions: true), - futureDeprecations: - parseDeprecations(logger, options.futureDeprecations), - silenceDeprecations: - parseDeprecations(logger, options.silenceDeprecations), - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: logger); + result = compileString( + data, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImporters(options, start), + functions: _parseFunctions(options, start).cast(), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + url: file == null ? 'stdin' : p.toUri(file).toString(), + quietDeps: options.quietDeps ?? false, + fatalDeprecations: parseDeprecations( + logger, + options.fatalDeprecations, + supportVersions: true, + ), + futureDeprecations: parseDeprecations( + logger, + options.futureDeprecations, + ), + silenceDeprecations: parseDeprecations( + logger, + options.silenceDeprecations, + ), + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: logger, + ); } else if (file != null) { - result = compile(file, - nodeImporter: _parseImporter(options, start), - importCache: _parsePackageImporters(options, start), - functions: _parseFunctions(options, start).cast(), - syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, - style: _parseOutputStyle(options.outputStyle), - useSpaces: options.indentType != 'tab', - indentWidth: _parseIndentWidth(options.indentWidth), - lineFeed: _parseLineFeed(options.linefeed), - quietDeps: options.quietDeps ?? false, - fatalDeprecations: parseDeprecations( - logger, options.fatalDeprecations, supportVersions: true), - futureDeprecations: - parseDeprecations(logger, options.futureDeprecations), - silenceDeprecations: - parseDeprecations(logger, options.silenceDeprecations), - verbose: options.verbose ?? false, - charset: options.charset ?? true, - sourceMap: _enableSourceMaps(options), - logger: logger); + result = compile( + file, + nodeImporter: _parseImporter(options, start), + importCache: _parsePackageImporters(options, start), + functions: _parseFunctions(options, start).cast(), + syntax: isTruthy(options.indentedSyntax) ? Syntax.sass : null, + style: _parseOutputStyle(options.outputStyle), + useSpaces: options.indentType != 'tab', + indentWidth: _parseIndentWidth(options.indentWidth), + lineFeed: _parseLineFeed(options.linefeed), + quietDeps: options.quietDeps ?? false, + fatalDeprecations: parseDeprecations( + logger, + options.fatalDeprecations, + supportVersions: true, + ), + futureDeprecations: parseDeprecations( + logger, + options.futureDeprecations, + ), + silenceDeprecations: parseDeprecations( + logger, + options.silenceDeprecations, + ), + verbose: options.verbose ?? false, + charset: options.charset ?? true, + sourceMap: _enableSourceMaps(options), + logger: logger, + ); } else { throw ArgumentError("Either options.data or options.file must be set."); } @@ -197,8 +242,13 @@ RenderResult renderSync(RenderOptions options) { } on SassException catch (error, stackTrace) { jsThrow(_wrapException(error, stackTrace)); } catch (error, stackTrace) { - jsThrow(_newRenderError(error.toString(), getTrace(error) ?? stackTrace, - status: 3)); + jsThrow( + _newRenderError( + error.toString(), + getTrace(error) ?? stackTrace, + status: 3, + ), + ); } } @@ -208,15 +258,17 @@ JsError _wrapException(Object exception, StackTrace stackTrace) { var file = switch (exception.span.sourceUrl) { null => 'stdin', Uri(scheme: 'file') && var url => p.fromUri(url), - var url => url.toString() + var url => url.toString(), }; - return _newRenderError(exception.toString().replaceFirst("Error: ", ""), - getTrace(exception) ?? stackTrace, - line: exception.span.start.line + 1, - column: exception.span.start.column + 1, - file: file, - status: 1); + return _newRenderError( + exception.toString().replaceFirst("Error: ", ""), + getTrace(exception) ?? stackTrace, + line: exception.span.start.line + 1, + column: exception.span.start.column + 1, + file: file, + status: 1, + ); } else { var error = JsError(exception.toString()); attachJsStack(error, getTrace(exception) ?? stackTrace); @@ -229,8 +281,11 @@ JsError _wrapException(Object exception, StackTrace stackTrace) { /// /// This is typed to always return [AsyncCallable], but in practice it will /// return a `List` if [asynch] is `false`. -List _parseFunctions(RenderOptions options, DateTime start, - {bool asynch = false}) { +List _parseFunctions( + RenderOptions options, + DateTime start, { + bool asynch = false, +}) { var functions = options.functions; if (functions == null) return const []; @@ -240,45 +295,61 @@ List _parseFunctions(RenderOptions options, DateTime start, context.options.context = context; if (options.fiber case var fiber?) { - result.add(Callable.fromSignature(signature.trimLeft(), (arguments) { - var currentFiber = fiber.current; - var jsArguments = [ - ...arguments.map(wrapValue), - allowInterop(([Object? result]) { - // Schedule a microtask so we don't try to resume the running fiber - // if [importer] calls `done()` synchronously. - scheduleMicrotask(() => currentFiber.run(result)); - }) - ]; - var result = wrapJSExceptions( - () => (callback as JSFunction).apply(context, jsArguments)); - return unwrapValue(isUndefined(result) - // Run `fiber.yield()` in runZoned() so that Dart resets the current - // zone once it's done. Otherwise, interweaving fibers can leave - // `Zone.current` in an inconsistent state. - ? runZoned(() => fiber.yield()) - : result); - }, requireParens: false)); + result.add( + Callable.fromSignature(signature.trimLeft(), (arguments) { + var currentFiber = fiber.current; + var jsArguments = [ + ...arguments.map(wrapValue), + allowInterop(([Object? result]) { + // Schedule a microtask so we don't try to resume the running fiber + // if [importer] calls `done()` synchronously. + scheduleMicrotask(() => currentFiber.run(result)); + }), + ]; + var result = wrapJSExceptions( + () => (callback as JSFunction).apply(context, jsArguments), + ); + return unwrapValue( + isUndefined(result) + // Run `fiber.yield()` in runZoned() so that Dart resets the current + // zone once it's done. Otherwise, interweaving fibers can leave + // `Zone.current` in an inconsistent state. + ? runZoned(() => fiber.yield()) + : result, + ); + }, requireParens: false), + ); } else if (!asynch) { - result.add(Callable.fromSignature( + result.add( + Callable.fromSignature( signature.trimLeft(), - (arguments) => unwrapValue(wrapJSExceptions(() => - (callback as JSFunction) - .apply(context, arguments.map(wrapValue).toList()))), - requireParens: false)); + (arguments) => unwrapValue( + wrapJSExceptions( + () => (callback as JSFunction).apply( + context, + arguments.map(wrapValue).toList(), + ), + ), + ), + requireParens: false, + ), + ); } else { result.add( - AsyncCallable.fromSignature(signature.trimLeft(), (arguments) async { - var completer = Completer(); - var jsArguments = [ - ...arguments.map(wrapValue), - allowInterop(([Object? result]) => completer.complete(result)) - ]; - var result = wrapJSExceptions( - () => (callback as JSFunction).apply(context, jsArguments)); - return unwrapValue( - isUndefined(result) ? await completer.future : result); - }, requireParens: false)); + AsyncCallable.fromSignature(signature.trimLeft(), (arguments) async { + var completer = Completer(); + var jsArguments = [ + ...arguments.map(wrapValue), + allowInterop(([Object? result]) => completer.complete(result)), + ]; + var result = wrapJSExceptions( + () => (callback as JSFunction).apply(context, jsArguments), + ); + return unwrapValue( + isUndefined(result) ? await completer.future : result, + ); + }, requireParens: false), + ); } }); return result; @@ -298,15 +369,24 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { if (options.fiber case var fiber?) { importers = importers.map((importer) { - return allowInteropCaptureThis( - (Object thisArg, String url, String previous, [Object? _]) { + return allowInteropCaptureThis(( + Object thisArg, + String url, + String previous, [ + Object? _, + ]) { var currentFiber = fiber.current; - var result = call3(importer, thisArg, url, previous, - allowInterop((Object result) { - // Schedule a microtask so we don't try to resume the running fiber if - // [importer] calls `done()` synchronously. - scheduleMicrotask(() => currentFiber.run(result)); - })); + var result = call3( + importer, + thisArg, + url, + previous, + allowInterop((Object result) { + // Schedule a microtask so we don't try to resume the running fiber if + // [importer] calls `done()` synchronously. + scheduleMicrotask(() => currentFiber.run(result)); + }), + ); // Run `fiber.yield()` in runZoned() so that Dart resets the current // zone once it's done. Otherwise, interweaving fibers can leave @@ -323,7 +403,9 @@ NodeImporter _parseImporter(RenderOptions options, DateTime start) { /// Creates an [AsyncImportCache] for Package Importers. AsyncImportCache? _parsePackageImportersAsync( - RenderOptions options, DateTime start) { + RenderOptions options, + DateTime start, +) { if (options.pkgImporter is NodePackageImporter) { return AsyncImportCache.only([options.pkgImporter!]); } @@ -343,32 +425,35 @@ ImportCache? _parsePackageImporters(RenderOptions options, DateTime start) { RenderContextOptions _contextOptions(RenderOptions options, DateTime start) { var includePaths = List.from(options.includePaths ?? []); return RenderContextOptions( - file: options.file, - data: options.data, - includePaths: ([p.current, ...includePaths]).join(isWindows ? ';' : ':'), - precision: SassNumber.precision, - style: 1, - indentType: options.indentType == 'tab' ? 1 : 0, - indentWidth: _parseIndentWidth(options.indentWidth) ?? 2, - linefeed: _parseLineFeed(options.linefeed).text, - result: RenderContextResult( - stats: RenderContextResultStats( - start: start.millisecondsSinceEpoch, - entry: options.file ?? 'data'))); + file: options.file, + data: options.data, + includePaths: ([p.current, ...includePaths]).join(isWindows ? ';' : ':'), + precision: SassNumber.precision, + style: 1, + indentType: options.indentType == 'tab' ? 1 : 0, + indentWidth: _parseIndentWidth(options.indentWidth) ?? 2, + linefeed: _parseLineFeed(options.linefeed).text, + result: RenderContextResult( + stats: RenderContextResultStats( + start: start.millisecondsSinceEpoch, + entry: options.file ?? 'data', + ), + ), + ); } /// Parse [style] into an [OutputStyle]. OutputStyle _parseOutputStyle(String? style) => switch (style) { null || 'expanded' => OutputStyle.expanded, 'compressed' => OutputStyle.compressed, - _ => jsThrow(JsError('Unknown output style "$style".')) + _ => jsThrow(JsError('Unknown output style "$style".')), }; /// Parses the indentation width into an [int]. int? _parseIndentWidth(Object? width) => switch (width) { null => null, int() => width, - _ => int.parse(width.toString()) + _ => int.parse(width.toString()), }; /// Parses the name of a line feed type into a [LineFeed]. @@ -376,12 +461,15 @@ LineFeed _parseLineFeed(String? str) => switch (str) { 'cr' => LineFeed.cr, 'crlf' => LineFeed.crlf, 'lfcr' => LineFeed.lfcr, - _ => LineFeed.lf + _ => LineFeed.lf, }; /// Creates a [RenderResult] that exposes [result] in the Node Sass API format. RenderResult _newRenderResult( - RenderOptions options, CompileResult result, DateTime start) { + RenderOptions options, + CompileResult result, + DateTime start, +) { var end = DateTime.now(); var css = result.css; @@ -401,7 +489,7 @@ RenderResult _newRenderResult( if (outFile == null) { sourceMap.targetUrl = switch (options.file) { var file? => p.toUri(p.setExtension(file, '.css')).toString(), - _ => sourceMap.targetUrl = 'stdin.css' + _ => sourceMap.targetUrl = 'stdin.css', }; } else { sourceMap.targetUrl = @@ -422,32 +510,37 @@ RenderResult _newRenderResult( } var json = sourceMap.toJson( - includeSourceContents: isTruthy(options.sourceMapContents)); + includeSourceContents: isTruthy(options.sourceMapContents), + ); sourceMapBytes = utf8Encode(jsonEncode(json)); if (!isTruthy(options.omitSourceMapUrl)) { var url = isTruthy(options.sourceMapEmbed) ? Uri.dataFromBytes(sourceMapBytes, mimeType: "application/json") - : p.toUri(outFile == null - ? sourceMapPath - : p.relative(sourceMapPath, from: p.dirname(outFile))); + : p.toUri( + outFile == null + ? sourceMapPath + : p.relative(sourceMapPath, from: p.dirname(outFile)), + ); var escapedUrl = url.toString().replaceAll("*/", '%2A/'); css += "\n\n/*# sourceMappingURL=$escapedUrl */"; } } return RenderResult( - css: utf8Encode(css), - map: sourceMapBytes, - stats: RenderResultStats( - entry: options.file ?? 'data', - start: start.millisecondsSinceEpoch, - end: end.millisecondsSinceEpoch, - duration: end.difference(start).inMilliseconds, - includedFiles: [ - for (var url in result.loadedUrls) - url.scheme == 'file' ? p.fromUri(url) : url.toString() - ])); + css: utf8Encode(css), + map: sourceMapBytes, + stats: RenderResultStats( + entry: options.file ?? 'data', + start: start.millisecondsSinceEpoch, + end: end.millisecondsSinceEpoch, + duration: end.difference(start).inMilliseconds, + includedFiles: [ + for (var url in result.loadedUrls) + url.scheme == 'file' ? p.fromUri(url) : url.toString(), + ], + ), + ); } /// Returns whether source maps are enabled by [options]. @@ -457,8 +550,14 @@ bool _enableSourceMaps(RenderOptions options) => /// Creates a [JsError] with the given fields added to it so it acts like a Node /// Sass error. -JsError _newRenderError(String message, StackTrace stackTrace, - {int? line, int? column, String? file, int? status}) { +JsError _newRenderError( + String message, + StackTrace stackTrace, { + int? line, + int? column, + String? file, + int? status, +}) { var error = JsError(message); setProperty(error, 'formatted', 'Error: $message'); if (line != null) setProperty(error, 'line', line); diff --git a/lib/src/js/legacy/render_context.dart b/lib/src/js/legacy/render_context.dart index d42db52f5..a5fcf7c38 100644 --- a/lib/src/js/legacy/render_context.dart +++ b/lib/src/js/legacy/render_context.dart @@ -10,8 +10,10 @@ class RenderContext { external RenderContextOptions get options; external bool? get fromImport; - external factory RenderContext( - {required RenderContextOptions options, bool? fromImport}); + external factory RenderContext({ + required RenderContextOptions options, + bool? fromImport, + }); } @JS() @@ -29,16 +31,17 @@ class RenderContextOptions { external set context(RenderContext value); external RenderContextResult get result; - external factory RenderContextOptions( - {String? file, - String? data, - required String includePaths, - required int precision, - required int style, - required int indentType, - required int indentWidth, - required String linefeed, - required RenderContextResult result}); + external factory RenderContextOptions({ + String? file, + String? data, + required String includePaths, + required int precision, + required int style, + required int indentType, + required int indentWidth, + required String linefeed, + required RenderContextResult result, + }); } @JS() @@ -46,8 +49,9 @@ class RenderContextOptions { class RenderContextResult { external RenderContextResultStats get stats; - external factory RenderContextResult( - {required RenderContextResultStats stats}); + external factory RenderContextResult({ + required RenderContextResultStats stats, + }); } @JS() @@ -56,6 +60,8 @@ class RenderContextResultStats { external int get start; external String get entry; - external factory RenderContextResultStats( - {required int start, required String entry}); + external factory RenderContextResultStats({ + required int start, + required String entry, + }); } diff --git a/lib/src/js/legacy/render_options.dart b/lib/src/js/legacy/render_options.dart index e63de45f1..d77ff205e 100644 --- a/lib/src/js/legacy/render_options.dart +++ b/lib/src/js/legacy/render_options.dart @@ -37,30 +37,31 @@ class RenderOptions { external bool? get charset; external JSLogger? get logger; - external factory RenderOptions( - {String? file, - String? data, - Object? importer, - NodePackageImporter? pkgImporter, - Object? functions, - List? includePaths, - bool? indentedSyntax, - bool? omitSourceMapUrl, - String? outFile, - String? outputStyle, - String? indentType, - Object? indentWidth, - String? linefeed, - FiberClass? fiber, - Object? sourceMap, - bool? sourceMapContents, - bool? sourceMapEmbed, - String? sourceMapRoot, - bool? quietDeps, - List? fatalDeprecations, - List? futureDeprecations, - List? silenceDeprecations, - bool? verbose, - bool? charset, - JSLogger? logger}); + external factory RenderOptions({ + String? file, + String? data, + Object? importer, + NodePackageImporter? pkgImporter, + Object? functions, + List? includePaths, + bool? indentedSyntax, + bool? omitSourceMapUrl, + String? outFile, + String? outputStyle, + String? indentType, + Object? indentWidth, + String? linefeed, + FiberClass? fiber, + Object? sourceMap, + bool? sourceMapContents, + bool? sourceMapEmbed, + String? sourceMapRoot, + bool? quietDeps, + List? fatalDeprecations, + List? futureDeprecations, + List? silenceDeprecations, + bool? verbose, + bool? charset, + JSLogger? logger, + }); } diff --git a/lib/src/js/legacy/render_result.dart b/lib/src/js/legacy/render_result.dart index 3e8a9ee54..9a109f701 100644 --- a/lib/src/js/legacy/render_result.dart +++ b/lib/src/js/legacy/render_result.dart @@ -13,10 +13,11 @@ class RenderResult { external Uint8List? get map; external RenderResultStats get stats; - external factory RenderResult( - {required Uint8List css, - Uint8List? map, - required RenderResultStats stats}); + external factory RenderResult({ + required Uint8List css, + Uint8List? map, + required RenderResultStats stats, + }); } @JS() @@ -28,10 +29,11 @@ class RenderResultStats { external int get duration; external List get includedFiles; - external factory RenderResultStats( - {required String entry, - required int start, - required int end, - required int duration, - required List includedFiles}); + external factory RenderResultStats({ + required String entry, + required int start, + required int end, + required int duration, + required List includedFiles, + }); } diff --git a/lib/src/js/legacy/types.dart b/lib/src/js/legacy/types.dart index c1e19d90f..9ac75163b 100644 --- a/lib/src/js/legacy/types.dart +++ b/lib/src/js/legacy/types.dart @@ -21,13 +21,14 @@ class Types { external set String(JSClass function); external set Error(JSClass function); - external factory Types( - {JSClass? Boolean, - JSClass? Color, - JSClass? List, - JSClass? Map, - JSClass? Null, - JSClass? Number, - JSClass? String, - JSClass? Error}); + external factory Types({ + JSClass? Boolean, + JSClass? Color, + JSClass? List, + JSClass? Map, + JSClass? Null, + JSClass? Number, + JSClass? String, + JSClass? Error, + }); } diff --git a/lib/src/js/legacy/value.dart b/lib/src/js/legacy/value.dart index 0087aa1ae..113bb66c1 100644 --- a/lib/src/js/legacy/value.dart +++ b/lib/src/js/legacy/value.dart @@ -45,5 +45,5 @@ Object wrapValue(Value value) => switch (value) { SassMap() => newNodeSassMap(value), SassNumber() => newNodeSassNumber(value), SassString() => newNodeSassString(value), - _ => value + _ => value, }; diff --git a/lib/src/js/legacy/value/color.dart b/lib/src/js/legacy/value/color.dart index 0545e761e..d80945abd 100644 --- a/lib/src/js/legacy/value/color.dart +++ b/lib/src/js/legacy/value/color.dart @@ -20,9 +20,14 @@ Object newNodeSassColor(SassColor value) => legacyColorClass.construct([null, null, null, null, value]); /// The JS `sass.types.Color` class. -final JSClass legacyColorClass = createJSClass('sass.types.Color', - (_NodeSassColor thisArg, num? redOrArgb, - [num? green, num? blue, num? alpha, SassColor? dartValue]) { +final JSClass legacyColorClass = createJSClass('sass.types.Color', ( + _NodeSassColor thisArg, + num? redOrArgb, [ + num? green, + num? blue, + num? alpha, + SassColor? dartValue, +]) { if (dartValue != null) { thisArg.dartValue = dartValue; return; @@ -46,8 +51,12 @@ final JSClass legacyColorClass = createJSClass('sass.types.Color', red = redOrArgb!; } - thisArg.dartValue = SassColor.rgb(_clamp(red), _clamp(green), _clamp(blue), - alpha.andThen((alpha) => clampLikeCss(alpha.toDouble(), 0, 1)) ?? 1); + thisArg.dartValue = SassColor.rgb( + _clamp(red), + _clamp(green), + _clamp(blue), + alpha.andThen((alpha) => clampLikeCss(alpha.toDouble(), 0, 1)) ?? 1, + ); }) ..defineMethods({ 'getR': (_NodeSassColor thisArg) => thisArg.dartValue.red, @@ -64,9 +73,10 @@ final JSClass legacyColorClass = createJSClass('sass.types.Color', thisArg.dartValue = thisArg.dartValue.changeRgb(blue: _clamp(value)); }, 'setA': (_NodeSassColor thisArg, num value) { - thisArg.dartValue = thisArg.dartValue - .changeRgb(alpha: clampLikeCss(value.toDouble(), 0, 1)); - } + thisArg.dartValue = thisArg.dartValue.changeRgb( + alpha: clampLikeCss(value.toDouble(), 0, 1), + ); + }, }); /// Clamps [channel] within the range 0, 255 and rounds it to the nearest diff --git a/lib/src/js/legacy/value/list.dart b/lib/src/js/legacy/value/list.dart index 29a6d371a..91e9b8ea1 100644 --- a/lib/src/js/legacy/value/list.dart +++ b/lib/src/js/legacy/value/list.dart @@ -19,13 +19,18 @@ Object newNodeSassList(SassList value) => legacyListClass.construct([null, null, value]); /// The JS `sass.types.List` class. -final JSClass legacyListClass = createJSClass('sass.types.List', - (_NodeSassList thisArg, int? length, - [bool? commaSeparator, SassList? dartValue]) { +final JSClass legacyListClass = createJSClass('sass.types.List', ( + _NodeSassList thisArg, + int? length, [ + bool? commaSeparator, + SassList? dartValue, +]) { thisArg.dartValue = dartValue ?? // Either [dartValue] or [length] must be passed. - SassList(Iterable.generate(length!, (_) => sassNull), - (commaSeparator ?? true) ? ListSeparator.comma : ListSeparator.space); + SassList( + Iterable.generate(length!, (_) => sassNull), + (commaSeparator ?? true) ? ListSeparator.comma : ListSeparator.space, + ); }) ..defineMethods({ 'getValue': (_NodeSassList thisArg, int index) => @@ -38,9 +43,11 @@ final JSClass legacyListClass = createJSClass('sass.types.List', 'getSeparator': (_NodeSassList thisArg) => thisArg.dartValue.separator == ListSeparator.comma, 'setSeparator': (_NodeSassList thisArg, bool isComma) { - thisArg.dartValue = SassList(thisArg.dartValue.asList, - isComma ? ListSeparator.comma : ListSeparator.space, - brackets: thisArg.dartValue.hasBrackets); + thisArg.dartValue = SassList( + thisArg.dartValue.asList, + isComma ? ListSeparator.comma : ListSeparator.space, + brackets: thisArg.dartValue.hasBrackets, + ); }, - 'getLength': (_NodeSassList thisArg) => thisArg.dartValue.asList.length + 'getLength': (_NodeSassList thisArg) => thisArg.dartValue.asList.length, }); diff --git a/lib/src/js/legacy/value/map.dart b/lib/src/js/legacy/value/map.dart index 44b59c618..1c3fed6a9 100644 --- a/lib/src/js/legacy/value/map.dart +++ b/lib/src/js/legacy/value/map.dart @@ -19,13 +19,19 @@ class _NodeSassMap { Object newNodeSassMap(SassMap value) => legacyMapClass.construct([null, value]); /// The JS `sass.types.Map` class. -final JSClass legacyMapClass = createJSClass('sass.types.Map', - (_NodeSassMap thisArg, int? length, [SassMap? dartValue]) { +final JSClass legacyMapClass = createJSClass('sass.types.Map', ( + _NodeSassMap thisArg, + int? length, [ + SassMap? dartValue, +]) { thisArg.dartValue = dartValue ?? - SassMap(Map.fromIterables( + SassMap( + Map.fromIterables( // Either [dartValue] or [length] must be passed. Iterable.generate(length!, (i) => SassNumber(i)), - Iterable.generate(length, (_) => sassNull))); + Iterable.generate(length, (_) => sassNull), + ), + ); }) ..defineMethods({ 'getKey': (_NodeSassMap thisArg, int index) => @@ -56,7 +62,9 @@ final JSClass legacyMapClass = createJSClass('sass.types.Map', }, 'setValue': (_NodeSassMap thisArg, int index, Object value) { var key = thisArg.dartValue.contents.keys.elementAt(index); - thisArg.dartValue = - SassMap({...thisArg.dartValue.contents, key: unwrapValue(value)}); - } + thisArg.dartValue = SassMap({ + ...thisArg.dartValue.contents, + key: unwrapValue(value), + }); + }, }); diff --git a/lib/src/js/legacy/value/number.dart b/lib/src/js/legacy/value/number.dart index ba179f163..9e2f16455 100644 --- a/lib/src/js/legacy/value/number.dart +++ b/lib/src/js/legacy/value/number.dart @@ -18,18 +18,23 @@ Object newNodeSassNumber(SassNumber value) => legacyNumberClass.construct([null, null, value]); /// The JS constructor for the `sass.types.Number` class. -final JSClass legacyNumberClass = createJSClass('sass.types.Number', - (_NodeSassNumber thisArg, num? value, - [String? unit, SassNumber? dartValue]) { +final JSClass legacyNumberClass = createJSClass('sass.types.Number', ( + _NodeSassNumber thisArg, + num? value, [ + String? unit, + SassNumber? dartValue, +]) { // Either [dartValue] or [value] must be passed. thisArg.dartValue = dartValue ?? _parseNumber(value!, unit); }) ..defineMethods({ 'getValue': (_NodeSassNumber thisArg) => thisArg.dartValue.value, 'setValue': (_NodeSassNumber thisArg, num value) { - thisArg.dartValue = SassNumber.withUnits(value, - numeratorUnits: thisArg.dartValue.numeratorUnits, - denominatorUnits: thisArg.dartValue.denominatorUnits); + thisArg.dartValue = SassNumber.withUnits( + value, + numeratorUnits: thisArg.dartValue.numeratorUnits, + denominatorUnits: thisArg.dartValue.denominatorUnits, + ); }, 'getUnit': (_NodeSassNumber thisArg) => thisArg.dartValue.numeratorUnits.join('*') + @@ -37,7 +42,7 @@ final JSClass legacyNumberClass = createJSClass('sass.types.Number', thisArg.dartValue.denominatorUnits.join('*'), 'setUnit': (_NodeSassNumber thisArg, String unit) { thisArg.dartValue = _parseNumber(thisArg.dartValue.value, unit); - } + }, }); /// Parses a [SassNumber] from [value] and [unit], using Node Sass's unit @@ -63,6 +68,9 @@ SassNumber _parseNumber(num value, String? unit) { denominator == null ? [] : denominator.split('*'); if (denominatorUnits.any((unit) => unit.isEmpty)) throw invalidUnit; - return SassNumber.withUnits(value, - numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits); + return SassNumber.withUnits( + value, + numeratorUnits: numeratorUnits, + denominatorUnits: denominatorUnits, + ); } diff --git a/lib/src/js/legacy/value/string.dart b/lib/src/js/legacy/value/string.dart index ba1d1a142..5d038185b 100644 --- a/lib/src/js/legacy/value/string.dart +++ b/lib/src/js/legacy/value/string.dart @@ -18,8 +18,11 @@ Object newNodeSassString(SassString value) => legacyStringClass.construct([null, value]); /// The JS constructor for the `sass.types.String` class. -final JSClass legacyStringClass = createJSClass('sass.types.String', - (_NodeSassString thisArg, String? value, [SassString? dartValue]) { +final JSClass legacyStringClass = createJSClass('sass.types.String', ( + _NodeSassString thisArg, + String? value, [ + SassString? dartValue, +]) { // Either [dartValue] or [value] must be passed. thisArg.dartValue = dartValue ?? SassString(value!, quotes: false); }) @@ -27,5 +30,5 @@ final JSClass legacyStringClass = createJSClass('sass.types.String', 'getValue': (_NodeSassString thisArg) => thisArg.dartValue.text, 'setValue': (_NodeSassString thisArg, String value) { thisArg.dartValue = SassString(value, quotes: false); - } + }, }); diff --git a/lib/src/js/logger.dart b/lib/src/js/logger.dart index 2e6fd6fc0..f72d2a7f4 100644 --- a/lib/src/js/logger.dart +++ b/lib/src/js/logger.dart @@ -13,9 +13,10 @@ class JSLogger { external void Function(String message, WarnOptions options)? get warn; external void Function(String message, DebugOptions options)? get debug; - external factory JSLogger( - {void Function(String message, WarnOptions options)? warn, - void Function(String message, DebugOptions options)? debug}); + external factory JSLogger({ + void Function(String message, WarnOptions options)? warn, + void Function(String message, DebugOptions options)? debug, + }); } @JS() @@ -26,11 +27,12 @@ class WarnOptions { external SourceSpan? get span; external String? get stack; - external factory WarnOptions( - {required bool deprecation, - Deprecation? deprecationType, - SourceSpan? span, - String? stack}); + external factory WarnOptions({ + required bool deprecation, + Deprecation? deprecationType, + SourceSpan? span, + String? stack, + }); } @JS() diff --git a/lib/src/js/parcel_watcher.dart b/lib/src/js/parcel_watcher.dart index 4d1720bdd..1c8eea7bd 100644 --- a/lib/src/js/parcel_watcher.dart +++ b/lib/src/js/parcel_watcher.dart @@ -22,21 +22,29 @@ extension type ParcelWatcherEvent(JSObject _) implements JSObject { extension type ParcelWatcher(JSObject _) implements JSObject { @JS('subscribe') external JSPromise _subscribe( - String path, JSFunction callback); - Future subscribe(String path, - void Function(Object? error, List) callback) => + String path, + JSFunction callback, + ); + Future subscribe( + String path, + void Function(Object? error, List) callback, + ) => _subscribe( - path, - (JSObject? error, JSArray events) { - callback(error, events.toDart); - }.toJS) - .toDart; + path, + (JSObject? error, JSArray events) { + callback(error, events.toDart); + }.toJS, + ).toDart; @JS('getEventsSince') external JSPromise> _getEventsSince( - String path, String snapshotPath); + String path, + String snapshotPath, + ); Future> getEventsSince( - String path, String snapshotPath) async => + String path, + String snapshotPath, + ) async => (await _getEventsSince(path, snapshotPath).toDart).toDart; @JS('writeSnapshot') diff --git a/lib/src/js/parser.dart b/lib/src/js/parser.dart index f0b5b9b40..616415b59 100644 --- a/lib/src/js/parser.dart +++ b/lib/src/js/parser.dart @@ -27,14 +27,15 @@ import 'visitor/statement.dart'; @JS() @anonymous class ParserExports { - external factory ParserExports( - {required Function parse, - required Function parseIdentifier, - required Function toCssIdentifier, - required Function createExpressionVisitor, - required Function createStatementVisitor, - required Function setToJS, - required Function mapToRecord}); + external factory ParserExports({ + required Function parse, + required Function parseIdentifier, + required Function toCssIdentifier, + required Function createExpressionVisitor, + required Function createStatementVisitor, + required Function setToJS, + required Function mapToRecord, + }); external set parse(Function function); external set parseIdentifier(Function function); @@ -57,15 +58,18 @@ final _expression = NullExpression(bogusSpan); ParserExports loadParserExports() { _updateAstPrototypes(); return ParserExports( - parse: allowInterop(_parse), - parseIdentifier: allowInterop(_parseIdentifier), - toCssIdentifier: allowInterop(_toCssIdentifier), - createExpressionVisitor: allowInterop( - (JSExpressionVisitorObject inner) => JSExpressionVisitor(inner)), - createStatementVisitor: allowInterop( - (JSStatementVisitorObject inner) => JSStatementVisitor(inner)), - setToJS: allowInterop((Set set) => JSSet([...set])), - mapToRecord: allowInterop(mapToObject)); + parse: allowInterop(_parse), + parseIdentifier: allowInterop(_parseIdentifier), + toCssIdentifier: allowInterop(_toCssIdentifier), + createExpressionVisitor: allowInterop( + (JSExpressionVisitorObject inner) => JSExpressionVisitor(inner), + ), + createStatementVisitor: allowInterop( + (JSStatementVisitorObject inner) => JSStatementVisitor(inner), + ), + setToJS: allowInterop((Set set) => JSSet([...set])), + mapToRecord: allowInterop(mapToObject), + ); } /// Modifies the prototypes of the Sass AST classes to provide access to JS. @@ -77,26 +81,34 @@ void _updateAstPrototypes() { // We don't need explicit getters for field names, because dart2js preserves // them as-is, so we actually need to expose very little to JS manually. var file = SourceFile.fromString(''); - getJSClass(file).defineMethod('getText', - (SourceFile self, int start, [int? end]) => self.getText(start, end)); - getJSClass(file) - .defineGetter('codeUnits', (SourceFile self) => self.codeUnits); - getJSClass(_interpolation) - .defineGetter('asPlain', (Interpolation self) => self.asPlain); + getJSClass(file).defineMethod( + 'getText', + (SourceFile self, int start, [int? end]) => self.getText(start, end), + ); + getJSClass( + file, + ).defineGetter('codeUnits', (SourceFile self) => self.codeUnits); + getJSClass( + _interpolation, + ).defineGetter('asPlain', (Interpolation self) => self.asPlain); getJSClass(ExtendRule(_interpolation, bogusSpan)).superclass.defineMethod( - 'accept', - (Statement self, StatementVisitor visitor) => - self.accept(visitor)); + 'accept', + (Statement self, StatementVisitor visitor) => + self.accept(visitor), + ); var string = StringExpression(_interpolation); getJSClass(string).superclass.defineMethod( - 'accept', - (Expression self, ExpressionVisitor visitor) => - self.accept(visitor)); + 'accept', + (Expression self, ExpressionVisitor visitor) => + self.accept(visitor), + ); var arguments = ArgumentList([], {}, bogusSpan); - getJSClass(IncludeRule('a', arguments, bogusSpan)) - .defineGetter('arguments', (IncludeRule self) => self.arguments); - getJSClass(ContentRule(arguments, bogusSpan)) - .defineGetter('arguments', (ContentRule self) => self.arguments); + getJSClass( + IncludeRule('a', arguments, bogusSpan), + ).defineGetter('arguments', (IncludeRule self) => self.arguments); + getJSClass( + ContentRule(arguments, bogusSpan), + ).defineGetter('arguments', (ContentRule self) => self.arguments); _addSupportsConditionToInterpolation(); @@ -104,7 +116,7 @@ void _updateAstPrototypes() { string, BinaryOperationExpression(BinaryOperator.plus, string, string), SupportsExpression(SupportsAnything(_interpolation, bogusSpan)), - LoudComment(_interpolation) + LoudComment(_interpolation), ]) { getJSClass(node).defineGetter('span', (SassNode self) => self.span); } @@ -122,10 +134,12 @@ void _addSupportsConditionToInterpolation() { SupportsFunction(_interpolation, _interpolation, bogusSpan), SupportsInterpolation(_expression, bogusSpan), SupportsNegation(anything, bogusSpan), - SupportsOperation(anything, anything, "and", bogusSpan) + SupportsOperation(anything, anything, "and", bogusSpan), ]) { getJSClass(node).defineMethod( - 'toInterpolation', (SupportsCondition self) => self.toInterpolation()); + 'toInterpolation', + (SupportsCondition self) => self.toInterpolation(), + ); } } @@ -136,7 +150,7 @@ Stylesheet _parse(String css, String syntax, String? path) => Stylesheet.parse( 'scss' => Syntax.scss, 'sass' => Syntax.sass, 'css' => Syntax.css, - _ => throw UnsupportedError('Unknown syntax "$syntax"') + _ => throw UnsupportedError('Unknown syntax "$syntax"'), }, url: path.andThen(p.toUri)); diff --git a/lib/src/js/reflection.dart b/lib/src/js/reflection.dart index 5c4efa33e..319fa1637 100644 --- a/lib/src/js/reflection.dart +++ b/lib/src/js/reflection.dart @@ -72,8 +72,11 @@ extension JSClassExtension on JSClass { /// Sets the custom inspect logic for this class to [body]. void setCustomInspect(String inspect(Object self)) { if (_util == null) return; - setProperty(prototype, _inspectSymbol, - allowInteropCaptureThis((Object self, _, __, [___]) => inspect(self))); + setProperty( + prototype, + _inspectSymbol, + allowInteropCaptureThis((Object self, _, __, [___]) => inspect(self)), + ); } /// Defines a static method with the given [name] and [body]. diff --git a/lib/src/js/source_span.dart b/lib/src/js/source_span.dart index ddf8ee776..ccc136a86 100644 --- a/lib/src/js/source_span.dart +++ b/lib/src/js/source_span.dart @@ -22,8 +22,11 @@ void updateSourceSpanPrototype() { getJSClass(item).defineGetters({ 'start': (FileSpan span) => span.start, 'end': (FileSpan span) => span.end, - 'url': (FileSpan span) => span.sourceUrl.andThen((url) => dartToJSUrl( - url.scheme == '' ? p.toUri(p.absolute(p.fromUri(url))) : url)), + 'url': (FileSpan span) => span.sourceUrl.andThen( + (url) => dartToJSUrl( + url.scheme == '' ? p.toUri(p.absolute(p.fromUri(url))) : url, + ), + ), 'text': (FileSpan span) => span.text, 'context': (FileSpan span) => span.context, }); diff --git a/lib/src/js/utils.dart b/lib/src/js/utils.dart index abaf6e8ef..194ee4eab 100644 --- a/lib/src/js/utils.dart +++ b/lib/src/js/utils.dart @@ -59,8 +59,13 @@ Object? call2(JSFunction function, Object thisArg, Object arg1, Object arg2) => function.apply(thisArg, [arg1, arg2]); /// Invokes [function] with [thisArg] as `this`. -Object? call3(JSFunction function, Object thisArg, Object arg1, Object arg2, - Object arg3) => +Object? call3( + JSFunction function, + Object thisArg, + Object arg1, + Object arg2, + Object arg3, +) => function.apply(thisArg, [arg1, arg2, arg3]); @JS("Object.keys") @@ -100,7 +105,10 @@ String jsType(Object? value) { @JS("Object.defineProperty") external void _defineProperty( - Object object, String name, _PropertyDescriptor prototype); + Object object, + String name, + _PropertyDescriptor prototype, +); @JS() @anonymous @@ -109,8 +117,11 @@ class _PropertyDescriptor { external Function get get; external bool get enumerable; - external factory _PropertyDescriptor( - {Object? value, Function? get, bool? enumerable}); + external factory _PropertyDescriptor({ + Object? value, + Function? get, + bool? enumerable, + }); } /// Defines a JS getter on [object] named [name]. @@ -119,12 +130,15 @@ class _PropertyDescriptor { /// the getter just returns [value]. void defineGetter(Object object, String name, {Object? value, Function? get}) { _defineProperty( - object, - name, - get == null - ? _PropertyDescriptor(value: value, enumerable: false) - : _PropertyDescriptor( - get: allowInteropCaptureThis(get), enumerable: false)); + object, + name, + get == null + ? _PropertyDescriptor(value: value, enumerable: false) + : _PropertyDescriptor( + get: allowInteropCaptureThis(get), + enumerable: false, + ), + ); } /// Like [allowInterop], but gives the function a [name] so it's more ergonomic @@ -173,14 +187,18 @@ bool isPromise(Object? object) => /// Like [futureToPromise] from `node_interop`, but stores the stack trace for /// errors using [throwWithTrace]. -Promise futureToPromise(Future future) => Promise(allowInterop( - (void Function(Object?) resolve, void Function(Object?) reject) { - future.then((result) => resolve(result), +Promise futureToPromise(Future future) => Promise( + allowInterop( + (void Function(Object?) resolve, void Function(Object?) reject) { + future.then( + (result) => resolve(result), onError: (Object error, StackTrace stackTrace) { - attachTrace(error, stackTrace); - reject(error); - }); - })); + attachTrace(error, stackTrace); + reject(error); + }, + ); + }), + ); @JS('URL') external JSClass get _urlClass; @@ -242,7 +260,7 @@ ListSeparator jsToDartSeparator(String? separator) => switch (separator) { ',' => ListSeparator.comma, '/' => ListSeparator.slash, null => ListSeparator.undecided, - _ => jsThrow(JsError('Unknown separator "$separator".')) + _ => jsThrow(JsError('Unknown separator "$separator".')), }; /// Converts a syntax string to an instance of [Syntax]. @@ -250,7 +268,7 @@ Syntax parseSyntax(String? syntax) => switch (syntax) { null || 'scss' => Syntax.scss, 'indented' => Syntax.sass, 'css' => Syntax.css, - _ => jsThrow(JsError('Unknown syntax "$syntax".')) + _ => jsThrow(JsError('Unknown syntax "$syntax".')), }; /// The path to the Node.js entrypoint, if one can be located. diff --git a/lib/src/js/value/argument_list.dart b/lib/src/js/value/argument_list.dart index c9ef8bebe..b975c6c7b 100644 --- a/lib/src/js/value/argument_list.dart +++ b/lib/src/js/value/argument_list.dart @@ -9,22 +9,29 @@ import '../utils.dart'; /// The JavaScript `SassArgumentList` class. final JSClass argumentListClass = () { - var jsClass = createJSClass('sass.SassArgumentList', - (Object self, Object contents, Object keywords, - [String? separator = ',']) { + var jsClass = createJSClass('sass.SassArgumentList', ( + Object self, + Object contents, + Object keywords, [ + String? separator = ',', + ]) { return SassArgumentList( - jsToDartList(contents).cast(), - (isImmutableMap(keywords) - ? immutableMapToDartMap(keywords as ImmutableMap) - : objectToMap(keywords)) - .cast(), - jsToDartSeparator(separator)); + jsToDartList(contents).cast(), + (isImmutableMap(keywords) + ? immutableMapToDartMap(keywords as ImmutableMap) + : objectToMap(keywords)) + .cast(), + jsToDartSeparator(separator), + ); }); - jsClass.defineGetter('keywords', - (SassArgumentList self) => dartMapToImmutableMap(self.keywords)); + jsClass.defineGetter( + 'keywords', + (SassArgumentList self) => dartMapToImmutableMap(self.keywords), + ); - getJSClass(SassArgumentList([], {}, ListSeparator.undecided)) - .injectSuperclass(jsClass); + getJSClass( + SassArgumentList([], {}, ListSeparator.undecided), + ).injectSuperclass(jsClass); return jsClass; }(); diff --git a/lib/src/js/value/boolean.dart b/lib/src/js/value/boolean.dart index 38d19a47e..349886dc0 100644 --- a/lib/src/js/value/boolean.dart +++ b/lib/src/js/value/boolean.dart @@ -11,8 +11,12 @@ import '../utils.dart'; /// The JavaScript `SassBoolean` class. final JSClass booleanClass = () { var jsClass = createJSClass('sass.SassBoolean', (Object self, [Object? _]) { - jsThrow(JsError("new sass.SassBoolean() isn't allowed.\n" - "Use sass.sassTrue or sass.sassFalse instead.")); + jsThrow( + JsError( + "new sass.SassBoolean() isn't allowed.\n" + "Use sass.sassTrue or sass.sassFalse instead.", + ), + ); }); getJSClass(sassTrue).injectSuperclass(jsClass); diff --git a/lib/src/js/value/calculation.dart b/lib/src/js/value/calculation.dart index 96e13c5b0..9ac1428b3 100644 --- a/lib/src/js/value/calculation.dart +++ b/lib/src/js/value/calculation.dart @@ -18,9 +18,12 @@ void _assertCalculationValue(Object arg) => switch (arg) { CalculationOperation() || CalculationInterpolation() => null, - _ => jsThrow(JsError( - 'Argument `$arg` must be one of SassNumber, unquoted SassString, ' - 'SassCalculation, CalculationOperation, CalculationInterpolation')), + _ => jsThrow( + JsError( + 'Argument `$arg` must be one of SassNumber, unquoted SassString, ' + 'SassCalculation, CalculationOperation, CalculationInterpolation', + ), + ), }; /// Check that [arg] is an unquoted string or interpolation. @@ -31,8 +34,10 @@ bool _isValidClampArg(Object? arg) => switch (arg) { /// The JavaScript `SassCalculation` class. final JSClass calculationClass = () { - var jsClass = - createJSClass('sass.SassCalculation', (Object self, [Object? _]) { + var jsClass = createJSClass('sass.SassCalculation', ( + Object self, [ + Object? _, + ]) { jsThrow(JsError("new sass.SassCalculation() isn't allowed")); }); @@ -54,12 +59,16 @@ final JSClass calculationClass = () { 'clamp': (Object min, [Object? value, Object? max]) { if ((value == null && !_isValidClampArg(min)) || (max == null && ![min, value].any(_isValidClampArg))) { - jsThrow(JsError('Expected at least one SassString or ' - 'CalculationInterpolation in `${[min, value, max].nonNulls}`')); + jsThrow( + JsError( + 'Expected at least one SassString or ' + 'CalculationInterpolation in `${[min, value, max].nonNulls}`', + ), + ); } [min, value, max].nonNulls.forEach(_assertCalculationValue); return SassCalculation.unsimplified('clamp', [min, value, max].nonNulls); - } + }, }); jsClass.defineMethods({ @@ -71,24 +80,36 @@ final JSClass calculationClass = () { 'arguments': (SassCalculation self) => ImmutableList(self.arguments), }); - getJSClass(SassCalculation.unsimplified('calc', [SassNumber(1)])) - .injectSuperclass(jsClass); + getJSClass( + SassCalculation.unsimplified('calc', [SassNumber(1)]), + ).injectSuperclass(jsClass); return jsClass; }(); /// The JavaScript `CalculationOperation` class. final JSClass calculationOperationClass = () { - var jsClass = createJSClass('sass.CalculationOperation', - (Object self, String strOperator, Object left, Object right) { - var operator = CalculationOperator.values - .firstWhereOrNull((value) => value.operator == strOperator); + var jsClass = createJSClass('sass.CalculationOperation', ( + Object self, + String strOperator, + Object left, + Object right, + ) { + var operator = CalculationOperator.values.firstWhereOrNull( + (value) => value.operator == strOperator, + ); if (operator == null) { jsThrow(JsError('Invalid operator: $strOperator')); } _assertCalculationValue(left); _assertCalculationValue(right); - return SassCalculation.operateInternal(operator, left, right, - inLegacySassFunction: null, simplify: false, warn: null); + return SassCalculation.operateInternal( + operator, + left, + right, + inLegacySassFunction: null, + simplify: false, + warn: null, + ); }); jsClass.defineMethods({ @@ -102,17 +123,25 @@ final JSClass calculationOperationClass = () { 'right': (CalculationOperation self) => self.right, }); - getJSClass(SassCalculation.operateInternal( - CalculationOperator.plus, SassNumber(1), SassNumber(1), - inLegacySassFunction: null, simplify: false, warn: null)) - .injectSuperclass(jsClass); + getJSClass( + SassCalculation.operateInternal( + CalculationOperator.plus, + SassNumber(1), + SassNumber(1), + inLegacySassFunction: null, + simplify: false, + warn: null, + ), + ).injectSuperclass(jsClass); return jsClass; }(); /// The JavaScript `CalculationInterpolation` class. final JSClass calculationInterpolationClass = () { - var jsClass = createJSClass('sass.CalculationInterpolation', - (Object self, String value) => CalculationInterpolation(value)); + var jsClass = createJSClass( + 'sass.CalculationInterpolation', + (Object self, String value) => CalculationInterpolation(value), + ); jsClass.defineMethods({ 'equals': (CalculationInterpolation self, Object other) => self == other, diff --git a/lib/src/js/value/color.dart b/lib/src/js/value/color.dart index 0594b0399..e464b7ae1 100644 --- a/lib/src/js/value/color.dart +++ b/lib/src/js/value/color.dart @@ -16,65 +16,127 @@ import '../utils.dart'; /// The JavaScript `SassColor` class. final JSClass colorClass = () { - var jsClass = createJSClass('sass.SassColor', - (Object self, _ConstructionOptions options) { + var jsClass = createJSClass('sass.SassColor', ( + Object self, + _ConstructionOptions options, + ) { var constructionSpace = _constructionSpace(options); switch (constructionSpace) { case ColorSpace.rgb: _checkNullAlphaDeprecation(options); - return SassColor.rgb(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.rgb( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.hsl: _checkNullAlphaDeprecation(options); - return SassColor.hsl(options.hue, options.saturation, options.lightness, - _handleUndefinedAlpha(options.alpha)); + return SassColor.hsl( + options.hue, + options.saturation, + options.lightness, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.hwb: _checkNullAlphaDeprecation(options); - return SassColor.hwb(options.hue, options.whiteness, options.blackness, - _handleUndefinedAlpha(options.alpha)); + return SassColor.hwb( + options.hue, + options.whiteness, + options.blackness, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.lab: - return SassColor.lab(options.lightness, options.a, options.b, - _handleUndefinedAlpha(options.alpha)); + return SassColor.lab( + options.lightness, + options.a, + options.b, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.oklab: - return SassColor.oklab(options.lightness, options.a, options.b, - _handleUndefinedAlpha(options.alpha)); + return SassColor.oklab( + options.lightness, + options.a, + options.b, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.lch: - return SassColor.lch(options.lightness, options.chroma, options.hue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.lch( + options.lightness, + options.chroma, + options.hue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.oklch: - return SassColor.oklch(options.lightness, options.chroma, options.hue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.oklch( + options.lightness, + options.chroma, + options.hue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.srgb: - return SassColor.srgb(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.srgb( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.srgbLinear: - return SassColor.srgbLinear(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.srgbLinear( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.displayP3: - return SassColor.displayP3(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.displayP3( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.a98Rgb: - return SassColor.a98Rgb(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.a98Rgb( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.prophotoRgb: - return SassColor.prophotoRgb(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.prophotoRgb( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.rec2020: - return SassColor.rec2020(options.red, options.green, options.blue, - _handleUndefinedAlpha(options.alpha)); + return SassColor.rec2020( + options.red, + options.green, + options.blue, + _handleUndefinedAlpha(options.alpha), + ); // `xyz` name is mapped to `xyzD65` space. case ColorSpace.xyzD50: - return SassColor.xyzD50(options.x, options.y, options.z, - _handleUndefinedAlpha(options.alpha)); + return SassColor.xyzD50( + options.x, + options.y, + options.z, + _handleUndefinedAlpha(options.alpha), + ); case ColorSpace.xyzD65: - return SassColor.xyzD65(options.x, options.y, options.z, - _handleUndefinedAlpha(options.alpha)); + return SassColor.xyzD65( + options.x, + options.y, + options.z, + _handleUndefinedAlpha(options.alpha), + ); default: throw "Unreachable"; @@ -89,9 +151,10 @@ final JSClass colorClass = () { _toSpace(self, space).isInGamut, 'toGamut': (SassColor self, _ToGamutOptions options) { var originalSpace = self.space; - return _toSpace(self, options.space) - .toGamut(GamutMapMethod.fromName(options.method)) - .toSpace(originalSpace); + return _toSpace( + self, + options.space, + ).toGamut(GamutMapMethod.fromName(options.method)).toSpace(originalSpace); }, 'channel': (SassColor self, String channel, [_ChannelOptions? options]) => _toSpace(self, options?.space).channel(channel), @@ -123,11 +186,12 @@ final JSClass colorClass = () { } if (space != self.space) { warnForDeprecationFromApi( - "Changing a channel not in this color's space without explicitly specifying " - "the `space` option is deprecated." - "\n" - "More info: https://sass-lang.com/d/color-4-api", - Deprecation.color4Api); + "Changing a channel not in this color's space without explicitly specifying " + "the `space` option is deprecated." + "\n" + "More info: https://sass-lang.com/d/color-4-api", + Deprecation.color4Api, + ); } } @@ -149,10 +213,11 @@ final JSClass colorClass = () { switch (space) { case ColorSpace.hsl when spaceSetExplicitly: changedColor = SassColor.hsl( - changedValue('hue'), - changedValue('saturation'), - changedValue('lightness'), - changedValue('alpha')); + changedValue('hue'), + changedValue('saturation'), + changedValue('lightness'), + changedValue('alpha'), + ); break; case ColorSpace.hsl: @@ -167,18 +232,20 @@ final JSClass colorClass = () { _emitNullAlphaDeprecation(); } changedColor = SassColor.hsl( - options.hue ?? color.channel('hue'), - options.saturation ?? color.channel('saturation'), - options.lightness ?? color.channel('lightness'), - options.alpha ?? color.channel('alpha')); + options.hue ?? color.channel('hue'), + options.saturation ?? color.channel('saturation'), + options.lightness ?? color.channel('lightness'), + options.alpha ?? color.channel('alpha'), + ); break; case ColorSpace.hwb when spaceSetExplicitly: changedColor = SassColor.hwb( - changedValue('hue'), - changedValue('whiteness'), - changedValue('blackness'), - changedValue('alpha')); + changedValue('hue'), + changedValue('whiteness'), + changedValue('blackness'), + changedValue('alpha'), + ); break; case ColorSpace.hwb: @@ -191,19 +258,21 @@ final JSClass colorClass = () { } if (isNull(options.alpha)) _emitNullAlphaDeprecation(); changedColor = SassColor.hwb( - options.hue ?? color.channel('hue'), - options.whiteness ?? color.channel('whiteness'), - options.blackness ?? color.channel('blackness'), - options.alpha ?? color.channel('alpha')); + options.hue ?? color.channel('hue'), + options.whiteness ?? color.channel('whiteness'), + options.blackness ?? color.channel('blackness'), + options.alpha ?? color.channel('alpha'), + ); break; case ColorSpace.rgb when spaceSetExplicitly: changedColor = SassColor.rgb( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.rgb: @@ -218,87 +287,114 @@ final JSClass colorClass = () { _emitNullAlphaDeprecation(); } changedColor = SassColor.rgb( - options.red ?? color.channel('red'), - options.green ?? color.channel('green'), - options.blue ?? color.channel('blue'), - options.alpha ?? color.channel('alpha')); + options.red ?? color.channel('red'), + options.green ?? color.channel('green'), + options.blue ?? color.channel('blue'), + options.alpha ?? color.channel('alpha'), + ); break; case ColorSpace.lab: - changedColor = SassColor.lab(changedValue('lightness'), - changedValue('a'), changedValue('b'), changedValue('alpha')); + changedColor = SassColor.lab( + changedValue('lightness'), + changedValue('a'), + changedValue('b'), + changedValue('alpha'), + ); break; case ColorSpace.oklab: - changedColor = SassColor.oklab(changedValue('lightness'), - changedValue('a'), changedValue('b'), changedValue('alpha')); + changedColor = SassColor.oklab( + changedValue('lightness'), + changedValue('a'), + changedValue('b'), + changedValue('alpha'), + ); break; case ColorSpace.lch: changedColor = SassColor.lch( - changedValue('lightness'), - changedValue('chroma'), - changedValue('hue'), - changedValue('alpha')); + changedValue('lightness'), + changedValue('chroma'), + changedValue('hue'), + changedValue('alpha'), + ); break; case ColorSpace.oklch: changedColor = SassColor.oklch( - changedValue('lightness'), - changedValue('chroma'), - changedValue('hue'), - changedValue('alpha')); + changedValue('lightness'), + changedValue('chroma'), + changedValue('hue'), + changedValue('alpha'), + ); break; case ColorSpace.a98Rgb: changedColor = SassColor.a98Rgb( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.displayP3: changedColor = SassColor.displayP3( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.prophotoRgb: changedColor = SassColor.prophotoRgb( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.rec2020: changedColor = SassColor.rec2020( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.srgb: changedColor = SassColor.srgb( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.srgbLinear: changedColor = SassColor.srgbLinear( - changedValue('red'), - changedValue('green'), - changedValue('blue'), - changedValue('alpha')); + changedValue('red'), + changedValue('green'), + changedValue('blue'), + changedValue('alpha'), + ); break; case ColorSpace.xyzD50: - changedColor = SassColor.forSpaceInternal(space, changedValue('x'), - changedValue('y'), changedValue('z'), changedValue('alpha')); + changedColor = SassColor.forSpaceInternal( + space, + changedValue('x'), + changedValue('y'), + changedValue('z'), + changedValue('alpha'), + ); break; case ColorSpace.xyzD65: - changedColor = SassColor.forSpaceInternal(space, changedValue('x'), - changedValue('y'), changedValue('z'), changedValue('alpha')); + changedColor = SassColor.forSpaceInternal( + space, + changedValue('x'), + changedValue('y'), + changedValue('z'), + changedValue('alpha'), + ); break; default: @@ -307,8 +403,11 @@ final JSClass colorClass = () { return changedColor.toSpace(self.space); }, - 'interpolate': (SassColor self, SassColor color2, - [_InterpolationOptions? options]) { + 'interpolate': ( + SassColor self, + SassColor color2, [ + _InterpolationOptions? options, + ]) { InterpolationMethod interpolationMethod; if (options?.method case var method?) { @@ -317,13 +416,18 @@ final JSClass colorClass = () { } else if (!self.space.isPolar) { interpolationMethod = InterpolationMethod(self.space); } else { - interpolationMethod = - InterpolationMethod(self.space, HueInterpolationMethod.shorter); + interpolationMethod = InterpolationMethod( + self.space, + HueInterpolationMethod.shorter, + ); } - return self.interpolate(color2, interpolationMethod, - weight: options?.weight); - } + return self.interpolate( + color2, + interpolationMethod, + weight: options?.weight, + ); + }, }); jsClass.defineGetters({ @@ -363,7 +467,7 @@ final JSClass colorClass = () { 'space': (SassColor self) => self.space.name, 'isLegacy': (SassColor self) => self.isLegacy, 'channelsOrNull': (SassColor self) => ImmutableList(self.channelsOrNull), - 'channels': (SassColor self) => ImmutableList(self.channels) + 'channels': (SassColor self) => ImmutableList(self.channels), }); getJSClass(SassColor.rgb(0, 0, 0)).injectSuperclass(jsClass); @@ -380,7 +484,10 @@ double? _handleUndefinedAlpha(double? alpha) => isUndefined(alpha) ? 1 : alpha; /// `initial` and returns the result of applying the change for `channel` to /// `initial`. double? _changeComponentValue( - SassColor initial, String channel, _ConstructionOptions changes) => + SassColor initial, + String channel, + _ConstructionOptions changes, +) => hasProperty(changes, channel) && !isUndefined(getProperty(changes, channel)) ? getProperty(changes, channel) : initial.channel(channel); @@ -411,28 +518,31 @@ void _checkNullAlphaDeprecation(_ConstructionOptions options) { // Warn users about null-alpha deprecation. void _emitNullAlphaDeprecation() { warnForDeprecationFromApi( - "Passing `alpha: null` without setting `space` is deprecated." - "\n" - "More info: https://sass-lang.com/d/null-alpha", - Deprecation.nullAlpha); + "Passing `alpha: null` without setting `space` is deprecated." + "\n" + "More info: https://sass-lang.com/d/null-alpha", + Deprecation.nullAlpha, + ); } // Warn users about `null` channel values without setting `space`. void _emitColor4ApiNullDeprecation(String name) { warnForDeprecationFromApi( - "Passing `$name: null` without setting `space` is deprecated." - "\n" - "More info: https://sass-lang.com/d/color-4-api", - Deprecation.color4Api); + "Passing `$name: null` without setting `space` is deprecated." + "\n" + "More info: https://sass-lang.com/d/color-4-api", + Deprecation.color4Api, + ); } // Warn users about legacy color channel getters. void _emitColor4ApiChannelDeprecation(String name) { warnForDeprecationFromApi( - "$name is deprecated, use `channel` instead." - "\n" - "More info: https://sass-lang.com/d/color-4-api", - Deprecation.color4Api); + "$name is deprecated, use `channel` instead." + "\n" + "More info: https://sass-lang.com/d/color-4-api", + Deprecation.color4Api, + ); } @JS() diff --git a/lib/src/js/value/function.dart b/lib/src/js/value/function.dart index 6dfc763ba..27cfcc8cc 100644 --- a/lib/src/js/value/function.dart +++ b/lib/src/js/value/function.dart @@ -11,19 +11,29 @@ import '../utils.dart'; /// The JavaScript `SassFunction` class. final JSClass functionClass = () { - var jsClass = createJSClass('sass.SassFunction', - (Object self, String signature, Value Function(List) callback) { + var jsClass = createJSClass('sass.SassFunction', ( + Object self, + String signature, + Value Function(List) callback, + ) { var paren = signature.indexOf('('); if (paren == -1 || !signature.endsWith(')')) { - jsThrow(JsError( - 'Invalid signature for new sass.SassFunction(): "$signature"')); + jsThrow( + JsError('Invalid signature for new sass.SassFunction(): "$signature"'), + ); } - return SassFunction(Callable(signature.substring(0, paren), - signature.substring(paren + 1, signature.length - 1), callback)); + return SassFunction( + Callable( + signature.substring(0, paren), + signature.substring(paren + 1, signature.length - 1), + callback, + ), + ); }); - getJSClass(SassFunction(Callable('f', '', (_) => sassNull))) - .injectSuperclass(jsClass); + getJSClass( + SassFunction(Callable('f', '', (_) => sassNull)), + ).injectSuperclass(jsClass); return jsClass; }(); diff --git a/lib/src/js/value/list.dart b/lib/src/js/value/list.dart index 60a6e5e55..7700a0fbd 100644 --- a/lib/src/js/value/list.dart +++ b/lib/src/js/value/list.dart @@ -12,8 +12,11 @@ import '../utils.dart'; /// The JavaScript `SassList` class. final JSClass listClass = () { - var jsClass = createJSClass('sass.SassList', (Object self, - [Object? contentsOrOptions, _ConstructorOptions? options]) { + var jsClass = createJSClass('sass.SassList', ( + Object self, [ + Object? contentsOrOptions, + _ConstructorOptions? options, + ]) { List contents; if (isImmutableList(contentsOrOptions)) { contents = (contentsOrOptions as ImmutableList).toArray().cast(); @@ -25,11 +28,12 @@ final JSClass listClass = () { } return SassList( - contents, - options == null || isUndefined(options.separator) - ? ListSeparator.comma - : jsToDartSeparator(options.separator), - brackets: options?.brackets ?? false); + contents, + options == null || isUndefined(options.separator) + ? ListSeparator.comma + : jsToDartSeparator(options.separator), + brackets: options?.brackets ?? false, + ); }); jsClass.defineMethod('get', (Value self, num indexFloat) { diff --git a/lib/src/js/value/map.dart b/lib/src/js/value/map.dart index 3e13373d6..785760c83 100644 --- a/lib/src/js/value/map.dart +++ b/lib/src/js/value/map.dart @@ -12,13 +12,16 @@ import '../reflection.dart'; /// The JavaScript `SassMap` class. final JSClass mapClass = () { var jsClass = createJSClass( - 'sass.SassMap', - (Object self, [ImmutableMap? contents]) => contents == null - ? const SassMap.empty() - : SassMap(immutableMapToDartMap(contents).cast())); + 'sass.SassMap', + (Object self, [ImmutableMap? contents]) => contents == null + ? const SassMap.empty() + : SassMap(immutableMapToDartMap(contents).cast()), + ); jsClass.defineGetter( - 'contents', (SassMap self) => dartMapToImmutableMap(self.contents)); + 'contents', + (SassMap self) => dartMapToImmutableMap(self.contents), + ); jsClass.defineMethod('get', (SassMap self, Object indexOrKey) { if (indexOrKey is num) { diff --git a/lib/src/js/value/mixin.dart b/lib/src/js/value/mixin.dart index cc55f3eb4..9c0ff4ee2 100644 --- a/lib/src/js/value/mixin.dart +++ b/lib/src/js/value/mixin.dart @@ -12,12 +12,16 @@ import '../utils.dart'; /// The JavaScript `SassMixin` class. final JSClass mixinClass = () { var jsClass = createJSClass('sass.SassMixin', (Object self) { - jsThrow(JsError( + jsThrow( + JsError( 'It is not possible to construct a SassMixin through the JavaScript ' - 'API')); + 'API', + ), + ); }); - getJSClass(SassMixin(Callable('f', '', (_) => sassNull))) - .injectSuperclass(jsClass); + getJSClass( + SassMixin(Callable('f', '', (_) => sassNull)), + ).injectSuperclass(jsClass); return jsClass; }(); diff --git a/lib/src/js/value/number.dart b/lib/src/js/value/number.dart index fcfcd0abe..429bd5672 100644 --- a/lib/src/js/value/number.dart +++ b/lib/src/js/value/number.dart @@ -11,16 +11,21 @@ import '../reflection.dart'; /// The JavaScript `SassNumber` class. final JSClass numberClass = () { - var jsClass = createJSClass('sass.SassNumber', (Object self, num value, - [Object? unitOrOptions]) { + var jsClass = createJSClass('sass.SassNumber', ( + Object self, + num value, [ + Object? unitOrOptions, + ]) { if (unitOrOptions is String) return SassNumber(value, unitOrOptions); var options = unitOrOptions as _ConstructorOptions?; - return SassNumber.withUnits(value, - numeratorUnits: - options?.numeratorUnits.andThen(jsToDartList)?.cast(), - denominatorUnits: - options?.denominatorUnits.andThen(jsToDartList)?.cast()); + return SassNumber.withUnits( + value, + numeratorUnits: + options?.numeratorUnits.andThen(jsToDartList)?.cast(), + denominatorUnits: + options?.denominatorUnits.andThen(jsToDartList)?.cast(), + ); }); jsClass.defineGetters({ @@ -44,33 +49,77 @@ final JSClass numberClass = () { 'hasUnit': (SassNumber self, String unit) => self.hasUnit(unit), 'compatibleWithUnit': (SassNumber self, String unit) => self.hasUnits && self.compatibleWithUnit(unit), - 'convert': (SassNumber self, Object numeratorUnits, Object denominatorUnits, - [String? name]) => - self.convert(jsToDartList(numeratorUnits).cast(), - jsToDartList(denominatorUnits).cast(), name), - 'convertToMatch': (SassNumber self, SassNumber other, - [String? name, String? otherName]) => + 'convert': ( + SassNumber self, + Object numeratorUnits, + Object denominatorUnits, [ + String? name, + ]) => + self.convert( + jsToDartList(numeratorUnits).cast(), + jsToDartList(denominatorUnits).cast(), + name, + ), + 'convertToMatch': ( + SassNumber self, + SassNumber other, [ + String? name, + String? otherName, + ]) => self.convertToMatch(other, name, otherName), - 'convertValue': (SassNumber self, Object numeratorUnits, - Object denominatorUnits, [String? name]) => - self.convertValue(jsToDartList(numeratorUnits).cast(), - jsToDartList(denominatorUnits).cast(), name), - 'convertValueToMatch': (SassNumber self, SassNumber other, - [String? name, String? otherName]) => + 'convertValue': ( + SassNumber self, + Object numeratorUnits, + Object denominatorUnits, [ + String? name, + ]) => + self.convertValue( + jsToDartList(numeratorUnits).cast(), + jsToDartList(denominatorUnits).cast(), + name, + ), + 'convertValueToMatch': ( + SassNumber self, + SassNumber other, [ + String? name, + String? otherName, + ]) => self.convertValueToMatch(other, name, otherName), - 'coerce': (SassNumber self, Object numeratorUnits, Object denominatorUnits, - [String? name]) => - self.coerce(jsToDartList(numeratorUnits).cast(), - jsToDartList(denominatorUnits).cast(), name), - 'coerceToMatch': (SassNumber self, SassNumber other, - [String? name, String? otherName]) => + 'coerce': ( + SassNumber self, + Object numeratorUnits, + Object denominatorUnits, [ + String? name, + ]) => + self.coerce( + jsToDartList(numeratorUnits).cast(), + jsToDartList(denominatorUnits).cast(), + name, + ), + 'coerceToMatch': ( + SassNumber self, + SassNumber other, [ + String? name, + String? otherName, + ]) => self.coerceToMatch(other, name, otherName), - 'coerceValue': (SassNumber self, Object numeratorUnits, - Object denominatorUnits, [String? name]) => - self.coerceValue(jsToDartList(numeratorUnits).cast(), - jsToDartList(denominatorUnits).cast(), name), - 'coerceValueToMatch': (SassNumber self, SassNumber other, - [String? name, String? otherName]) => + 'coerceValue': ( + SassNumber self, + Object numeratorUnits, + Object denominatorUnits, [ + String? name, + ]) => + self.coerceValue( + jsToDartList(numeratorUnits).cast(), + jsToDartList(denominatorUnits).cast(), + name, + ), + 'coerceValueToMatch': ( + SassNumber self, + SassNumber other, [ + String? name, + String? otherName, + ]) => self.coerceValueToMatch(other, name, otherName), }); diff --git a/lib/src/js/value/string.dart b/lib/src/js/value/string.dart index 3d4960e78..44a7b89ee 100644 --- a/lib/src/js/value/string.dart +++ b/lib/src/js/value/string.dart @@ -10,13 +10,14 @@ import '../reflection.dart'; /// The JavaScript `SassString` class. final JSClass stringClass = () { var jsClass = createJSClass( - 'sass.SassString', - (Object self, [Object? textOrOptions, _ConstructorOptions? options]) => - textOrOptions is String - ? SassString(textOrOptions, quotes: options?.quotes ?? true) - : SassString.empty( - quotes: - (textOrOptions as _ConstructorOptions?)?.quotes ?? true)); + 'sass.SassString', + (Object self, [Object? textOrOptions, _ConstructorOptions? options]) => + textOrOptions is String + ? SassString(textOrOptions, quotes: options?.quotes ?? true) + : SassString.empty( + quotes: (textOrOptions as _ConstructorOptions?)?.quotes ?? true, + ), + ); jsClass.defineGetters({ 'text': (SassString self) => self.text, @@ -25,9 +26,10 @@ final JSClass stringClass = () { }); jsClass.defineMethod( - 'sassIndexToStringIndex', - (SassString self, Value sassIndex, [String? name]) => - self.sassIndexToStringIndex(sassIndex, name)); + 'sassIndexToStringIndex', + (SassString self, Value sassIndex, [String? name]) => + self.sassIndexToStringIndex(sassIndex, name), + ); getJSClass(SassString.empty()).injectSuperclass(jsClass); return jsClass; diff --git a/lib/src/js/visitor/expression.dart b/lib/src/js/visitor/expression.dart index 88fa684de..f408afc64 100644 --- a/lib/src/js/visitor/expression.dart +++ b/lib/src/js/visitor/expression.dart @@ -20,7 +20,8 @@ class JSExpressionVisitor implements ExpressionVisitor { Object? visitColorExpression(ColorExpression node) => _inner.visitColorExpression(node); Object? visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) => + InterpolatedFunctionExpression node, + ) => _inner.visitInterpolatedFunctionExpression(node); Object? visitFunctionExpression(FunctionExpression node) => _inner.visitFunctionExpression(node); @@ -53,11 +54,13 @@ class JSExpressionVisitor implements ExpressionVisitor { @JS() class JSExpressionVisitorObject { external Object? visitBinaryOperationExpression( - BinaryOperationExpression node); + BinaryOperationExpression node, + ); external Object? visitBooleanExpression(BooleanExpression node); external Object? visitColorExpression(ColorExpression node); external Object? visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node); + InterpolatedFunctionExpression node, + ); external Object? visitFunctionExpression(FunctionExpression node); external Object? visitIfExpression(IfExpression node); external Object? visitListExpression(ListExpression node); diff --git a/lib/src/logger.dart b/lib/src/logger.dart index da85627b4..a25b24eb3 100644 --- a/lib/src/logger.dart +++ b/lib/src/logger.dart @@ -30,8 +30,12 @@ abstract class Logger { /// warning was issued. If [deprecation] is `true`, it indicates that this is /// a deprecation warning. Implementations should surface all this information /// to the end user. - void warn(String message, - {FileSpan? span, Trace? trace, bool deprecation = false}); + void warn( + String message, { + FileSpan? span, + Trace? trace, + bool deprecation = false, + }); /// Emits a debugging message associated with the given [span]. void debug(String message, SourceSpan span); @@ -55,12 +59,18 @@ abstract class LoggerWithDeprecationType implements Logger { /// /// For non-user deprecation warnings, the [warnForDeprecation] extension /// method should be called instead. - void warn(String message, - {FileSpan? span, Trace? trace, bool deprecation = false}) { - internalWarn(message, - span: span, - trace: trace, - deprecation: deprecation ? Deprecation.userAuthored : null); + void warn( + String message, { + FileSpan? span, + Trace? trace, + bool deprecation = false, + }) { + internalWarn( + message, + span: span, + trace: trace, + deprecation: deprecation ? Deprecation.userAuthored : null, + ); } /// Equivalent to [Logger.warn], but for internal loggers that support @@ -68,8 +78,12 @@ abstract class LoggerWithDeprecationType implements Logger { /// /// Subclasses of this logger should override this method instead of [warn]. @protected - void internalWarn(String message, - {FileSpan? span, Trace? trace, Deprecation? deprecation}); + void internalWarn( + String message, { + FileSpan? span, + Trace? trace, + Deprecation? deprecation, + }); } /// An extension to add a `warnForDeprecation` method to loggers without @@ -77,12 +91,20 @@ abstract class LoggerWithDeprecationType implements Logger { @internal extension WarnForDeprecation on Logger { /// Emits a deprecation warning for [deprecation] with the given [message]. - void warnForDeprecation(Deprecation deprecation, String message, - {FileSpan? span, Trace? trace}) { + void warnForDeprecation( + Deprecation deprecation, + String message, { + FileSpan? span, + Trace? trace, + }) { if (deprecation.isFuture && this is! DeprecationProcessingLogger) return; if (this case LoggerWithDeprecationType self) { - self.internalWarn(message, - span: span, trace: trace, deprecation: deprecation); + self.internalWarn( + message, + span: span, + trace: trace, + deprecation: deprecation, + ); } else { warn(message, span: span, trace: trace, deprecation: true); } @@ -91,7 +113,11 @@ extension WarnForDeprecation on Logger { /// A logger that emits no messages. final class _QuietLogger implements Logger { - void warn(String message, - {FileSpan? span, Trace? trace, bool deprecation = false}) {} + void warn( + String message, { + FileSpan? span, + Trace? trace, + bool deprecation = false, + }) {} void debug(String message, SourceSpan span) {} } diff --git a/lib/src/logger/deprecation_processing.dart b/lib/src/logger/deprecation_processing.dart index d83c2ef80..5f0303982 100644 --- a/lib/src/logger/deprecation_processing.dart +++ b/lib/src/logger/deprecation_processing.dart @@ -41,11 +41,13 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { /// [_maxRepetitions]. final bool limitRepetition; - DeprecationProcessingLogger(this._inner, - {required this.silenceDeprecations, - required this.fatalDeprecations, - required this.futureDeprecations, - this.limitRepetition = true}); + DeprecationProcessingLogger( + this._inner, { + required this.silenceDeprecations, + required this.fatalDeprecations, + required this.futureDeprecations, + this.limitRepetition = true, + }); /// Warns if any of the deprecations options are incompatible or unnecessary. void validate() { @@ -53,14 +55,20 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { switch (deprecation) { case Deprecation(isFuture: true) when !futureDeprecations.contains(deprecation): - warn('Future $deprecation deprecation must be enabled before it can ' - 'be made fatal.'); + warn( + 'Future $deprecation deprecation must be enabled before it can ' + 'be made fatal.', + ); case Deprecation(obsoleteIn: Version()): - warn('$deprecation deprecation is obsolete, so does not need to be ' - 'made fatal.'); + warn( + '$deprecation deprecation is obsolete, so does not need to be ' + 'made fatal.', + ); case _ when silenceDeprecations.contains(deprecation): - warn('Ignoring setting to silence $deprecation deprecation, since it ' - 'has also been made fatal.'); + warn( + 'Ignoring setting to silence $deprecation deprecation, since it ' + 'has also been made fatal.', + ); default: // No warning. } @@ -71,15 +79,21 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { case Deprecation.userAuthored: warn('User-authored deprecations should not be silenced.'); case Deprecation(obsoleteIn: Version()): - warn('$deprecation deprecation is obsolete. If you were previously ' - 'silencing it, your code may now behave in unexpected ways.'); + warn( + '$deprecation deprecation is obsolete. If you were previously ' + 'silencing it, your code may now behave in unexpected ways.', + ); case Deprecation(isFuture: true) when futureDeprecations.contains(deprecation): - warn('Conflicting options for future $deprecation deprecation cancel ' - 'each other out.'); + warn( + 'Conflicting options for future $deprecation deprecation cancel ' + 'each other out.', + ); case Deprecation(isFuture: true): - warn('Future $deprecation deprecation is not yet active, so ' - 'silencing it is unnecessary.'); + warn( + 'Future $deprecation deprecation is not yet active, so ' + 'silencing it is unnecessary.', + ); default: // No warning. } @@ -87,14 +101,20 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { for (var deprecation in futureDeprecations) { if (!deprecation.isFuture) { - warn('$deprecation is not a future deprecation, so it does not need to ' - 'be explicitly enabled.'); + warn( + '$deprecation is not a future deprecation, so it does not need to ' + 'be explicitly enabled.', + ); } } } - void internalWarn(String message, - {FileSpan? span, Trace? trace, Deprecation? deprecation}) { + void internalWarn( + String message, { + FileSpan? span, + Trace? trace, + Deprecation? deprecation, + }) { if (deprecation != null) { _handleDeprecation(deprecation, message, span: span, trace: trace); } else { @@ -111,8 +131,12 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { /// [limitRepetitions] is true, the warning is dropped. /// /// Otherwise, this is passed on to [warn]. - void _handleDeprecation(Deprecation deprecation, String message, - {FileSpan? span, Trace? trace}) { + void _handleDeprecation( + Deprecation deprecation, + String message, { + FileSpan? span, + Trace? trace, + }) { if (deprecation.isFuture && !futureDeprecations.contains(deprecation)) { return; } @@ -124,7 +148,7 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { throw switch ((span, trace)) { (var span?, var trace?) => SassRuntimeException(message, span, trace), (var span?, null) => SassException(message, span), - _ => SassScriptException(message) + _ => SassScriptException(message), }; } if (silenceDeprecations.contains(deprecation)) return; @@ -136,8 +160,12 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { } if (_inner case LoggerWithDeprecationType inner) { - inner.internalWarn(message, - span: span, trace: trace, deprecation: deprecation); + inner.internalWarn( + message, + span: span, + trace: trace, + deprecation: deprecation, + ); } else { _inner.warn(message, span: span, trace: trace, deprecation: true); } @@ -156,8 +184,10 @@ final class DeprecationProcessingLogger extends LoggerWithDeprecationType { .map((count) => count - _maxRepetitions) .sum; if (total > 0) { - _inner.warn("$total repetitive deprecation warnings omitted." + - (js ? "" : "\nRun in verbose mode to see all warnings.")); + _inner.warn( + "$total repetitive deprecation warnings omitted." + + (js ? "" : "\nRun in verbose mode to see all warnings."), + ); } } } diff --git a/lib/src/logger/js_to_dart.dart b/lib/src/logger/js_to_dart.dart index 8ee2b205c..5a2fa264d 100644 --- a/lib/src/logger/js_to_dart.dart +++ b/lib/src/logger/js_to_dart.dart @@ -29,25 +29,39 @@ final class JSToDartLogger extends LoggerWithDeprecationType { JSToDartLogger(this._node, this._fallback, {bool? ascii}) : _ascii = ascii ?? glyph.ascii; - void internalWarn(String message, - {FileSpan? span, Trace? trace, Deprecation? deprecation}) { + void internalWarn( + String message, { + FileSpan? span, + Trace? trace, + Deprecation? deprecation, + }) { if (_node?.warn case var warn?) { warn( - message, - WarnOptions( - span: span ?? (undefined as SourceSpan?), - stack: trace.toString(), - deprecation: deprecation != null, - deprecationType: deprecations[deprecation?.id])); + message, + WarnOptions( + span: span ?? (undefined as SourceSpan?), + stack: trace.toString(), + deprecation: deprecation != null, + deprecationType: deprecations[deprecation?.id], + ), + ); } else { _withAscii(() { switch (_fallback) { case LoggerWithDeprecationType(): - _fallback.internalWarn(message, - span: span, trace: trace, deprecation: deprecation); + _fallback.internalWarn( + message, + span: span, + trace: trace, + deprecation: deprecation, + ); case _: - _fallback.warn(message, - span: span, trace: trace, deprecation: deprecation != null); + _fallback.warn( + message, + span: span, + trace: trace, + deprecation: deprecation != null, + ); } }); } diff --git a/lib/src/logger/stderr.dart b/lib/src/logger/stderr.dart index 07dcd70b9..1b644ec70 100644 --- a/lib/src/logger/stderr.dart +++ b/lib/src/logger/stderr.dart @@ -18,8 +18,12 @@ final class StderrLogger extends LoggerWithDeprecationType { const StderrLogger({this.color = false}); - void internalWarn(String message, - {FileSpan? span, Trace? trace, Deprecation? deprecation}) { + void internalWarn( + String message, { + FileSpan? span, + Trace? trace, + Deprecation? deprecation, + }) { var result = StringBuffer(); var showDeprecation = deprecation != null && deprecation != Deprecation.userAuthored; diff --git a/lib/src/logger/tracking.dart b/lib/src/logger/tracking.dart index efa463787..62b6940b2 100644 --- a/lib/src/logger/tracking.dart +++ b/lib/src/logger/tracking.dart @@ -21,8 +21,12 @@ final class TrackingLogger implements Logger { TrackingLogger(this._logger); - void warn(String message, - {FileSpan? span, Trace? trace, bool deprecation = false}) { + void warn( + String message, { + FileSpan? span, + Trace? trace, + bool deprecation = false, + }) { _emittedWarning = true; _logger.warn(message, span: span, trace: trace, deprecation: deprecation); } diff --git a/lib/src/module/built_in.dart b/lib/src/module/built_in.dart index e19d9bdb0..991bfcfba 100644 --- a/lib/src/module/built_in.dart +++ b/lib/src/module/built_in.dart @@ -27,11 +27,12 @@ final class BuiltInModule implements Module { bool get transitivelyContainsCss => false; bool get transitivelyContainsExtensions => false; - BuiltInModule(String name, - {Iterable? functions, - Iterable? mixins, - Map? variables}) - : url = Uri(scheme: "sass", path: name), + BuiltInModule( + String name, { + Iterable? functions, + Iterable? mixins, + Map? variables, + }) : url = Uri(scheme: "sass", path: name), functions = _callableMap(functions), mixins = _callableMap(mixins), variables = @@ -39,11 +40,15 @@ final class BuiltInModule implements Module { /// Returns a map from [callables]' names to their values. static Map _callableMap( - Iterable? callables) => - UnmodifiableMapView(callables == null - ? {} - : UnmodifiableMapView( - {for (var callable in callables) callable.name: callable})); + Iterable? callables, + ) => + UnmodifiableMapView( + callables == null + ? {} + : UnmodifiableMapView({ + for (var callable in callables) callable.name: callable, + }), + ); void setVariable(String name, Value value, AstNode nodeWithSpan) { if (!variables.containsKey(name)) { diff --git a/lib/src/module/forwarded_view.dart b/lib/src/module/forwarded_view.dart index c6cb42647..2875490f3 100644 --- a/lib/src/module/forwarded_view.dart +++ b/lib/src/module/forwarded_view.dart @@ -39,7 +39,9 @@ class ForwardedModuleView implements Module { /// Like [ForwardedModuleView], but returns `inner` as-is if it doesn't need /// any modification. static Module ifNecessary( - Module inner, ForwardRule rule) { + Module inner, + ForwardRule rule, + ) { if (rule.prefix == null && rule.shownMixinsAndFunctions == null && rule.shownVariables == null && @@ -52,21 +54,41 @@ class ForwardedModuleView implements Module { } ForwardedModuleView(this._inner, this._rule) - : variables = _forwardedMap(_inner.variables, _rule.prefix, - _rule.shownVariables, _rule.hiddenVariables), - variableNodes = _forwardedMap(_inner.variableNodes, _rule.prefix, - _rule.shownVariables, _rule.hiddenVariables), - functions = _forwardedMap(_inner.functions, _rule.prefix, - _rule.shownMixinsAndFunctions, _rule.hiddenMixinsAndFunctions), - mixins = _forwardedMap(_inner.mixins, _rule.prefix, - _rule.shownMixinsAndFunctions, _rule.hiddenMixinsAndFunctions); + : variables = _forwardedMap( + _inner.variables, + _rule.prefix, + _rule.shownVariables, + _rule.hiddenVariables, + ), + variableNodes = _forwardedMap( + _inner.variableNodes, + _rule.prefix, + _rule.shownVariables, + _rule.hiddenVariables, + ), + functions = _forwardedMap( + _inner.functions, + _rule.prefix, + _rule.shownMixinsAndFunctions, + _rule.hiddenMixinsAndFunctions, + ), + mixins = _forwardedMap( + _inner.mixins, + _rule.prefix, + _rule.shownMixinsAndFunctions, + _rule.hiddenMixinsAndFunctions, + ); /// Wraps [map] so that it only shows members allowed by [blocklist] or /// [safelist], with the given [prefix], if given. /// /// Only one of [blocklist] or [safelist] may be non-`null`. - static Map _forwardedMap(Map map, String? prefix, - Set? safelist, Set? blocklist) { + static Map _forwardedMap( + Map map, + String? prefix, + Set? safelist, + Set? blocklist, + ) { assert(safelist == null || blocklist == null); if (prefix == null && safelist == null && diff --git a/lib/src/module/shadowed_view.dart b/lib/src/module/shadowed_view.dart index b355bc2b8..32144b402 100644 --- a/lib/src/module/shadowed_view.dart +++ b/lib/src/module/shadowed_view.dart @@ -42,39 +42,56 @@ final class ShadowedModuleView implements Module { /// Like [ShadowedModuleView], but returns `null` if [inner] would be unchanged. static ShadowedModuleView? ifNecessary( - Module inner, - {Set? variables, - Set? functions, - Set? mixins}) => + Module inner, { + Set? variables, + Set? functions, + Set? mixins, + }) => _needsBlocklist(inner.variables, variables) || _needsBlocklist(inner.functions, functions) || _needsBlocklist(inner.mixins, mixins) - ? ShadowedModuleView(inner, - variables: variables, functions: functions, mixins: mixins) + ? ShadowedModuleView( + inner, + variables: variables, + functions: functions, + mixins: mixins, + ) : null; /// Returns a view of [inner] that doesn't include the given [variables], /// [functions], or [mixins]. - ShadowedModuleView(this._inner, - {Set? variables, Set? functions, Set? mixins}) - : variables = _shadowedMap(_inner.variables, variables), + ShadowedModuleView( + this._inner, { + Set? variables, + Set? functions, + Set? mixins, + }) : variables = _shadowedMap(_inner.variables, variables), variableNodes = _shadowedMap(_inner.variableNodes, variables), functions = _shadowedMap(_inner.functions, functions), mixins = _shadowedMap(_inner.mixins, mixins); - ShadowedModuleView._(this._inner, this.variables, this.variableNodes, - this.functions, this.mixins); + ShadowedModuleView._( + this._inner, + this.variables, + this.variableNodes, + this.functions, + this.mixins, + ); /// Returns a view of [map] with all keys in [blocklist] omitted. static Map _shadowedMap( - Map map, Set? blocklist) => + Map map, + Set? blocklist, + ) => blocklist == null || !_needsBlocklist(map, blocklist) ? map : LimitedMapView.blocklist(map, blocklist); /// Returns whether any of [map]'s keys are in [blocklist]. static bool _needsBlocklist( - Map map, Set? blocklist) => + Map map, + Set? blocklist, + ) => blocklist != null && map.isNotEmpty && blocklist.any(map.containsKey); void setVariable(String name, Value value, AstNode nodeWithSpan) { @@ -100,7 +117,12 @@ final class ShadowedModuleView implements Module { int get hashCode => _inner.hashCode; Module cloneCss() => ShadowedModuleView._( - _inner.cloneCss(), variables, variableNodes, functions, mixins); + _inner.cloneCss(), + variables, + variableNodes, + functions, + mixins, + ); String toString() => "shadowed $_inner"; } diff --git a/lib/src/parse/css.dart b/lib/src/parse/css.dart index d27b02cc0..beb403204 100644 --- a/lib/src/parse/css.dart +++ b/lib/src/parse/css.dart @@ -44,8 +44,10 @@ class CssParser extends ScssParser { var start = scanner.state; super.silentComment(); - error("Silent comments aren't allowed in plain CSS.", - scanner.spanFrom(start)); + error( + "Silent comments aren't allowed in plain CSS.", + scanner.spanFrom(start), + ); } Statement atRule(Statement child(), {bool root = false}) { @@ -77,7 +79,7 @@ class CssParser extends ScssParser { "media" => mediaRule(start), "-moz-document" => mozDocumentRule(start, name), "supports" => supportsRule(start), - _ => unknownAtRule(start, name) + _ => unknownAtRule(start, name), }; } @@ -103,7 +105,7 @@ class CssParser extends ScssParser { rest: null, keywordRest: null, ), - :var span + :var span, ) => (InterpolationBuffer() ..addInterpolation(name) @@ -112,19 +114,22 @@ class CssParser extends ScssParser { ..writeCharCode($rparen)) .interpolation(span), // This shouldn't be reachable. - var expression => - error("Unsupported plain CSS import.", expression.span) + var expression => error( + "Unsupported plain CSS import.", + expression.span, + ), }, - _ => StringExpression(interpolatedString().asInterpolation(static: true)) - .text + _ => StringExpression( + interpolatedString().asInterpolation(static: true), + ).text, }; _whitespace(); var modifiers = tryImportModifiers(); expectStatementSeparator("@import rule"); - return ImportRule( - [StaticImport(url, scanner.spanFrom(urlStart), modifiers: modifiers)], - scanner.spanFrom(start)); + return ImportRule([ + StaticImport(url, scanner.spanFrom(urlStart), modifiers: modifiers), + ], scanner.spanFrom(start)); } ParenthesizedExpression parentheses() { @@ -173,13 +178,16 @@ class CssParser extends ScssParser { if (_disallowedFunctionNames.contains(plain)) { error( - "This function isn't allowed in plain CSS.", scanner.spanFrom(start)); + "This function isn't allowed in plain CSS.", + scanner.spanFrom(start), + ); } return FunctionExpression( - plain, - ArgumentList(arguments, const {}, scanner.spanFrom(beforeArguments)), - scanner.spanFrom(start)); + plain, + ArgumentList(arguments, const {}, scanner.spanFrom(beforeArguments)), + scanner.spanFrom(start), + ); } Expression namespacedExpression(String namespace, LineScannerState start) { diff --git a/lib/src/parse/media_query.dart b/lib/src/parse/media_query.dart index 5101d4393..dcf57350d 100644 --- a/lib/src/parse/media_query.dart +++ b/lib/src/parse/media_query.dart @@ -88,12 +88,18 @@ class MediaQueryParser extends Parser { if (scanIdentifier("not")) { // For example, "@media screen and not (...) {" expectWhitespace(); - return CssMediaQuery.type(type, - modifier: modifier, conditions: ["(not ${_mediaInParens()})"]); + return CssMediaQuery.type( + type, + modifier: modifier, + conditions: ["(not ${_mediaInParens()})"], + ); } - return CssMediaQuery.type(type, - modifier: modifier, conditions: _mediaLogicSequence("and")); + return CssMediaQuery.type( + type, + modifier: modifier, + conditions: _mediaLogicSequence("and"), + ); } /// Consumes one or more `` expressions separated by diff --git a/lib/src/parse/parser.dart b/lib/src/parse/parser.dart index e573187f6..54402e221 100644 --- a/lib/src/parse/parser.dart +++ b/lib/src/parse/parser.dart @@ -216,8 +216,11 @@ class Parser { } /// Like [_identifierBody], but parses the body into the [text] buffer. - void _identifierBody(StringBuffer text, - {bool normalize = false, bool unit = false}) { + void _identifierBody( + StringBuffer text, { + bool normalize = false, + bool unit = false, + }) { loop: while (true) { switch (scanner.peekChar()) { @@ -475,8 +478,11 @@ class Parser { try { return String.fromCharCode(value); } on RangeError { - scanner.error("Invalid Unicode code point.", - position: start, length: scanner.position - start); + scanner.error( + "Invalid Unicode code point.", + position: start, + length: scanner.position - start, + ); } } else if (value <= 0x1F || value == 0x7F || @@ -536,8 +542,10 @@ class Parser { void expectIdentChar(int letter, {bool caseSensitive = false}) { if (scanIdentChar(letter, caseSensitive: caseSensitive)) return; - scanner.error('Expected "${String.fromCharCode(letter)}".', - position: scanner.position); + scanner.error( + 'Expected "${String.fromCharCode(letter)}".', + position: scanner.position, + ); } // ## Utilities @@ -554,9 +562,9 @@ class Parser { $plus || $minus => switch (scanner.peekChar(1)) { int(isDigit: true) => true, $dot => scanner.peekChar(2)?.isDigit ?? false, - _ => false + _ => false, }, - _ => false + _ => false, }; /// Returns whether the scanner is immediately before a plain CSS identifier. @@ -576,9 +584,9 @@ class Parser { int(isNameStart: true) || $backslash => true, $dash => switch (scanner.peekChar(forward + 1)) { int(isNameStart: true) || $backslash || $dash => true, - _ => false + _ => false, }, - _ => false + _ => false, }; } @@ -633,8 +641,11 @@ class Parser { /// Consumes an identifier and asserts that its name exactly matches [text]. @protected - void expectIdentifier(String text, - {String? name, bool caseSensitive = false}) { + void expectIdentifier( + String text, { + String? name, + bool caseSensitive = false, + }) { name ??= '"$text"'; var start = scanner.position; @@ -686,9 +697,10 @@ class Parser { return callback(); } on SourceSpanFormatException catch (error, stackTrace) { throwWithTrace( - SourceSpanFormatException(message, error.span, error.source), - error, - stackTrace); + SourceSpanFormatException(message, error.span, error.source), + error, + stackTrace, + ); } } @@ -725,15 +737,20 @@ class Parser { span = _adjustExceptionSpan(span); secondarySpans = { for (var (span, description) in secondarySpans.pairs) - _adjustExceptionSpan(span): description + _adjustExceptionSpan(span): description, }; } throwWithTrace( - MultiSpanSassFormatException( - error.message, span, error.primaryLabel, secondarySpans), - error, - stackTrace); + MultiSpanSassFormatException( + error.message, + span, + error.primaryLabel, + secondarySpans, + ), + error, + stackTrace, + ); } on SourceSpanFormatException catch (error, stackTrace) { var span = error.span as FileSpan; if (startsWithIgnoreCase(error.message, "expected")) { @@ -741,7 +758,10 @@ class Parser { } throwWithTrace( - SassFormatException(error.message, span), error, stackTrace); + SassFormatException(error.message, span), + error, + stackTrace, + ); } } diff --git a/lib/src/parse/sass.dart b/lib/src/parse/sass.dart index 406011445..394033331 100644 --- a/lib/src/parse/sass.dart +++ b/lib/src/parse/sass.dart @@ -60,8 +60,9 @@ class SassParser extends StylesheetParser { } if (_peekIndentation() <= currentIndentation) return; scanner.error( - "Nothing may be indented ${name == null ? 'here' : 'beneath a $name'}.", - position: _nextIndentationEnd!.position); + "Nothing may be indented ${name == null ? 'here' : 'beneath a $name'}.", + position: _nextIndentationEnd!.position, + ); } bool atEndOfStatement() => scanner.peekChar()?.isNewline ?? true; @@ -102,7 +103,9 @@ class SassParser extends StylesheetParser { // Serialize [url] as a Sass string because [StaticImport] expects it to // include quotes. return StaticImport( - Interpolation.plain(SassString(url).toString(), span), span); + Interpolation.plain(SassString(url).toString(), span), + span, + ); } else { try { return DynamicImport(parseImportUrl(url), span); @@ -139,8 +142,11 @@ class SassParser extends StylesheetParser { List statements(Statement? statement()) { if (scanner.peekChar() case $tab || $space) { - scanner.error("Indenting at the beginning of the document is illegal.", - position: 0, length: scanner.position); + scanner.error( + "Indenting at the beginning of the document is illegal.", + position: 0, + length: scanner.position, + ); } var statements = []; @@ -164,9 +170,9 @@ class SassParser extends StylesheetParser { $slash => switch (scanner.peekChar(1)) { $slash => _silentComment(), $asterisk => _loudComment(), - _ => child() + _ => child(), }, - _ => child() + _ => child(), }; /// Consumes an indented-style silent comment. @@ -210,8 +216,10 @@ class SassParser extends StylesheetParser { } } while (scanner.scan("//")); - return lastSilentComment = - SilentComment(buffer.toString(), scanner.spanFrom(start)); + return lastSilentComment = SilentComment( + buffer.toString(), + scanner.spanFrom(start), + ); } /// Consumes an indented-style loud context. @@ -281,10 +289,11 @@ class SassParser extends StylesheetParser { scanner.readChar(); } throw MultiSpanSassFormatException( - "Unexpected text after end of comment", - scanner.spanFrom(errorStart), - "extra text", - {span: "comment"}); + "Unexpected text after end of comment", + scanner.spanFrom(errorStart), + "extra text", + {span: "comment"}, + ); } else { return LoudComment(buffer.interpolation(span)); } @@ -336,9 +345,11 @@ class SassParser extends StylesheetParser { scanner.readChar(); return; default: - scanner.error(trailingSemicolon - ? "multiple statements on one line are not supported in the indented syntax." - : "expected newline."); + scanner.error( + trailingSemicolon + ? "multiple statements on one line are not supported in the indented syntax." + : "expected newline.", + ); } } @@ -347,10 +358,10 @@ class SassParser extends StylesheetParser { $cr => switch (scanner.peekChar(1)) { $lf => scanner.peekChar(2).isNewline, $cr || $ff => true, - _ => false + _ => false, }, $lf || $ff => scanner.peekChar(1).isNewline, - _ => false + _ => false, }; /// As long as the scanner's position is indented beneath the starting line, @@ -363,9 +374,10 @@ class SassParser extends StylesheetParser { childIndentation ??= indentation; if (childIndentation != indentation) { scanner.error( - "Inconsistent indentation, expected $childIndentation spaces.", - position: scanner.position - scanner.column, - length: scanner.column); + "Inconsistent indentation, expected $childIndentation spaces.", + position: scanner.position - scanner.column, + length: scanner.column, + ); } body(); @@ -444,17 +456,24 @@ class SassParser extends StylesheetParser { void _checkIndentationConsistency(bool containsTab, bool containsSpace) { if (containsTab) { if (containsSpace) { - scanner.error("Tabs and spaces may not be mixed.", - position: scanner.position - scanner.column, - length: scanner.column); + scanner.error( + "Tabs and spaces may not be mixed.", + position: scanner.position - scanner.column, + length: scanner.column, + ); } else if (_spaces == true) { - scanner.error("Expected spaces, was tabs.", - position: scanner.position - scanner.column, - length: scanner.column); + scanner.error( + "Expected spaces, was tabs.", + position: scanner.position - scanner.column, + length: scanner.column, + ); } } else if (containsSpace && _spaces == false) { - scanner.error("Expected tabs, was spaces.", - position: scanner.position - scanner.column, length: scanner.column); + scanner.error( + "Expected tabs, was spaces.", + position: scanner.position - scanner.column, + length: scanner.column, + ); } } diff --git a/lib/src/parse/scss.dart b/lib/src/parse/scss.dart index 0c238d292..3d5e002d8 100644 --- a/lib/src/parse/scss.dart +++ b/lib/src/parse/scss.dart @@ -50,7 +50,7 @@ class ScssParser extends StylesheetParser { 'versions.\n' '\n' 'Recommendation: @else if', - span: scanner.spanFrom(beforeAt) + span: scanner.spanFrom(beforeAt), )); scanner.position -= 2; return true; @@ -138,12 +138,16 @@ class ScssParser extends StylesheetParser { } while (scanner.scan("//")); if (plainCss) { - error("Silent comments aren't allowed in plain CSS.", - scanner.spanFrom(start)); + error( + "Silent comments aren't allowed in plain CSS.", + scanner.spanFrom(start), + ); } return lastSilentComment = SilentComment( - scanner.substring(start.position), scanner.spanFrom(start)); + scanner.substring(start.position), + scanner.spanFrom(start), + ); } /// Consumes a statement-level loud comment block. diff --git a/lib/src/parse/selector.dart b/lib/src/parse/selector.dart index 3d1e5e671..e7380daab 100644 --- a/lib/src/parse/selector.dart +++ b/lib/src/parse/selector.dart @@ -20,7 +20,7 @@ final _selectorPseudoClasses = { "any", "has", "host", - "host-context" + "host-context", }; /// Pseudo-element selectors that take unadorned selectors as arguments. @@ -41,12 +41,13 @@ class SelectorParser extends Parser { /// /// If [plainCss] is `true`, this will parse the selector as a plain CSS /// selector rather than a Sass selector. - SelectorParser(super.contents, - {super.url, - super.interpolationMap, - bool allowParent = true, - bool plainCss = false}) - : _allowParent = allowParent, + SelectorParser( + super.contents, { + super.url, + super.interpolationMap, + bool allowParent = true, + bool plainCss = false, + }) : _allowParent = allowParent, _plainCss = plainCss; SelectorList parse() { @@ -123,20 +124,23 @@ class SelectorParser extends Parser { case $plus: var combinatorStart = scanner.state; scanner.readChar(); - combinators - .add(CssValue(Combinator.nextSibling, spanFrom(combinatorStart))); + combinators.add( + CssValue(Combinator.nextSibling, spanFrom(combinatorStart)), + ); case $gt: var combinatorStart = scanner.state; scanner.readChar(); - combinators - .add(CssValue(Combinator.child, spanFrom(combinatorStart))); + combinators.add( + CssValue(Combinator.child, spanFrom(combinatorStart)), + ); case $tilde: var combinatorStart = scanner.state; scanner.readChar(); combinators.add( - CssValue(Combinator.followingSibling, spanFrom(combinatorStart))); + CssValue(Combinator.followingSibling, spanFrom(combinatorStart)), + ); case null: break loop; @@ -151,8 +155,13 @@ class SelectorParser extends Parser { $pipe: case _ when lookingAtIdentifier(): if (lastCompound != null) { - components.add(ComplexSelectorComponent( - lastCompound, combinators, spanFrom(componentStart))); + components.add( + ComplexSelectorComponent( + lastCompound, + combinators, + spanFrom(componentStart), + ), + ); } else if (combinators.isNotEmpty) { assert(initialCombinators == null); initialCombinators = combinators; @@ -163,7 +172,8 @@ class SelectorParser extends Parser { combinators = []; if (scanner.peekChar() == $ampersand) { scanner.error( - '"&" may only used at the beginning of a compound selector.'); + '"&" may only used at the beginning of a compound selector.', + ); } case _: @@ -174,8 +184,13 @@ class SelectorParser extends Parser { if (combinators.isNotEmpty && _plainCss) { scanner.error("expected selector."); } else if (lastCompound != null) { - components.add(ComplexSelectorComponent( - lastCompound, combinators, spanFrom(componentStart))); + components.add( + ComplexSelectorComponent( + lastCompound, + combinators, + spanFrom(componentStart), + ), + ); } else if (combinators.isNotEmpty) { initialCombinators = combinators; } else { @@ -183,8 +198,11 @@ class SelectorParser extends Parser { } return ComplexSelector( - initialCombinators ?? const [], components, spanFrom(start), - lineBreak: lineBreak); + initialCombinators ?? const [], + components, + spanFrom(start), + lineBreak: lineBreak, + ); } /// Consumes a compound selector. @@ -216,8 +234,10 @@ class SelectorParser extends Parser { case $percent: var selector = _placeholderSelector(); if (_plainCss) { - error("Placeholder selectors aren't allowed in plain CSS.", - scanner.spanFrom(start)); + error( + "Placeholder selectors aren't allowed in plain CSS.", + scanner.spanFrom(start), + ); } return selector; case $colon: @@ -226,7 +246,9 @@ class SelectorParser extends Parser { var selector = _parentSelector(); if (!allowParent) { error( - "Parent selectors aren't allowed here.", scanner.spanFrom(start)); + "Parent selectors aren't allowed here.", + scanner.spanFrom(start), + ); } return selector; @@ -264,8 +286,12 @@ class SelectorParser extends Parser { scanner.expectChar($rbracket); return AttributeSelector.withOperator( - name, operator, value, spanFrom(start), - modifier: modifier); + name, + operator, + value, + spanFrom(start), + modifier: modifier, + ); } /// Consumes a qualified name as part of an attribute selector. @@ -350,8 +376,11 @@ class SelectorParser extends Parser { scanner.expectChar($ampersand); var suffix = lookingAtIdentifierBody() ? identifierBody() : null; if (_plainCss && suffix != null) { - scanner.error("Parent selectors can't have suffixes in plain CSS.", - position: start.position, length: scanner.position - start.position); + scanner.error( + "Parent selectors can't have suffixes in plain CSS.", + position: start.position, + length: scanner.position - start.position, + ); } return ParentSelector(spanFrom(start), suffix: suffix); @@ -395,8 +424,13 @@ class SelectorParser extends Parser { } scanner.expectChar($rparen); - return PseudoSelector(name, spanFrom(start), - element: element, argument: argument, selector: selector); + return PseudoSelector( + name, + spanFrom(start), + element: element, + argument: argument, + selector: selector, + ); } /// Consumes an [`An+B` production][An+B] and returns its text. @@ -452,12 +486,16 @@ class SelectorParser extends Parser { return scanner.scanChar($asterisk) ? UniversalSelector(spanFrom(start), namespace: "*") : TypeSelector( - QualifiedName(identifier(), namespace: "*"), spanFrom(start)); + QualifiedName(identifier(), namespace: "*"), + spanFrom(start), + ); } else if (scanner.scanChar($pipe)) { return scanner.scanChar($asterisk) ? UniversalSelector(spanFrom(start), namespace: "") : TypeSelector( - QualifiedName(identifier(), namespace: ""), spanFrom(start)); + QualifiedName(identifier(), namespace: ""), + spanFrom(start), + ); } var nameOrNamespace = identifier(); @@ -467,8 +505,9 @@ class SelectorParser extends Parser { return UniversalSelector(spanFrom(start), namespace: nameOrNamespace); } else { return TypeSelector( - QualifiedName(identifier(), namespace: nameOrNamespace), - spanFrom(start)); + QualifiedName(identifier(), namespace: nameOrNamespace), + spanFrom(start), + ); } } @@ -477,7 +516,7 @@ class SelectorParser extends Parser { bool _isSimpleSelectorStart(int? character) => switch (character) { $asterisk || $lbracket || $dot || $hash || $percent || $colon => true, $ampersand => _plainCss, - _ => false + _ => false, }; /// The value of `consumeNewlines` is not relevant for this class. diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index c75a3df53..62cc99eba 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -100,8 +100,13 @@ abstract class StylesheetParser extends Parser { }); scanner.expectDone(); - return Stylesheet.internal(statements, scanner.spanFrom(start), warnings, - plainCss: plainCss, globalVariables: _globalVariables); + return Stylesheet.internal( + statements, + scanner.spanFrom(start), + warnings, + plainCss: plainCss, + globalVariables: _globalVariables, + ); }); } @@ -116,8 +121,10 @@ abstract class StylesheetParser extends Parser { return parameters; }); - (Expression, List) parseExpression() => - (_parseSingleProduction(_expression), warnings); + (Expression, List) parseExpression() => ( + _parseSingleProduction(_expression), + warnings, + ); SassNumber parseNumber() { var expression = _parseSingleProduction(_number); @@ -125,10 +132,12 @@ abstract class StylesheetParser extends Parser { } (VariableDeclaration, List) parseVariableDeclaration() => ( - _parseSingleProduction(() => lookingAtIdentifier() - ? _variableDeclarationWithNamespace() - : variableDeclarationWithoutNamespace()), - warnings + _parseSingleProduction( + () => lookingAtIdentifier() + ? _variableDeclarationWithNamespace() + : variableDeclarationWithoutNamespace(), + ), + warnings, ); (UseRule, List) parseUseRule() => ( @@ -139,7 +148,7 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: true); return _useRule(start); }), - warnings + warnings, ); /// Parses and returns [production] as the entire contents of [scanner]. @@ -214,8 +223,10 @@ abstract class StylesheetParser extends Parser { /// This never *consumes* a namespace, but if [namespace] is passed it will be /// used for the declaration. @protected - VariableDeclaration variableDeclarationWithoutNamespace( - [String? namespace, LineScannerState? start_]) { + VariableDeclaration variableDeclarationWithoutNamespace([ + String? namespace, + LineScannerState? start_, + ]) { var precedingComment = lastSilentComment; lastSilentComment = null; var start = start_ ?? scanner.state; // dart-lang/sdk#45348 @@ -224,8 +235,10 @@ abstract class StylesheetParser extends Parser { if (namespace != null) _assertPublic(name, () => scanner.spanFrom(start)); if (plainCss) { - error("Sass variables aren't allowed in plain CSS.", - scanner.spanFrom(start)); + error( + "Sass variables aren't allowed in plain CSS.", + scanner.spanFrom(start), + ); } whitespace(consumeNewlines: true); @@ -246,22 +259,24 @@ abstract class StylesheetParser extends Parser { message: '!default should only be written once for each variable.\n' 'This will be an error in Dart Sass 2.0.0.', - span: scanner.spanFrom(flagStart) + span: scanner.spanFrom(flagStart), )); } guarded = true; case 'global': if (namespace != null) { - error("!global isn't allowed for variables in other modules.", - scanner.spanFrom(flagStart)); + error( + "!global isn't allowed for variables in other modules.", + scanner.spanFrom(flagStart), + ); } else if (global) { warnings.add(( deprecation: Deprecation.duplicateVarFlags, message: '!global should only be written once for each variable.\n' 'This will be an error in Dart Sass 2.0.0.', - span: scanner.spanFrom(flagStart) + span: scanner.spanFrom(flagStart), )); } global = true; @@ -275,11 +290,15 @@ abstract class StylesheetParser extends Parser { } expectStatementSeparator("variable declaration"); - var declaration = VariableDeclaration(name, value, scanner.spanFrom(start), - namespace: namespace, - guarded: guarded, - global: global, - comment: precedingComment); + var declaration = VariableDeclaration( + name, + value, + scanner.spanFrom(start), + namespace: namespace, + guarded: guarded, + global: global, + comment: precedingComment, + ); if (global) _globalVariables.putIfAbsent(name, () => declaration.span); return declaration; } @@ -302,7 +321,8 @@ abstract class StylesheetParser extends Parser { : _styleRule( InterpolationBuffer() ..addInterpolation(variableOrInterpolation as Interpolation), - start); + start, + ); } /// Consumes a [VariableDeclaration], a [Declaration], or a [StyleRule]. @@ -392,7 +412,8 @@ abstract class StylesheetParser extends Parser { var name = nameBuffer.interpolation(scanner.spanFrom(start, beforeColon)); if (name.initialPlain.startsWith('--')) { var value = StringExpression( - _interpolatedDeclarationValue(silentComments: false)); + _interpolatedDeclarationValue(silentComments: false), + ); expectStatementSeparator("custom property"); return Declaration(name, value, scanner.spanFrom(start)); } @@ -482,8 +503,10 @@ abstract class StylesheetParser extends Parser { /// Consumes a [StyleRule], optionally with a [buffer] that may contain some /// text that has already been parsed. - StyleRule _styleRule( - [InterpolationBuffer? buffer, LineScannerState? start_]) { + StyleRule _styleRule([ + InterpolationBuffer? buffer, + LineScannerState? start_, + ]) { _isUseAllowed = false; var start = start_ ?? scanner.state; // dart-lang/sdk#45348 @@ -503,7 +526,7 @@ abstract class StylesheetParser extends Parser { deprecation: null, message: "This selector doesn't have any properties and won't be " "rendered.", - span: interpolation.span + span: interpolation.span, )); } @@ -522,8 +545,9 @@ abstract class StylesheetParser extends Parser { /// /// If [parseCustomProperties] is `true`, properties that begin with `--` will /// be parsed using custom property parsing rules. - Statement _propertyOrVariableDeclaration( - {bool parseCustomProperties = true}) { + Statement _propertyOrVariableDeclaration({ + bool parseCustomProperties = true, + }) { var start = scanner.state; Interpolation name; @@ -549,7 +573,8 @@ abstract class StylesheetParser extends Parser { if (parseCustomProperties && name.initialPlain.startsWith('--')) { var value = StringExpression( - _interpolatedDeclarationValue(silentComments: false)); + _interpolatedDeclarationValue(silentComments: false), + ); expectStatementSeparator("custom property"); return Declaration(name, value, scanner.spanFrom(start)); } @@ -572,17 +597,20 @@ abstract class StylesheetParser extends Parser { /// If [value] is passed, it's used as the value of the property without /// nesting. Declaration? _tryDeclarationChildren( - Interpolation name, LineScannerState start, - {Expression? value}) { + Interpolation name, + LineScannerState start, { + Expression? value, + }) { if (!lookingAtChildren()) return null; if (plainCss) { scanner.error("Nested declarations aren't allowed in plain CSS."); } return _withChildren( - _declarationChild, - start, - (children, span) => - Declaration.nested(name, children, span, value: value)); + _declarationChild, + start, + (children, span) => + Declaration.nested(name, children, span, value: value), + ); } /// Consumes a statement that's allowed within a declaration. @@ -682,7 +710,7 @@ abstract class StylesheetParser extends Parser { "include" => _includeRule(start), "warn" => _warnRule(start), "while" => _whileRule(start, _declarationChild), - _ => _disallowedAtRule(start) + _ => _disallowedAtRule(start), }; } @@ -706,10 +734,11 @@ abstract class StylesheetParser extends Parser { } error( - "@function rules may not contain " - "${statement is StyleRule ? "style rules" : "declarations"}.", - statement.span, - stackTrace); + "@function rules may not contain " + "${statement is StyleRule ? "style rules" : "declarations"}.", + statement.span, + stackTrace, + ); } } @@ -742,11 +771,17 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: false); if (scanner.peekChar() == $lparen) { var query = _atRootQuery(); - return _withChildren(_statement, start, - (children, span) => AtRootRule(children, span, query: query)); + return _withChildren( + _statement, + start, + (children, span) => AtRootRule(children, span, query: query), + ); } else if (lookingAtChildren() || (indented && atEndOfStatement())) { return _withChildren( - _statement, start, (children, span) => AtRootRule(children, span)); + _statement, + start, + (children, span) => AtRootRule(children, span), + ); } else { var child = _styleRule(); return AtRootRule([child], scanner.spanFrom(start)); @@ -781,8 +816,10 @@ abstract class StylesheetParser extends Parser { /// [start] should point before the `@`. ContentRule _contentRule(LineScannerState start) { if (!_inMixin) { - error("@content is only allowed within mixin declarations.", - scanner.spanFrom(start)); + error( + "@content is only allowed within mixin declarations.", + scanner.spanFrom(start), + ); } var beforeWhitespace = scanner.location; @@ -855,8 +892,10 @@ abstract class StylesheetParser extends Parser { ExtendRule _extendRule(LineScannerState start) { whitespace(consumeNewlines: true); if (!_inStyleRule && !_inMixin && !_inContentBlock) { - error("@extend may only be used within style rules.", - scanner.spanFrom(start)); + error( + "@extend may only be used within style rules.", + scanner.spanFrom(start), + ); } var value = almostAnyValue(); @@ -887,7 +926,7 @@ abstract class StylesheetParser extends Parser { 'compatibility with plain CSS mixins.\n' '\n' 'For details, see https://sass-lang.com/d/css-function-mixin', - span: scanner.spanFrom(beforeName) + span: scanner.spanFrom(beforeName), )); } @@ -895,11 +934,15 @@ abstract class StylesheetParser extends Parser { var parameters = _parameterList(); if (_inMixin || _inContentBlock) { - error("Mixins may not contain function declarations.", - scanner.spanFrom(start)); + error( + "Mixins may not contain function declarations.", + scanner.spanFrom(start), + ); } else if (_inControlDirective) { - error("Functions may not be declared in control directives.", - scanner.spanFrom(start)); + error( + "Functions may not be declared in control directives.", + scanner.spanFrom(start), + ); } if (unvendor(name) @@ -916,10 +959,16 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: false); return _withChildren( - _functionChild, - start, - (children, span) => FunctionRule(name, parameters, children, span, - comment: precedingComment)); + _functionChild, + start, + (children, span) => FunctionRule( + name, + parameters, + children, + span, + comment: precedingComment, + ), + ); } /// Consumes a `@for` rule. @@ -938,19 +987,20 @@ abstract class StylesheetParser extends Parser { bool? exclusive; var from = _expression( - consumeNewlines: true, - until: () { - if (!lookingAtIdentifier()) return false; - if (scanIdentifier("to")) { - exclusive = true; - return true; - } else if (scanIdentifier("through")) { - exclusive = false; - return true; - } else { - return false; - } - }); + consumeNewlines: true, + until: () { + if (!lookingAtIdentifier()) return false; + if (scanIdentifier("to")) { + exclusive = true; + return true; + } else if (scanIdentifier("through")) { + exclusive = false; + return true; + } else { + return false; + } + }, + ); if (exclusive == null) scanner.error('Expected "to" or "through".'); whitespace(consumeNewlines: true); @@ -958,8 +1008,14 @@ abstract class StylesheetParser extends Parser { return _withChildren(child, start, (children, span) { _inControlDirective = wasInControlDirective; - return ForRule(variable, from, to, children, span, - exclusive: exclusive!); // dart-lang/sdk#45348 + return ForRule( + variable, + from, + to, + children, + span, + exclusive: exclusive!, + ); // dart-lang/sdk#45348 }); } @@ -1002,15 +1058,29 @@ abstract class StylesheetParser extends Parser { if (shownMixinsAndFunctions != null) { return ForwardRule.show( - url, shownMixinsAndFunctions, shownVariables!, span, - prefix: prefix, configuration: configuration); + url, + shownMixinsAndFunctions, + shownVariables!, + span, + prefix: prefix, + configuration: configuration, + ); } else if (hiddenMixinsAndFunctions != null) { return ForwardRule.hide( - url, hiddenMixinsAndFunctions, hiddenVariables!, span, - prefix: prefix, configuration: configuration); + url, + hiddenMixinsAndFunctions, + hiddenVariables!, + span, + prefix: prefix, + configuration: configuration, + ); } else { - return ForwardRule(url, span, - prefix: prefix, configuration: configuration); + return ForwardRule( + url, + span, + prefix: prefix, + configuration: configuration, + ); } } @@ -1086,7 +1156,7 @@ abstract class StylesheetParser extends Parser { 'Sass 3.0.0.\n\n' 'More info and automated migrator: ' 'https://sass-lang.com/d/import', - span: argument.span + span: argument.span, )); } if ((_inControlDirective || _inMixin) && argument is DynamicImport) { @@ -1112,11 +1182,12 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: false); var modifiers = tryImportModifiers(); return StaticImport( - url is StringExpression - ? url.text - : Interpolation([url], [url.span], url.span), - scanner.spanFrom(start), - modifiers: modifiers); + url is StringExpression + ? url.text + : Interpolation([url], [url.span], url.span), + scanner.spanFrom(start), + modifiers: modifiers, + ); } var url = string(); @@ -1125,8 +1196,10 @@ abstract class StylesheetParser extends Parser { var modifiers = tryImportModifiers(); if (isPlainImportUrl(url) || modifiers != null) { return StaticImport( - Interpolation.plain(urlSpan.text, urlSpan), scanner.spanFrom(start), - modifiers: modifiers); + Interpolation.plain(urlSpan.text, urlSpan), + scanner.spanFrom(start), + modifiers: modifiers, + ); } else { try { return DynamicImport(parseImportUrl(url), urlSpan); @@ -1159,7 +1232,7 @@ abstract class StylesheetParser extends Parser { return switch (url.codeUnitAt(0)) { $slash => url.codeUnitAt(1) == $slash, $h => url.startsWith("http://") || url.startsWith("https://"), - _ => false + _ => false, }; } @@ -1192,8 +1265,13 @@ abstract class StylesheetParser extends Parser { if (query is! SupportsDeclaration) buffer.writeCharCode($rparen); } else { buffer.writeCharCode($lparen); - buffer.addInterpolation(_interpolatedDeclarationValue( - allowEmpty: true, allowSemicolon: true, consumeNewlines: true)); + buffer.addInterpolation( + _interpolatedDeclarationValue( + allowEmpty: true, + allowSemicolon: true, + consumeNewlines: true, + ), + ); buffer.writeCharCode($rparen); } scanner.expectChar($rparen); @@ -1224,7 +1302,9 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: true); var start = scanner.state; return SupportsNegation( - _supportsConditionInParens(), scanner.spanFrom(start)); + _supportsConditionInParens(), + scanner.spanFrom(start), + ); } else if (scanner.peekChar() == $lparen) { return _supportsCondition(inParentheses: true); } else { @@ -1234,7 +1314,10 @@ abstract class StylesheetParser extends Parser { var name = _expression(consumeNewlines: true); scanner.expectChar($colon); return SupportsDeclaration( - name, _supportsDeclarationValue(name), scanner.spanFrom(start)); + name, + _supportsDeclarationValue(name), + scanner.spanFrom(start), + ); } } @@ -1253,7 +1336,10 @@ abstract class StylesheetParser extends Parser { } var value = _interpolatedDeclarationValue( - allowEmpty: true, allowSemicolon: true, consumeNewlines: true); + allowEmpty: true, + allowSemicolon: true, + consumeNewlines: true, + ); scanner.expectChar($rparen); return SupportsFunction(name, value, scanner.spanFrom(start)); @@ -1290,8 +1376,11 @@ abstract class StylesheetParser extends Parser { contentParameters ?? ParameterList.empty(scanner.emptySpan); var wasInContentBlock = _inContentBlock; _inContentBlock = true; - content = _withChildren(_statement, start, - (children, span) => ContentBlock(contentParameters_, children, span)); + content = _withChildren( + _statement, + start, + (children, span) => ContentBlock(contentParameters_, children, span), + ); _inContentBlock = wasInContentBlock; } else { expectStatementSeparator(); @@ -1299,8 +1388,13 @@ abstract class StylesheetParser extends Parser { var span = scanner.spanFrom(start, start).expand((content ?? arguments).span); - return IncludeRule(name, arguments, span, - namespace: namespace, content: content); + return IncludeRule( + name, + arguments, + span, + namespace: namespace, + content: content, + ); } /// Consumes a `@media` rule. @@ -1310,8 +1404,11 @@ abstract class StylesheetParser extends Parser { MediaRule mediaRule(LineScannerState start) { whitespace(consumeNewlines: false); var query = _mediaQueryList(); - return _withChildren(_statement, start, - (children, span) => MediaRule(query, children, span)); + return _withChildren( + _statement, + start, + (children, span) => MediaRule(query, children, span), + ); } /// Consumes a mixin declaration. @@ -1332,7 +1429,7 @@ abstract class StylesheetParser extends Parser { 'compatibility with plain CSS mixins.\n' '\n' 'For details, see https://sass-lang.com/d/css-function-mixin', - span: scanner.spanFrom(beforeName) + span: scanner.spanFrom(beforeName), )); } @@ -1342,11 +1439,15 @@ abstract class StylesheetParser extends Parser { : ParameterList.empty(scanner.emptySpan); if (_inMixin || _inContentBlock) { - error("Mixins may not contain mixin declarations.", - scanner.spanFrom(start)); + error( + "Mixins may not contain mixin declarations.", + scanner.spanFrom(start), + ); } else if (_inControlDirective) { - error("Mixins may not be declared in control directives.", - scanner.spanFrom(start)); + error( + "Mixins may not be declared in control directives.", + scanner.spanFrom(start), + ); } whitespace(consumeNewlines: false); @@ -1354,8 +1455,13 @@ abstract class StylesheetParser extends Parser { return _withChildren(_statement, start, (children, span) { _inMixin = false; - return MixinRule(name, parameters, children, span, - comment: precedingComment); + return MixinRule( + name, + parameters, + children, + span, + comment: precedingComment, + ); }); } @@ -1437,7 +1543,7 @@ abstract class StylesheetParser extends Parser { "Dart Sass 2.0.0.\n" "\n" "For details, see https://sass-lang.com/d/moz-document.", - span: span + span: span, )); } @@ -1463,8 +1569,11 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: false); var condition = _supportsCondition(); whitespace(consumeNewlines: false); - return _withChildren(_statement, start, - (children, span) => SupportsRule(condition, children, span)); + return _withChildren( + _statement, + start, + (children, span) => SupportsRule(condition, children, span), + ); } /// Consumes a `@use` rule. @@ -1502,15 +1611,18 @@ abstract class StylesheetParser extends Parser { var basename = url.pathSegments.isEmpty ? "" : url.pathSegments.last; var dot = basename.indexOf("."); var namespace = basename.substring( - basename.startsWith("_") ? 1 : 0, dot == -1 ? basename.length : dot); + basename.startsWith("_") ? 1 : 0, + dot == -1 ? basename.length : dot, + ); try { return Parser.parseIdentifier(namespace); } on SassFormatException { error( - 'The default namespace "$namespace" is not a valid Sass identifier.\n' - "\n" - 'Recommendation: add an "as" clause to define an explicit namespace.', - scanner.spanFrom(start)); + 'The default namespace "$namespace" is not a valid Sass identifier.\n' + "\n" + 'Recommendation: add an "as" clause to define an explicit namespace.', + scanner.spanFrom(start), + ); } } @@ -1556,8 +1668,9 @@ abstract class StylesheetParser extends Parser { error("The same variable may only be configured once.", span); } variableNames.add(name); - configuration - .add(ConfiguredVariable(name, expression, span, guarded: guarded)); + configuration.add( + ConfiguredVariable(name, expression, span, guarded: guarded), + ); if (!scanner.scanChar($comma)) break; whitespace(consumeNewlines: true); @@ -1612,10 +1725,11 @@ abstract class StylesheetParser extends Parser { AtRule rule; if (lookingAtChildren()) { rule = _withChildren( - _statement, - start, - (children, span) => - AtRule(name, span, value: value, children: children)); + _statement, + start, + (children, span) => + AtRule(name, span, value: value, children: children), + ); } else { expectStatementSeparator(); rule = AtRule(name, scanner.spanFrom(start), value: value); @@ -1662,8 +1776,13 @@ abstract class StylesheetParser extends Parser { break; } - parameters.add(Parameter(name, scanner.spanFrom(variableStart), - defaultValue: defaultValue)); + parameters.add( + Parameter( + name, + scanner.spanFrom(variableStart), + defaultValue: defaultValue, + ), + ); if (!named.add(name)) { error("Duplicate parameter.", parameters.last.span); } @@ -1672,8 +1791,11 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: true); } scanner.expectChar($rparen); - return ParameterList(parameters, scanner.spanFrom(start), - restParameter: restParameter); + return ParameterList( + parameters, + scanner.spanFrom(start), + restParameter: restParameter, + ); } // ## Expressions @@ -1687,8 +1809,10 @@ abstract class StylesheetParser extends Parser { /// If [allowEmptySecondArg] is `true`, this allows the second argument to be /// omitted, in which case an unquoted empty string will be passed in its /// place. - ArgumentList _argumentInvocation( - {bool mixin = false, bool allowEmptySecondArg = false}) { + ArgumentList _argumentInvocation({ + bool mixin = false, + bool allowEmptySecondArg = false, + }) { var start = scanner.state; scanner.expectChar($lparen); whitespace(consumeNewlines: true); @@ -1719,8 +1843,10 @@ abstract class StylesheetParser extends Parser { break; } } else if (named.isNotEmpty) { - error("Positional arguments must come before keyword arguments.", - expression.span); + error( + "Positional arguments must come before keyword arguments.", + expression.span, + ); } else { positional.add(expression); } @@ -1740,8 +1866,13 @@ abstract class StylesheetParser extends Parser { } scanner.expectChar($rparen); - return ArgumentList(positional, named, scanner.spanFrom(start), - rest: rest, keywordRest: keywordRest); + return ArgumentList( + positional, + named, + scanner.spanFrom(start), + rest: rest, + keywordRest: keywordRest, + ); } /// Consumes an expression. @@ -1760,11 +1891,12 @@ abstract class StylesheetParser extends Parser { /// as whitespace. It should only be set to `true` in positions when a /// statement can't end. @protected - Expression _expression( - {bool bracketList = false, - bool singleEquals = false, - bool consumeNewlines = false, - bool until()?}) { + Expression _expression({ + bool bracketList = false, + bool singleEquals = false, + bool consumeNewlines = false, + bool until()?, + }) { if (until != null && until()) scanner.error("Expected expression."); LineScannerState? beforeBracket; @@ -1775,8 +1907,11 @@ abstract class StylesheetParser extends Parser { if (scanner.scanChar($rbracket)) { return ListExpression( - [], ListSeparator.undecided, scanner.spanFrom(beforeBracket), - brackets: true); + [], + ListSeparator.undecided, + scanner.spanFrom(beforeBracket), + brackets: true, + ); } } @@ -1837,9 +1972,11 @@ abstract class StylesheetParser extends Parser { var left = operands.removeLast(); var right = singleExpression_; if (right == null) { - scanner.error("Expected expression.", - position: scanner.position - operator.operator.length, - length: operator.operator.length); + scanner.error( + "Expected expression.", + position: scanner.position - operator.operator.length, + length: operator.operator.length, + ); } if (allowSlash && @@ -1854,7 +1991,9 @@ abstract class StylesheetParser extends Parser { if (operator case BinaryOperator.plus || BinaryOperator.minus) { if (scanner.string.substring( - right.span.start.offset - 1, right.span.start.offset) == + right.span.start.offset - 1, + right.span.start.offset, + ) == operator.operator && scanner.string.codeUnitAt(left.span.end.offset).isWhitespace) { warnings.add(( @@ -1875,7 +2014,7 @@ abstract class StylesheetParser extends Parser { "\n" "More info and automated migrator: " "https://sass-lang.com/d/strict-unary", - span: singleExpression_!.span + span: singleExpression_!.span, )); } } @@ -1925,9 +2064,11 @@ abstract class StylesheetParser extends Parser { operator != BinaryOperator.minus && operator != BinaryOperator.times && operator != BinaryOperator.dividedBy) { - scanner.error("Operators aren't allowed in plain CSS.", - position: scanner.position - operator.operator.length, - length: operator.operator.length); + scanner.error( + "Operators aren't allowed in plain CSS.", + position: scanner.position - operator.operator.length, + length: operator.operator.length, + ); } allowSlash = allowSlash && operator == BinaryOperator.dividedBy; @@ -1942,9 +2083,11 @@ abstract class StylesheetParser extends Parser { var singleExpression = singleExpression_; if (singleExpression == null) { - scanner.error("Expected expression.", - position: scanner.position - operator.operator.length, - length: operator.operator.length); + scanner.error( + "Expected expression.", + position: scanner.position - operator.operator.length, + length: operator.operator.length, + ); } operands.add(singleExpression); whitespace(consumeNewlines: true); @@ -1961,8 +2104,11 @@ abstract class StylesheetParser extends Parser { if (singleExpression == null) scanner.error("Expected expression."); spaceExpressions.add(singleExpression); - singleExpression_ = ListExpression(spaceExpressions, ListSeparator.space, - spaceExpressions.first.span.expand(singleExpression.span)); + singleExpression_ = ListExpression( + spaceExpressions, + ListSeparator.space, + spaceExpressions.first.span.expand(singleExpression.span), + ); spaceExpressions_ = null; } @@ -2017,15 +2163,19 @@ abstract class StylesheetParser extends Parser { case $langle: scanner.readChar(); - addOperator(scanner.scanChar($equal) - ? BinaryOperator.lessThanOrEquals - : BinaryOperator.lessThan); + addOperator( + scanner.scanChar($equal) + ? BinaryOperator.lessThanOrEquals + : BinaryOperator.lessThan, + ); case $rangle: scanner.readChar(); - addOperator(scanner.scanChar($equal) - ? BinaryOperator.greaterThanOrEquals - : BinaryOperator.greaterThan); + addOperator( + scanner.scanChar($equal) + ? BinaryOperator.greaterThanOrEquals + : BinaryOperator.greaterThan, + ); case $asterisk: scanner.readChar(); @@ -2137,21 +2287,30 @@ abstract class StylesheetParser extends Parser { var singleExpression = singleExpression_; if (singleExpression != null) commaExpressions.add(singleExpression); _inExpression = wasInExpression; - return ListExpression(commaExpressions, ListSeparator.comma, - scanner.spanFrom(beforeBracket ?? start), - brackets: bracketList); + return ListExpression( + commaExpressions, + ListSeparator.comma, + scanner.spanFrom(beforeBracket ?? start), + brackets: bracketList, + ); } else if (bracketList && spaceExpressions != null) { resolveOperations(); _inExpression = wasInExpression; - return ListExpression(spaceExpressions..add(singleExpression_!), - ListSeparator.space, scanner.spanFrom(beforeBracket!), - brackets: true); + return ListExpression( + spaceExpressions..add(singleExpression_!), + ListSeparator.space, + scanner.spanFrom(beforeBracket!), + brackets: true, + ); } else { resolveSpaceExpressions(); if (bracketList) { - singleExpression_ = ListExpression([singleExpression_!], - ListSeparator.undecided, scanner.spanFrom(beforeBracket!), - brackets: true); + singleExpression_ = ListExpression( + [singleExpression_!], + ListSeparator.undecided, + scanner.spanFrom(beforeBracket!), + brackets: true, + ); } _inExpression = wasInExpression; return singleExpression_!; @@ -2165,9 +2324,10 @@ abstract class StylesheetParser extends Parser { /// operator at the top level. Expression expressionUntilComma({bool singleEquals = false}) { return _expression( - singleEquals: singleEquals, - consumeNewlines: true, - until: () => scanner.peekChar() == $comma); + singleEquals: singleEquals, + consumeNewlines: true, + until: () => scanner.peekChar() == $comma, + ); } /// Whether [expression] is allowed as an operand of a `/` expression that @@ -2206,7 +2366,7 @@ abstract class StylesheetParser extends Parser { $backslash || >= 0x80 => identifierLike(), - _ => scanner.error("Expected expression.") + _ => scanner.error("Expected expression."), }; /// Consumes a parenthesized expression. @@ -2221,7 +2381,10 @@ abstract class StylesheetParser extends Parser { if (!_lookingAtExpression()) { scanner.expectChar($rparen); return ListExpression( - [], ListSeparator.undecided, scanner.spanFrom(start)); + [], + ListSeparator.undecided, + scanner.spanFrom(start), + ); } var first = expressionUntilComma(); @@ -2246,7 +2409,10 @@ abstract class StylesheetParser extends Parser { scanner.expectChar($rparen); return ListExpression( - expressions, ListSeparator.comma, scanner.spanFrom(start)); + expressions, + ListSeparator.comma, + scanner.spanFrom(start), + ); } finally { _inParentheses = wasInParentheses; } @@ -2335,13 +2501,14 @@ abstract class StylesheetParser extends Parser { } return SassColor.rgbInternal( - red, - green, - blue, - alpha ?? 1, - // Don't emit four- or eight-digit hex colors as hex, since that's not - // yet well-supported in browsers. - alpha == null ? SpanColorFormat(scanner.spanFrom(start)) : null); + red, + green, + blue, + alpha ?? 1, + // Don't emit four- or eight-digit hex colors as hex, since that's not + // yet well-supported in browsers. + alpha == null ? SpanColorFormat(scanner.spanFrom(start)) : null, + ); } /// Returns whether [interpolation] is a plain string that can be parsed as a @@ -2393,8 +2560,11 @@ abstract class StylesheetParser extends Parser { if (operator == null) { scanner.error("Expected unary operator.", position: scanner.position - 1); } else if (plainCss && operator != UnaryOperator.divide) { - scanner.error("Operators aren't allowed in plain CSS.", - position: scanner.position - 1, length: 1); + scanner.error( + "Operators aren't allowed in plain CSS.", + position: scanner.position - 1, + length: 1, + ); } whitespace(consumeNewlines: true); @@ -2408,7 +2578,7 @@ abstract class StylesheetParser extends Parser { $plus => UnaryOperator.plus, $minus => UnaryOperator.minus, $slash => UnaryOperator.divide, - _ => null + _ => null, }; /// Consumes a number expression. @@ -2423,9 +2593,10 @@ abstract class StylesheetParser extends Parser { // dot. We don't allow a plain ".", but we need to allow "1." so that // "1..." will work as a rest argument. _tryDecimal( - allowTrailingDot: scanner.position != start.position && - first != $plus && - first != $minus); + allowTrailingDot: scanner.position != start.position && + first != $plus && + first != $minus, + ); _tryExponent(); // Use Dart's built-in double parsing so that we don't accumulate @@ -2516,7 +2687,9 @@ abstract class StylesheetParser extends Parser { error("Expected at most 6 digits.", scanner.spanFrom(start)); } else if (hasQuestionMark) { return StringExpression.plain( - scanner.substring(start.position), scanner.spanFrom(start)); + scanner.substring(start.position), + scanner.spanFrom(start), + ); } if (scanner.scanChar($minus)) { @@ -2538,7 +2711,9 @@ abstract class StylesheetParser extends Parser { } return StringExpression.plain( - scanner.substring(start.position), scanner.spanFrom(start)); + scanner.substring(start.position), + scanner.spanFrom(start), + ); } /// Consumes a variable expression. @@ -2547,8 +2722,10 @@ abstract class StylesheetParser extends Parser { var name = variableName(); if (plainCss) { - error("Sass variables aren't allowed in plain CSS.", - scanner.spanFrom(start)); + error( + "Sass variables aren't allowed in plain CSS.", + scanner.spanFrom(start), + ); } return VariableExpression(name, scanner.spanFrom(start)); @@ -2557,8 +2734,10 @@ abstract class StylesheetParser extends Parser { /// Consumes a selector expression. SelectorExpression _selector() { if (plainCss) { - scanner.error("The parent selector isn't allowed in plain CSS.", - length: 1); + scanner.error( + "The parent selector isn't allowed in plain CSS.", + length: 1, + ); } var start = scanner.state; @@ -2569,7 +2748,7 @@ abstract class StylesheetParser extends Parser { deprecation: null, message: 'In Sass, "&&" means two copies of the parent selector. You ' 'probably want to use "and" instead.', - span: scanner.spanFrom(start) + span: scanner.spanFrom(start), )); scanner.position--; } @@ -2615,8 +2794,10 @@ abstract class StylesheetParser extends Parser { } } - return StringExpression(buffer.interpolation(scanner.spanFrom(start)), - quotes: true); + return StringExpression( + buffer.interpolation(scanner.spanFrom(start)), + quotes: true, + ); } /// Consumes an expression that starts like an identifier. @@ -2630,12 +2811,17 @@ abstract class StylesheetParser extends Parser { if (plain == "if" && scanner.peekChar() == $lparen) { var invocation = _argumentInvocation(); return IfExpression( - invocation, identifier.span.expand(invocation.span)); + invocation, + identifier.span.expand(invocation.span), + ); } else if (plain == "not") { whitespace(consumeNewlines: true); var expression = _singleExpression(); - return UnaryOperationExpression(UnaryOperator.not, expression, - identifier.span.expand(expression.span)); + return UnaryOperationExpression( + UnaryOperator.not, + expression, + identifier.span.expand(expression.span), + ); } lower = plain.toLowerCase(); @@ -2650,8 +2836,13 @@ abstract class StylesheetParser extends Parser { } if (colorsByName[lower] case var color?) { - color = SassColor.rgbInternal(color.red, color.green, color.blue, - color.alpha, SpanColorFormat(identifier.span)); + color = SassColor.rgbInternal( + color.red, + color.green, + color.blue, + color.alpha, + SpanColorFormat(identifier.span), + ); return ColorExpression(color, identifier.span); } } @@ -2673,13 +2864,17 @@ abstract class StylesheetParser extends Parser { case $lparen when plain != null: return FunctionExpression( - plain, - _argumentInvocation(allowEmptySecondArg: lower == 'var'), - scanner.spanFrom(start)); + plain, + _argumentInvocation(allowEmptySecondArg: lower == 'var'), + scanner.spanFrom(start), + ); case $lparen: return InterpolatedFunctionExpression( - identifier, _argumentInvocation(), scanner.spanFrom(start)); + identifier, + _argumentInvocation(), + scanner.spanFrom(start), + ); case _: return StringExpression(identifier); @@ -2695,13 +2890,19 @@ abstract class StylesheetParser extends Parser { if (scanner.peekChar() == $dollar) { var name = variableName(); _assertPublic(name, () => scanner.spanFrom(start)); - return VariableExpression(name, scanner.spanFrom(start), - namespace: namespace); + return VariableExpression( + name, + scanner.spanFrom(start), + namespace: namespace, + ); } return FunctionExpression( - _publicIdentifier(), _argumentInvocation(), scanner.spanFrom(start), - namespace: namespace); + _publicIdentifier(), + _argumentInvocation(), + scanner.spanFrom(start), + namespace: namespace, + ); } /// If [name] is the name of a function with special syntax, consumes it. @@ -2733,8 +2934,9 @@ abstract class StylesheetParser extends Parser { buffer.writeCharCode($lparen); case "url": - return _tryUrlContents(start) - .andThen((contents) => StringExpression(contents)); + return _tryUrlContents( + start, + ).andThen((contents) => StringExpression(contents)); case _: return null; @@ -2808,9 +3010,10 @@ abstract class StylesheetParser extends Parser { } return InterpolatedFunctionExpression( - Interpolation.plain("url", scanner.spanFrom(start)), - _argumentInvocation(), - scanner.spanFrom(start)); + Interpolation.plain("url", scanner.spanFrom(start)), + _argumentInvocation(), + scanner.spanFrom(start), + ); } /// Consumes tokens up to "{", "}", ";", or "!". @@ -2944,13 +3147,14 @@ abstract class StylesheetParser extends Parser { /// statement can't end. /// /// Unlike [declarationValue], this allows interpolation. - Interpolation _interpolatedDeclarationValue( - {bool allowEmpty = false, - bool allowSemicolon = false, - bool allowColon = true, - bool allowOpenBrace = true, - bool silentComments = true, - bool consumeNewlines = false}) { + Interpolation _interpolatedDeclarationValue({ + bool allowEmpty = false, + bool allowSemicolon = false, + bool allowColon = true, + bool allowOpenBrace = true, + bool silentComments = true, + bool consumeNewlines = false, + }) { // NOTE: this logic is largely duplicated in Parser.declarationValue. Most // changes here should be mirrored there. @@ -3332,12 +3536,13 @@ abstract class StylesheetParser extends Parser { /// Consumes an expression until it reaches a top-level `<`, `>`, or a `=` /// that's not `==`. Expression _expressionUntilComparison() => _expression( - consumeNewlines: true, - until: () => switch (scanner.peekChar()) { - $equal => scanner.peekChar(1) != $equal, - $langle || $rangle => true, - _ => false - }); + consumeNewlines: true, + until: () => switch (scanner.peekChar()) { + $equal => scanner.peekChar(1) != $equal, + $langle || $rangle => true, + _ => false, + }, + ); // ## Supports Conditions @@ -3350,7 +3555,9 @@ abstract class StylesheetParser extends Parser { if (scanIdentifier("not")) { whitespace(consumeNewlines: inParentheses); return SupportsNegation( - _supportsConditionInParens(), scanner.spanFrom(start)); + _supportsConditionInParens(), + scanner.spanFrom(start), + ); } var condition = _supportsConditionInParens(); @@ -3369,7 +3576,11 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: inParentheses); var right = _supportsConditionInParens(); condition = SupportsOperation( - condition, right, operator, scanner.spanFrom(start)); + condition, + right, + operator, + scanner.spanFrom(start), + ); whitespace(consumeNewlines: inParentheses); } return condition; @@ -3387,7 +3598,10 @@ abstract class StylesheetParser extends Parser { if (scanner.scanChar($lparen)) { var arguments = _interpolatedDeclarationValue( - allowEmpty: true, allowSemicolon: true, consumeNewlines: true); + allowEmpty: true, + allowSemicolon: true, + consumeNewlines: true, + ); scanner.expectChar($rparen); return SupportsFunction(identifier, arguments, scanner.spanFrom(start)); } else if (identifier.contents case [Expression expression]) { @@ -3446,11 +3660,14 @@ abstract class StylesheetParser extends Parser { // after all, so we rethrow the declaration-parsing error. var contents = (InterpolationBuffer() ..addInterpolation(identifier) - ..addInterpolation(_interpolatedDeclarationValue( + ..addInterpolation( + _interpolatedDeclarationValue( allowEmpty: true, allowSemicolon: true, allowColon: false, - consumeNewlines: true))) + consumeNewlines: true, + ), + )) .interpolation(scanner.spanFrom(nameStart)); if (scanner.peekChar() == $colon) rethrow; @@ -3466,8 +3683,11 @@ abstract class StylesheetParser extends Parser { /// Parses and returns the right-hand side of a declaration in a supports /// query. Expression _supportsDeclarationValue(Expression name) { - if (name case StringExpression(hasQuotes: false, :var text) - when text.initialPlain.startsWith("--")) { + if (name + case StringExpression( + hasQuotes: false, + :var text, + ) when text.initialPlain.startsWith("--")) { return StringExpression(_interpolatedDeclarationValue()); } else { whitespace(consumeNewlines: true); @@ -3479,7 +3699,9 @@ abstract class StylesheetParser extends Parser { /// /// Otherwise, return `null` without moving the scanner position. SupportsOperation? _trySupportsOperation( - Interpolation interpolation, LineScannerState start) { + Interpolation interpolation, + LineScannerState start, + ) { if (interpolation.contents.length != 1) return null; var expression = interpolation.contents.first; if (expression is! Expression) return null; @@ -3504,10 +3726,11 @@ abstract class StylesheetParser extends Parser { whitespace(consumeNewlines: true); var right = _supportsConditionInParens(); operation = SupportsOperation( - operation ?? SupportsInterpolation(expression, interpolation.span), - right, - operator, - scanner.spanFrom(start)); + operation ?? SupportsInterpolation(expression, interpolation.span), + right, + operator, + scanner.spanFrom(start), + ); whitespace(consumeNewlines: true); } @@ -3525,7 +3748,6 @@ abstract class StylesheetParser extends Parser { /// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier bool _lookingAtInterpolatedIdentifier() => // See also [ScssParser._lookingAtIdentifier]. - switch (scanner.peekChar()) { null => false, int(isNameStart: true) || $backslash => true, @@ -3534,9 +3756,9 @@ abstract class StylesheetParser extends Parser { null => false, $hash => scanner.peekChar(2) == $lbrace, int(isNameStart: true) || $backslash || $dash => true, - _ => false + _ => false, }, - _ => false + _ => false, }; /// Returns whether the scanner is immediately before a character that could @@ -3544,7 +3766,7 @@ abstract class StylesheetParser extends Parser { bool _lookingAtPotentialPropertyHack() => switch (scanner.peekChar()) { $colon || $asterisk || $dot => true, $hash => scanner.peekChar(1) != $lbrace, - _ => false + _ => false, }; /// Returns whether the scanner is immediately before a sequence of characters @@ -3555,7 +3777,7 @@ abstract class StylesheetParser extends Parser { null => false, int(isName: true) || $backslash => true, $hash => scanner.peekChar(1) == $lbrace, - _ => false + _ => false, }; /// Returns whether the scanner is immediately before a SassScript expression. @@ -3564,7 +3786,7 @@ abstract class StylesheetParser extends Parser { $dot => scanner.peekChar(1) != $dot, $exclamation => switch (scanner.peekChar(1)) { null || $i || $I || int(isWhitespace: true) => true, - _ => false + _ => false, }, $lparen || $slash || @@ -3580,15 +3802,18 @@ abstract class StylesheetParser extends Parser { int(isNameStart: true) || int(isDigit: true) => true, - _ => false + _ => false, }; // ## Utilities /// Consumes a block of [child] statements and passes them, as well as the /// span from [start] to the end of the child block, to [create]. - T _withChildren(Statement child(), LineScannerState start, - T create(List children, FileSpan span)) { + T _withChildren( + Statement child(), + LineScannerState start, + T create(List children, FileSpan span), + ) { var result = create(children(child), scanner.spanFrom(start)); whitespaceWithoutComments(consumeNewlines: false); return result; @@ -3601,8 +3826,11 @@ abstract class StylesheetParser extends Parser { try { return Uri.parse(url); } on FormatException catch (innerError, stackTrace) { - error("Invalid URL: ${innerError.message}", scanner.spanFrom(start), - stackTrace); + error( + "Invalid URL: ${innerError.message}", + scanner.spanFrom(start), + stackTrace, + ); } } @@ -3619,8 +3847,10 @@ abstract class StylesheetParser extends Parser { /// Calls [span] to provide the span for an error if one occurs. void _assertPublic(String identifier, FileSpan span()) { if (!isPrivate(identifier)) return; - error("Private members can't be accessed from outside their modules.", - span()); + error( + "Private members can't be accessed from outside their modules.", + span(), + ); } /// Adds [expression] to [buffer], or if it's an unquoted string adds the diff --git a/lib/src/stylesheet_graph.dart b/lib/src/stylesheet_graph.dart index 5e03cf458..f2ad283bc 100644 --- a/lib/src/stylesheet_graph.dart +++ b/lib/src/stylesheet_graph.dart @@ -46,13 +46,18 @@ class StylesheetGraph { /// import [url] (resolved relative to [baseUrl] if it's passed). /// /// Returns `true` if the import cache can't find a stylesheet at [url]. - bool modifiedSince(Uri url, DateTime since, - [Importer? baseImporter, Uri? baseUrl]) { + bool modifiedSince( + Uri url, + DateTime since, [ + Importer? baseImporter, + Uri? baseUrl, + ]) { DateTime transitiveModificationTime(StylesheetNode node) { return _transitiveModificationTimes.putIfAbsent(node.canonicalUrl, () { var latest = node.importer.modificationTime(node.canonicalUrl); - for (var upstream - in node.upstream.values.followedBy(node.upstreamImports.values)) { + for (var upstream in node.upstream.values.followedBy( + node.upstreamImports.values, + )) { // If an import is missing, always recompile so we show the user the // error. var upstreamTime = upstream == null @@ -77,8 +82,13 @@ class StylesheetGraph { /// /// Returns `null` if the import cache can't find a stylesheet at [url]. StylesheetNode? _add(Uri url, [Importer? baseImporter, Uri? baseUrl]) { - var result = _ignoreErrors(() => importCache.canonicalize(url, - baseImporter: baseImporter, baseUrl: baseUrl)); + var result = _ignoreErrors( + () => importCache.canonicalize( + url, + baseImporter: baseImporter, + baseUrl: baseUrl, + ), + ); if (result case (var importer, var canonicalUrl, :var originalUrl)) { addCanonical(importer, canonicalUrl, originalUrl); return nodes[canonicalUrl]; @@ -103,16 +113,28 @@ class StylesheetGraph { /// `false` when initially adding stylesheets, not when handling future /// updates. Set addCanonical( - Importer importer, Uri canonicalUrl, Uri originalUrl, - {bool recanonicalize = true}) { + Importer importer, + Uri canonicalUrl, + Uri originalUrl, { + bool recanonicalize = true, + }) { if (_nodes[canonicalUrl] != null) return const {}; - var stylesheet = _ignoreErrors(() => importCache - .importCanonical(importer, canonicalUrl, originalUrl: originalUrl)); + var stylesheet = _ignoreErrors( + () => importCache.importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + ), + ); if (stylesheet == null) return const {}; - var node = StylesheetNode._(stylesheet, importer, canonicalUrl, - _upstreamNodes(stylesheet, importer, canonicalUrl)); + var node = StylesheetNode._( + stylesheet, + importer, + canonicalUrl, + _upstreamNodes(stylesheet, importer, canonicalUrl), + ); _nodes[canonicalUrl] = node; return recanonicalize @@ -126,18 +148,21 @@ class StylesheetGraph { /// The first map contains stylesheets depended on via module loads while the /// second map contains those depended on via `@import`. _UpstreamNodes _upstreamNodes( - Stylesheet stylesheet, Importer baseImporter, Uri baseUrl) { + Stylesheet stylesheet, + Importer baseImporter, + Uri baseUrl, + ) { var active = {baseUrl}; var dependencies = findDependencies(stylesheet); return ( modules: { for (var url in dependencies.modules) - url: _nodeFor(url, baseImporter, baseUrl, active) + url: _nodeFor(url, baseImporter, baseUrl, active), }, imports: { for (var url in dependencies.imports) - url: _nodeFor(url, baseImporter, baseUrl, active, forImport: true) - } + url: _nodeFor(url, baseImporter, baseUrl, active, forImport: true), + }, ); } @@ -161,7 +186,8 @@ class StylesheetGraph { importCache.clearImport(canonicalUrl); var stylesheet = _ignoreErrors( - () => importCache.importCanonical(node.importer, canonicalUrl)); + () => importCache.importCanonical(node.importer, canonicalUrl), + ); if (stylesheet == null) return false; node._stylesheet = stylesheet; @@ -231,21 +257,31 @@ class StylesheetGraph { /// /// Returns all nodes whose imports were changed. Set _recanonicalizeImports( - Importer importer, Uri canonicalUrl) { + Importer importer, + Uri canonicalUrl, + ) { importCache.clearCanonicalize(canonicalUrl); var changed = {}; for (var node in nodes.values) { var newUpstream = _recanonicalizeImportsForNode( - node, importer, canonicalUrl, - forImport: false); + node, + importer, + canonicalUrl, + forImport: false, + ); var newUpstreamImports = _recanonicalizeImportsForNode( - node, importer, canonicalUrl, - forImport: true); + node, + importer, + canonicalUrl, + forImport: true, + ); if (newUpstream.isNotEmpty || newUpstreamImports.isNotEmpty) { changed.add(node); - node._replaceUpstream(mergeMaps(node.upstream, newUpstream), - mergeMaps(node.upstreamImports, newUpstreamImports)); + node._replaceUpstream( + mergeMaps(node.upstream, newUpstream), + mergeMaps(node.upstreamImports, newUpstreamImports), + ); } } @@ -265,8 +301,11 @@ class StylesheetGraph { /// [node.upstreamImports]. Otherwise, it re-runs canonicalization for /// [node.upstream]. Map _recanonicalizeImportsForNode( - StylesheetNode node, Importer importer, Uri canonicalUrl, - {required bool forImport}) { + StylesheetNode node, + Importer importer, + Uri canonicalUrl, { + required bool forImport, + }) { var map = forImport ? node.upstreamImports : node.upstream; var newMap = {}; for (var (url, upstream) in map.pairs) { @@ -276,10 +315,12 @@ class StylesheetGraph { // before, it changed and the stylesheet needs to be recompiled. CanonicalizeResult? result; try { - result = importCache.canonicalize(url, - baseImporter: node.importer, - baseUrl: node.canonicalUrl, - forImport: forImport); + result = importCache.canonicalize( + url, + baseImporter: node.importer, + baseUrl: node.canonicalUrl, + forImport: forImport, + ); } catch (_) { // If the call to canonicalize failed, we ignore the error so that // it can be surfaced more gracefully when [node]'s stylesheet is @@ -300,10 +341,20 @@ class StylesheetGraph { /// The [active] set should contain the canonical URLs that are currently /// being imported. It's used to detect circular imports. StylesheetNode? _nodeFor( - Uri url, Importer baseImporter, Uri baseUrl, Set active, - {bool forImport = false}) { - var result = _ignoreErrors(() => importCache.canonicalize(url, - baseImporter: baseImporter, baseUrl: baseUrl, forImport: forImport)); + Uri url, + Importer baseImporter, + Uri baseUrl, + Set active, { + bool forImport = false, + }) { + var result = _ignoreErrors( + () => importCache.canonicalize( + url, + baseImporter: baseImporter, + baseUrl: baseUrl, + forImport: forImport, + ), + ); // If an import fails, let the evaluator surface that error rather than // surfacing it here. @@ -318,13 +369,22 @@ class StylesheetGraph { /// error will be produced during compilation. if (active.contains(canonicalUrl)) return null; - var stylesheet = _ignoreErrors(() => importCache - .importCanonical(importer, canonicalUrl, originalUrl: originalUrl)); + var stylesheet = _ignoreErrors( + () => importCache.importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + ), + ); if (stylesheet == null) return null; active.add(canonicalUrl); - var node = StylesheetNode._(stylesheet, importer, canonicalUrl, - _upstreamNodes(stylesheet, importer, canonicalUrl)); + var node = StylesheetNode._( + stylesheet, + importer, + canonicalUrl, + _upstreamNodes(stylesheet, importer, canonicalUrl), + ); active.remove(canonicalUrl); _nodes[canonicalUrl] = node; return node; @@ -380,9 +440,12 @@ class StylesheetNode { Set get downstream => UnmodifiableSetView(_downstream); final _downstream = {}; - StylesheetNode._(this._stylesheet, this.importer, this.canonicalUrl, - _UpstreamNodes allUpstream) - : _upstream = allUpstream.modules, + StylesheetNode._( + this._stylesheet, + this.importer, + this.canonicalUrl, + _UpstreamNodes allUpstream, + ) : _upstream = allUpstream.modules, _upstreamImports = allUpstream.imports { for (var node in upstream.values.followedBy(upstreamImports.values)) { node?._downstream.add(this); @@ -392,8 +455,10 @@ class StylesheetNode { /// Updates [upstream] and [upstreamImports] from [newUpstream] and /// [newUpstreamImports] and adjusts upstream nodes' [downstream] fields /// accordingly. - void _replaceUpstream(Map newUpstream, - Map newUpstreamImports) { + void _replaceUpstream( + Map newUpstream, + Map newUpstreamImports, + ) { var oldUpstream = {...upstream.values, ...upstreamImports.values}.removeNull(); var newUpstreamSet = diff --git a/lib/src/syntax.dart b/lib/src/syntax.dart index ab9fc9557..2594f5587 100644 --- a/lib/src/syntax.dart +++ b/lib/src/syntax.dart @@ -21,7 +21,7 @@ enum Syntax { static Syntax forPath(String path) => switch (p.extension(path)) { '.sass' => Syntax.sass, '.css' => Syntax.css, - _ => Syntax.scss + _ => Syntax.scss, }; /// The name of the syntax. diff --git a/lib/src/util/character.dart b/lib/src/util/character.dart index 614fec45a..1bfd6d5ef 100644 --- a/lib/src/util/character.dart +++ b/lib/src/util/character.dart @@ -122,7 +122,7 @@ int asHex(int character) { <= $9 => character - $0, // ignore: non_constant_relational_pattern_expression <= $F => 10 + character - $A, - _ => 10 + character - $a + _ => 10 + character - $a, }; } @@ -157,7 +157,8 @@ int opposite(int character) => switch (character) { $lbrace => $rbrace, $lbracket => $rbracket, _ => throw ArgumentError( - '"${String.fromCharCode(character)}" isn\'t a brace-like character.') + '"${String.fromCharCode(character)}" isn\'t a brace-like character.', + ), }; /// Returns [character], converted to upper case if it's an ASCII lowercase diff --git a/lib/src/util/limited_map_view.dart b/lib/src/util/limited_map_view.dart index 754e5c9f8..9cfd6d9ce 100644 --- a/lib/src/util/limited_map_view.dart +++ b/lib/src/util/limited_map_view.dart @@ -41,7 +41,7 @@ class LimitedMapView extends UnmodifiableMapBase { LimitedMapView.blocklist(this._map, Set blocklist) : _keys = { for (var key in _map.keys) - if (!blocklist.contains(key)) key + if (!blocklist.contains(key)) key, }; V? operator [](Object? key) => _keys.contains(key) ? _map[key] : null; diff --git a/lib/src/util/multi_span.dart b/lib/src/util/multi_span.dart index 41121042a..173b81691 100644 --- a/lib/src/util/multi_span.dart +++ b/lib/src/util/multi_span.dart @@ -22,9 +22,11 @@ class MultiSpan implements FileSpan { /// The [secondarySpans] map for [SourceSpanExtension.messageMultiple]. final Map secondarySpans; - MultiSpan(FileSpan primary, String primaryLabel, - Map secondarySpans) - : this._(primary, primaryLabel, Map.unmodifiable(secondarySpans)); + MultiSpan( + FileSpan primary, + String primaryLabel, + Map secondarySpans, + ) : this._(primary, primaryLabel, Map.unmodifiable(secondarySpans)); MultiSpan._(this._primary, this.primaryLabel, this.secondarySpans); @@ -42,33 +44,52 @@ class MultiSpan implements FileSpan { MultiSpan subspan(int start, [int? end]) => _withPrimary(_primary.subspan(start, end)); - String highlight({dynamic color}) => - _primary.highlightMultiple(primaryLabel, secondarySpans, - color: color == true || color is String, - primaryColor: color is String ? color : null); + String highlight({dynamic color}) => _primary.highlightMultiple( + primaryLabel, + secondarySpans, + color: color == true || color is String, + primaryColor: color is String ? color : null, + ); - String message(String message, {dynamic color}) => - _primary.messageMultiple(message, primaryLabel, secondarySpans, - color: color == true || color is String, - primaryColor: color is String ? color : null); + String message(String message, {dynamic color}) => _primary.messageMultiple( + message, + primaryLabel, + secondarySpans, + color: color == true || color is String, + primaryColor: color is String ? color : null, + ); String highlightMultiple( - String newLabel, Map additionalSecondarySpans, - {bool color = false, String? primaryColor, String? secondaryColor}) => + String newLabel, + Map additionalSecondarySpans, { + bool color = false, + String? primaryColor, + String? secondaryColor, + }) => _primary.highlightMultiple( - newLabel, {...secondarySpans, ...additionalSecondarySpans}, - color: color, - primaryColor: primaryColor, - secondaryColor: secondaryColor); + newLabel, + {...secondarySpans, ...additionalSecondarySpans}, + color: color, + primaryColor: primaryColor, + secondaryColor: secondaryColor, + ); - String messageMultiple(String message, String newLabel, - Map additionalSecondarySpans, - {bool color = false, String? primaryColor, String? secondaryColor}) => + String messageMultiple( + String message, + String newLabel, + Map additionalSecondarySpans, { + bool color = false, + String? primaryColor, + String? secondaryColor, + }) => _primary.messageMultiple( - message, newLabel, {...secondarySpans, ...additionalSecondarySpans}, - color: color, - primaryColor: primaryColor, - secondaryColor: secondaryColor); + message, + newLabel, + {...secondarySpans, ...additionalSecondarySpans}, + color: color, + primaryColor: primaryColor, + secondaryColor: secondaryColor, + ); /// Returns a copy of `this` with [newPrimary] as its primary span. MultiSpan _withPrimary(FileSpan newPrimary) => diff --git a/lib/src/util/no_source_map_buffer.dart b/lib/src/util/no_source_map_buffer.dart index 0e2bd257d..fa73d001b 100644 --- a/lib/src/util/no_source_map_buffer.dart +++ b/lib/src/util/no_source_map_buffer.dart @@ -29,5 +29,6 @@ class NoSourceMapBuffer implements SourceMapBuffer { throw UnsupportedError("SourceMapBuffer.clear() is not supported."); SingleMapping buildSourceMap({String? prefix}) => throw UnsupportedError( - "NoSourceMapBuffer.buildSourceMap() is not supported."); + "NoSourceMapBuffer.buildSourceMap() is not supported.", + ); } diff --git a/lib/src/util/number.dart b/lib/src/util/number.dart index 8aad45581..e2bf0ecd9 100644 --- a/lib/src/util/number.dart +++ b/lib/src/util/number.dart @@ -118,7 +118,12 @@ double fuzzyAssertRange(double number, int min, int max, [String? name]) { var result = fuzzyCheckRange(number, min, max); if (result != null) return result; throw RangeError.range( - number, min, max, name, "must be between $min and $max"); + number, + min, + max, + name, + "must be between $min and $max", + ); } /// Return [num1] modulo [num2], using Sass's [floored division] modulo diff --git a/lib/src/util/source_map_buffer.dart b/lib/src/util/source_map_buffer.dart index e1026f3c4..8e960ab22 100644 --- a/lib/src/util/source_map_buffer.dart +++ b/lib/src/util/source_map_buffer.dart @@ -166,14 +166,21 @@ class SourceMapBuffer implements StringBuffer { } } - return SingleMapping.fromEntries(_entries.map((entry) => Entry( - entry.source, - SourceLocation(entry.target.offset + prefixLength, + return SingleMapping.fromEntries( + _entries.map( + (entry) => Entry( + entry.source, + SourceLocation( + entry.target.offset + prefixLength, line: entry.target.line + prefixLines, // Only adjust the column for entries that are on the same line as // the last chunk of the prefix. column: entry.target.column + - (entry.target.line == 0 ? prefixColumn : 0)), - entry.identifierName))); + (entry.target.line == 0 ? prefixColumn : 0), + ), + entry.identifierName, + ), + ), + ); } } diff --git a/lib/src/util/string.dart b/lib/src/util/string.dart index 949b9092c..8ce0ccb13 100644 --- a/lib/src/util/string.dart +++ b/lib/src/util/string.dart @@ -28,9 +28,10 @@ extension StringExtension on String { void consumeSurrogatePair(int character) { if (scanner.peekChar(1) case null || int(isLowSurrogate: false)) { scanner.error( - "An individual surrogates can't be represented as a CSS " - "identifier.", - length: 1); + "An individual surrogates can't be represented as a CSS " + "identifier.", + length: 1, + ); } else if (character.isPrivateUseHighSurrogate) { writeEscape(combineSurrogates(scanner.readChar(), scanner.readChar())); } else { @@ -55,7 +56,8 @@ extension StringExtension on String { switch (scanner.peekChar()) { case null: scanner.error( - "The empty string can't be represented as a CSS identifier."); + "The empty string can't be represented as a CSS identifier.", + ); case 0: scanner.error("The U+0000 can't be represented as a CSS identifier."); @@ -65,9 +67,10 @@ extension StringExtension on String { case int(isLowSurrogate: true): scanner.error( - "An individual surrogate can't be represented as a CSS " - "identifier.", - length: 1); + "An individual surrogate can't be represented as a CSS " + "identifier.", + length: 1, + ); case int(isNameStart: true, isPrivateUseBMP: false): buffer.writeCharCode(scanner.readChar()); @@ -91,9 +94,10 @@ extension StringExtension on String { case int(isLowSurrogate: true): scanner.error( - "An individual surrogate can't be represented as a CSS " - "identifier.", - length: 1); + "An individual surrogate can't be represented as a CSS " + "identifier.", + length: 1, + ); case int(isName: true, isPrivateUseBMP: false): buffer.writeCharCode(scanner.readChar()); diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cafc81e51..6aa72f74a 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -56,7 +56,7 @@ String bulletedList(Iterable bullets) => bullets.map((element) { return "${glyph.bullet} ${lines.first}" + switch (lines) { [_, ...var rest] => "\n" + indent(rest.join("\n"), 2), - _ => "" + _ => "", }; }).join("\n"); @@ -78,7 +78,9 @@ String trimAscii(String string, {bool excludeEscape = false}) { return start == null ? "" : string.substring( - start, _lastNonWhitespace(string, excludeEscape: excludeEscape)! + 1); + start, + _lastNonWhitespace(string, excludeEscape: excludeEscape)! + 1, + ); } /// Like [String.trimLeft], but only trims ASCII whitespace. @@ -211,10 +213,11 @@ int mapHash(Map map) => /// By default, the frame's URL is set to `span.sourceUrl`. However, if [url] is /// passed, it's used instead. Frame frameForSpan(SourceSpan span, String member, {Uri? url}) => Frame( - url ?? span.sourceUrl ?? _noSourceUrl, - span.start.line + 1, - span.start.column + 1, - member); + url ?? span.sourceUrl ?? _noSourceUrl, + span.start.line + 1, + span.start.column + 1, + member, + ); /// Returns the variable name (including the leading `$`) from a [span] that /// covers a variable declaration, which includes the variable name as well as @@ -249,7 +252,9 @@ bool equalsIgnoreCase(String? string1, String? string2) { for (var i = 0; i < string1.length; i++) { if (!characterEqualsIgnoreCase( - string1.codeUnitAt(i), string2.codeUnitAt(i))) { + string1.codeUnitAt(i), + string2.codeUnitAt(i), + )) { return false; } } @@ -261,7 +266,9 @@ bool startsWithIgnoreCase(String string, String prefix) { if (string.length < prefix.length) return false; for (var i = 0; i < prefix.length; i++) { if (!characterEqualsIgnoreCase( - string.codeUnitAt(i), prefix.codeUnitAt(i))) { + string.codeUnitAt(i), + prefix.codeUnitAt(i), + )) { return false; } } @@ -283,17 +290,24 @@ void mapInPlace(List list, T function(T element)) { /// If [select] is passed, it's used to check equality between elements in each /// list. If it returns `null`, the elements are considered unequal; otherwise, /// it should return the element to include in the return value. -List longestCommonSubsequence(List list1, List list2, - {T? select(T element1, T element2)?}) { +List longestCommonSubsequence( + List list1, + List list2, { + T? select(T element1, T element2)?, +}) { select ??= (element1, element2) => element1 == element2 ? element1 : null; var lengths = List.generate( - list1.length + 1, (_) => List.filled(list2.length + 1, 0), - growable: false); + list1.length + 1, + (_) => List.filled(list2.length + 1, 0), + growable: false, + ); var selections = List>.generate( - list1.length, (_) => List.filled(list2.length, null), - growable: false); + list1.length, + (_) => List.filled(list2.length, null), + growable: false, + ); for (var i = 0; i < list1.length; i++) { for (var j = 0; j < list2.length; j++) { @@ -335,7 +349,9 @@ void removeFirstWhere(List list, bool test(T value), {void orElse()?}) { /// /// This avoids copying inner maps from [source] if possible. void mapAddAll2( - Map> destination, Map> source) { + Map> destination, + Map> source, +) { source.forEach((key, inner) { if (destination[key] case var innerDestination?) { innerDestination.addAll(inner); @@ -365,7 +381,9 @@ void rotateSlice(List list, int start, int end) { /// Like [Iterable.map] but for an asynchronous [callback]. Future> mapAsync( - Iterable iterable, Future callback(E value)) async => + Iterable iterable, + Future callback(E value), +) async => [for (var element in iterable) await callback(element)]; /// Like [Map.putIfAbsent], but for an asynchronous [ifAbsent]. @@ -373,7 +391,10 @@ Future> mapAsync( /// Note that this is *not* safe to call in parallel on the same map with the /// same key. Future putIfAbsentAsync( - Map map, K key, Future ifAbsent()) async { + Map map, + K key, + Future ifAbsent(), +) async { if (map.containsKey(key)) return map[key] as V; var value = await ifAbsent(); map[key] = value; @@ -381,12 +402,14 @@ Future putIfAbsentAsync( } /// Returns a deep copy of a map that contains maps. -Map> copyMapOfMap(Map> map) => - {for (var (key, child) in map.pairs) key: Map.of(child)}; +Map> copyMapOfMap(Map> map) => { + for (var (key, child) in map.pairs) key: Map.of(child), + }; /// Returns a deep copy of a map that contains lists. -Map> copyMapOfList(Map> map) => - {for (var (key, list) in map.pairs) key: list.toList()}; +Map> copyMapOfList(Map> map) => { + for (var (key, list) in map.pairs) key: list.toList(), + }; /// Consumes an escape sequence from [scanner] and returns the character it /// represents. @@ -410,7 +433,7 @@ int consumeEscapedCharacter(StringScanner scanner) { return switch (value) { 0 || (>= 0xD800 && <= 0xDFFF) || >= maxAllowedCharacter => 0xFFFD, - _ => value + _ => value, }; case _: return scanner.readChar(); @@ -452,15 +475,20 @@ StackTrace? getTrace(Object error) => /// If [requireParens] is `false`, this allows parentheses to be omitted. /// /// Throws a [SassFormatException] if parsing fails. -(String name, ParameterList) parseSignature(String signature, - {bool requireParens = true}) { +(String name, ParameterList) parseSignature( + String signature, { + bool requireParens = true, +}) { try { return ScssParser(signature).parseSignature(requireParens: requireParens); } on SassFormatException catch (error, stackTrace) { throwWithTrace( - SassFormatException( - 'Invalid signature "$signature": ${error.message}', error.span), - error, - stackTrace); + SassFormatException( + 'Invalid signature "$signature": ${error.message}', + error.span, + ), + error, + stackTrace, + ); } } diff --git a/lib/src/value.dart b/lib/src/value.dart index 81f4df27e..915b0e680 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -127,22 +127,24 @@ abstract class Value { var indexValue = sassIndex.assertNumber(name); if (indexValue.hasUnits) { warnForDeprecation( - "\$$name: Passing a number with unit ${indexValue.unitString} is " - "deprecated.\n" - "\n" - "To preserve current behavior: " - "${indexValue.unitSuggestion(name ?? 'index')}\n" - "\n" - "More info: https://sass-lang.com/d/function-units", - Deprecation.functionUnits); + "\$$name: Passing a number with unit ${indexValue.unitString} is " + "deprecated.\n" + "\n" + "To preserve current behavior: " + "${indexValue.unitSuggestion(name ?? 'index')}\n" + "\n" + "More info: https://sass-lang.com/d/function-units", + Deprecation.functionUnits, + ); } var index = indexValue.assertInt(name); if (index == 0) throw SassScriptException("List index may not be 0.", name); if (index.abs() > lengthAsList) { throw SassScriptException( - "Invalid index $sassIndex for a list with $lengthAsList elements.", - name); + "Invalid index $sassIndex for a list with $lengthAsList elements.", + name, + ); } return index < 0 ? lengthAsList + index : index - 1; @@ -247,9 +249,10 @@ abstract class Value { if (_selectorStringOrNull() case var string?) return string; throw SassScriptException( - "$this is not a valid selector: it must be a string,\n" - "a list of strings, or a list of lists of strings.", - name); + "$this is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings.", + name, + ); } /// Converts a `selector-parse()`-style input into a string that can be @@ -291,10 +294,16 @@ abstract class Value { /// Returns a new list containing [contents] that defaults to this value's /// separator and brackets. - SassList withListContents(Iterable contents, - {ListSeparator? separator, bool? brackets}) { - return SassList(contents, separator ?? this.separator, - brackets: brackets ?? hasBrackets); + SassList withListContents( + Iterable contents, { + ListSeparator? separator, + bool? brackets, + }) { + return SassList( + contents, + separator ?? this.separator, + brackets: brackets ?? hasBrackets, + ); } /// The SassScript `=` operation. @@ -351,11 +360,13 @@ abstract class Value { /// @nodoc @internal Value plus(Value other) => switch (other) { - SassString() => - SassString(toCssString() + other.text, quotes: other.hasQuotes), + SassString() => SassString( + toCssString() + other.text, + quotes: other.hasQuotes, + ), SassCalculation() => throw SassScriptException('Undefined operation "$this + $other".'), - _ => SassString(toCssString() + other.toCssString(), quotes: false) + _ => SassString(toCssString() + other.toCssString(), quotes: false), }; /// The SassScript `-` operation. @@ -364,7 +375,10 @@ abstract class Value { @internal Value minus(Value other) => other is SassCalculation ? throw SassScriptException('Undefined operation "$this - $other".') - : SassString("${toCssString()}-${other.toCssString()}", quotes: false); + : SassString( + "${toCssString()}-${other.toCssString()}", + quotes: false, + ); /// The SassScript `/` operation. /// @@ -448,10 +462,10 @@ extension SassApiValue on Value { // TODO(nweiz): colorize this if we're running in an environment where // that works. throwWithTrace( - SassScriptException( - error.toString().replaceFirst("Error: ", ""), name), - error, - stackTrace); + SassScriptException(error.toString().replaceFirst("Error: ", ""), name), + error, + stackTrace, + ); } } @@ -464,8 +478,10 @@ extension SassApiValue on Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - SimpleSelector assertSimpleSelector( - {String? name, bool allowParent = false}) { + SimpleSelector assertSimpleSelector({ + String? name, + bool allowParent = false, + }) { var string = _selectorString(name); try { return SimpleSelector.parse(string, allowParent: allowParent); @@ -473,10 +489,10 @@ extension SassApiValue on Value { // TODO(nweiz): colorize this if we're running in an environment where // that works. throwWithTrace( - SassScriptException( - error.toString().replaceFirst("Error: ", ""), name), - error, - stackTrace); + SassScriptException(error.toString().replaceFirst("Error: ", ""), name), + error, + stackTrace, + ); } } @@ -489,8 +505,10 @@ extension SassApiValue on Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - CompoundSelector assertCompoundSelector( - {String? name, bool allowParent = false}) { + CompoundSelector assertCompoundSelector({ + String? name, + bool allowParent = false, + }) { var string = _selectorString(name); try { return CompoundSelector.parse(string, allowParent: allowParent); @@ -498,10 +516,10 @@ extension SassApiValue on Value { // TODO(nweiz): colorize this if we're running in an environment where // that works. throwWithTrace( - SassScriptException( - error.toString().replaceFirst("Error: ", ""), name), - error, - stackTrace); + SassScriptException(error.toString().replaceFirst("Error: ", ""), name), + error, + stackTrace, + ); } } @@ -514,8 +532,10 @@ extension SassApiValue on Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - ComplexSelector assertComplexSelector( - {String? name, bool allowParent = false}) { + ComplexSelector assertComplexSelector({ + String? name, + bool allowParent = false, + }) { var string = _selectorString(name); try { return ComplexSelector.parse(string, allowParent: allowParent); @@ -523,10 +543,10 @@ extension SassApiValue on Value { // TODO(nweiz): colorize this if we're running in an environment where // that works. throwWithTrace( - SassScriptException( - error.toString().replaceFirst("Error: ", ""), name), - error, - stackTrace); + SassScriptException(error.toString().replaceFirst("Error: ", ""), name), + error, + stackTrace, + ); } } } diff --git a/lib/src/value/calculation.dart b/lib/src/value/calculation.dart index 14e862762..89339ab3a 100644 --- a/lib/src/value/calculation.dart +++ b/lib/src/value/calculation.dart @@ -46,7 +46,9 @@ final class SassCalculation extends Value { /// that will not be simplified. @internal static SassCalculation unsimplified( - String name, Iterable arguments) => + String name, + Iterable arguments, + ) => SassCalculation._(name, List.unmodifiable(arguments)); /// Creates a `calc()` calculation with the given [argument]. @@ -60,8 +62,10 @@ final class SassCalculation extends Value { static Value calc(Object argument) => switch (_simplify(argument)) { SassNumber value => value, SassCalculation value => value, - var simplified => - SassCalculation._("calc", List.unmodifiable([simplified])) + var simplified => SassCalculation._( + "calc", + List.unmodifiable([simplified]), + ), }; /// Creates a `min()` calculation with the given [arguments]. @@ -152,13 +156,18 @@ final class SassCalculation extends Value { if (number is! SassNumber || !number.hasCompatibleUnits(first)) { return SassCalculation._("hypot", args); } - var value = - number.convertValueToMatch(first, "numbers[${i + 1}]", "numbers[1]"); + var value = number.convertValueToMatch( + first, + "numbers[${i + 1}]", + "numbers[1]", + ); subtotal += value * value; } - return SassNumber.withUnits(math.sqrt(subtotal), - numeratorUnits: first.numeratorUnits, - denominatorUnits: first.denominatorUnits); + return SassNumber.withUnits( + math.sqrt(subtotal), + numeratorUnits: first.numeratorUnits, + denominatorUnits: first.denominatorUnits, + ); } /// Creates a `sqrt()` calculation with the given [argument]. @@ -251,13 +260,14 @@ final class SassCalculation extends Value { if (argument is! SassNumber) return SassCalculation._("abs", [argument]); if (argument.hasUnit("%")) { warnForDeprecation( - "Passing percentage units to the global abs() function is deprecated.\n" - "In the future, this will emit a CSS abs() function to be resolved by the browser.\n" - "To preserve current behavior: math.abs($argument)" - "\n" - "To emit a CSS abs() now: abs(#{$argument})\n" - "More info: https://sass-lang.com/d/abs-percent", - Deprecation.absPercent); + "Passing percentage units to the global abs() function is deprecated.\n" + "In the future, this will emit a CSS abs() function to be resolved by the browser.\n" + "To preserve current behavior: math.abs($argument)" + "\n" + "To emit a CSS abs() now: abs(#{$argument})\n" + "More info: https://sass-lang.com/d/abs-percent", + Deprecation.absPercent, + ); } return number_lib.abs(argument); } @@ -291,8 +301,9 @@ final class SassCalculation extends Value { argument = _simplify(argument); return switch (argument) { SassNumber(value: double(isNaN: true) || 0) => argument, - SassNumber arg when !arg.hasUnit('%') => - SassNumber(arg.value.sign).coerceToMatch(argument), + SassNumber arg when !arg.hasUnit('%') => SassNumber( + arg.value.sign, + ).coerceToMatch(argument), _ => SassCalculation._("sign", [argument]), }; } @@ -327,8 +338,11 @@ final class SassCalculation extends Value { return value; } - var args = List.unmodifiable( - [min, if (value != null) value, if (max != null) max]); + var args = List.unmodifiable([ + min, + if (value != null) value, + if (max != null) max, + ]); _verifyCompatibleNumbers(args); _verifyLength(args, 3); return SassCalculation._("clamp", args); @@ -482,10 +496,19 @@ final class SassCalculation extends Value { /// /// This may be passed fewer than two arguments, but only if one of the /// arguments is an unquoted `var()` string. - static Value round(Object strategyOrNumber, - [Object? numberOrStep, Object? step]) => - roundInternal(strategyOrNumber, numberOrStep, step, - span: null, inLegacySassFunction: null, warn: null); + static Value round( + Object strategyOrNumber, [ + Object? numberOrStep, + Object? step, + ]) => + roundInternal( + strategyOrNumber, + numberOrStep, + step, + span: null, + inLegacySassFunction: null, + warn: null, + ); /// Like [round], but with the internal-only [inLegacySassFunction] and /// [warn] parameters. @@ -502,28 +525,31 @@ final class SassCalculation extends Value { /// @nodoc @internal static Value roundInternal( - Object strategyOrNumber, Object? numberOrStep, Object? step, - {required FileSpan? span, - required String? inLegacySassFunction, - required void Function(String message, [Deprecation? deprecation])? - warn}) { + Object strategyOrNumber, + Object? numberOrStep, + Object? step, { + required FileSpan? span, + required String? inLegacySassFunction, + required void Function(String message, [Deprecation? deprecation])? warn, + }) { switch (( _simplify(strategyOrNumber), numberOrStep.andThen(_simplify), - step.andThen(_simplify) + step.andThen(_simplify), )) { case (SassNumber(hasUnits: false) && var number, null, null): return SassNumber(number.value.round()); case (SassNumber number, null, null) when inLegacySassFunction != null: warn!( - "In future versions of Sass, round() will be interpreted as a CSS " - "round() calculation. This requires an explicit modulus when " - "rounding numbers with units. If you want to use the Sass " - "function, call math.round() instead.\n" - "\n" - "See https://sass-lang.com/d/import", - Deprecation.globalBuiltin); + "In future versions of Sass, round() will be interpreted as a CSS " + "round() calculation. This requires an explicit modulus when " + "rounding numbers with units. If you want to use the Sass " + "function, call math.round() instead.\n" + "\n" + "See https://sass-lang.com/d/import", + Deprecation.globalBuiltin, + ); return _matchUnits(number.value.round().toDouble(), number); case (SassNumber number, SassNumber step, null) @@ -539,7 +565,7 @@ final class SassCalculation extends Value { SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && var strategy, SassNumber number, - SassNumber step + SassNumber step, ) when !number.hasCompatibleUnits(step): _verifyCompatibleNumbers([number, step]); @@ -549,7 +575,7 @@ final class SassCalculation extends Value { SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && var strategy, SassNumber number, - SassNumber step + SassNumber step, ): _verifyCompatibleNumbers([number, step]); return _roundWithStep(strategy.text, number, step); @@ -558,24 +584,25 @@ final class SassCalculation extends Value { SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && var strategy, SassString rest, - null + null, ): return SassCalculation._("round", [strategy, rest]); case ( SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'), _?, - null + null, ): throw SassScriptException("If strategy is not null, step is required."); case ( SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'), null, - null + null, ): throw SassScriptException( - "Number to round and step arguments are required."); + "Number to round and step arguments are required.", + ); case (var number, null, null): return SassCalculation._("round", [number]); @@ -588,13 +615,14 @@ final class SassCalculation extends Value { SassString(isVar: true)) && var strategy, var number?, - var step? + var step?, ): return SassCalculation._("round", [strategy, number, step]); case (_, _?, _?): throw SassScriptException( - "$strategyOrNumber must be either nearest, up, down or to-zero."); + "$strategyOrNumber must be either nearest, up, down or to-zero.", + ); case (_, null, _?): // TODO(pamelalozano): Get rid of this case once dart-lang/sdk#52908 is solved. @@ -628,9 +656,18 @@ final class SassCalculation extends Value { /// Each of [left] and [right] must be either a [SassNumber], a /// [SassCalculation], an unquoted [SassString], or a [CalculationOperation]. static Object operate( - CalculationOperator operator, Object left, Object right) => - operateInternal(operator, left, right, - inLegacySassFunction: null, simplify: true, warn: null); + CalculationOperator operator, + Object left, + Object right, + ) => + operateInternal( + operator, + left, + right, + inLegacySassFunction: null, + simplify: true, + warn: null, + ); /// Like [operate], but with the internal-only [inLegacySassFunction] and /// [warn] parameters. @@ -647,11 +684,13 @@ final class SassCalculation extends Value { /// @nodoc @internal static Object operateInternal( - CalculationOperator operator, Object left, Object right, - {required String? inLegacySassFunction, - required bool simplify, - required void Function(String message, [Deprecation? deprecation])? - warn}) { + CalculationOperator operator, + Object left, + Object right, { + required String? inLegacySassFunction, + required bool simplify, + required void Function(String message, [Deprecation? deprecation])? warn, + }) { if (!simplify) return CalculationOperation._(operator, left, right); left = _simplify(left); right = _simplify(right); @@ -663,14 +702,15 @@ final class SassCalculation extends Value { inLegacySassFunction != null && left.isComparableTo(right)) { warn!( - "In future versions of Sass, $inLegacySassFunction() will be " - "interpreted as the CSS $inLegacySassFunction() calculation. " - "This doesn't allow unitless numbers to be mixed with numbers " - "with units. If you want to use the Sass function, call " - "math.$inLegacySassFunction() instead.\n" - "\n" - "See https://sass-lang.com/d/import", - Deprecation.globalBuiltin); + "In future versions of Sass, $inLegacySassFunction() will be " + "interpreted as the CSS $inLegacySassFunction() calculation. " + "This doesn't allow unitless numbers to be mixed with numbers " + "with units. If you want to use the Sass function, call " + "math.$inLegacySassFunction() instead.\n" + "\n" + "See https://sass-lang.com/d/import", + Deprecation.globalBuiltin, + ); compatible = true; } if (compatible) { @@ -705,17 +745,23 @@ final class SassCalculation extends Value { // Returns [value] coerced to [number]'s units. static SassNumber _matchUnits(double value, SassNumber number) => - SassNumber.withUnits(value, - numeratorUnits: number.numeratorUnits, - denominatorUnits: number.denominatorUnits); + SassNumber.withUnits( + value, + numeratorUnits: number.numeratorUnits, + denominatorUnits: number.denominatorUnits, + ); /// Returns a rounded [number] based on a selected rounding [strategy], /// to the nearest integer multiple of [step]. static SassNumber _roundWithStep( - String strategy, SassNumber number, SassNumber step) { + String strategy, + SassNumber number, + SassNumber step, + ) { if (!{'nearest', 'up', 'down', 'to-zero'}.contains(strategy)) { throw ArgumentError( - "$strategy must be either nearest, up, down or to-zero."); + "$strategy must be either nearest, up, down or to-zero.", + ); } if (number.value.isInfinite && step.value.isInfinite || @@ -735,7 +781,7 @@ final class SassCalculation extends Value { ('up', _) => _matchUnits(-0.0, number), ('down', < 0) => _matchUnits(-double.infinity, number), ('down', _) => _matchUnits(0, number), - (_, _) => throw UnsupportedError("Invalid argument: $strategy.") + (_, _) => throw UnsupportedError("Invalid argument: $strategy."), }; } @@ -743,27 +789,32 @@ final class SassCalculation extends Value { return switch (strategy) { 'nearest' => _matchUnits( (number.value / stepWithNumberUnit).round() * stepWithNumberUnit, - number), + number, + ), 'up' => _matchUnits( (step.value < 0 ? (number.value / stepWithNumberUnit).floor() : (number.value / stepWithNumberUnit).ceil()) * stepWithNumberUnit, - number), + number, + ), 'down' => _matchUnits( (step.value < 0 ? (number.value / stepWithNumberUnit).ceil() : (number.value / stepWithNumberUnit).floor()) * stepWithNumberUnit, - number), + number, + ), 'to-zero' => number.value < 0 ? _matchUnits( (number.value / stepWithNumberUnit).ceil() * stepWithNumberUnit, - number) + number, + ) : _matchUnits( (number.value / stepWithNumberUnit).floor() * stepWithNumberUnit, - number), - _ => _matchUnits(double.nan, number) + number, + ), + _ => _matchUnits(double.nan, number), }; } @@ -778,10 +829,11 @@ final class SassCalculation extends Value { SassString('(${arg.value})', quotes: false), SassString(hasQuotes: false) => arg, SassString() => throw SassScriptException( - "Quoted string $arg can't be used in a calculation."), + "Quoted string $arg can't be used in a calculation.", + ), SassCalculation( name: 'calc', - arguments: [SassString(hasQuotes: false, :var text)] + arguments: [SassString(hasQuotes: false, :var text)], ) when _needsParentheses(text) => SassString('($text)', quotes: false), @@ -789,7 +841,7 @@ final class SassCalculation extends Value { SassCalculation() => arg, Value() => throw SassScriptException( "Value $arg can't be used in a calculation."), - _ => throw ArgumentError("Unexpected calculation argument $arg.") + _ => throw ArgumentError("Unexpected calculation argument $arg."), }; /// Returns whether [text] needs parentheses if it's the contents of a @@ -836,7 +888,8 @@ final class SassCalculation extends Value { for (var arg in args) { if (arg case SassNumber(hasComplexUnits: true)) { throw SassScriptException( - "Number $arg isn't compatible with CSS calculations."); + "Number $arg isn't compatible with CSS calculations.", + ); } } @@ -861,8 +914,9 @@ final class SassCalculation extends Value { return; } throw SassScriptException( - "$expectedLength arguments required, but only ${args.length} " - "${pluralize('was', args.length, plural: 'were')} passed."); + "$expectedLength arguments required, but only ${args.length} " + "${pluralize('was', args.length, plural: 'were')} passed.", + ); } /// Returns a [Callable] named [name] that calls a single argument @@ -870,8 +924,11 @@ final class SassCalculation extends Value { /// /// If [forbidUnits] is `true` it will throw an error if [argument] has units. static Value _singleArgument( - String name, Object argument, SassNumber mathFunc(SassNumber value), - {bool forbidUnits = false}) { + String name, + Object argument, + SassNumber mathFunc(SassNumber value), { + bool forbidUnits = false, + }) { argument = _simplify(argument); if (argument is! SassNumber) { return SassCalculation._(name, [argument]); @@ -952,8 +1009,10 @@ final class CalculationOperation { int get hashCode => operator.hashCode ^ left.hashCode ^ right.hashCode; String toString() { - var parenthesized = - serializeValue(SassCalculation._("", [this]), inspect: true); + var parenthesized = serializeValue( + SassCalculation._("", [this]), + inspect: true, + ); return parenthesized.substring(1, parenthesized.length - 1); } } diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index d68f465a0..ea839ac2f 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -72,7 +72,7 @@ class SassColor extends Value { bool get isChannel0Powerless => switch (space) { ColorSpace.hsl => fuzzyEquals(channel1, 0), ColorSpace.hwb => fuzzyGreaterThanOrEquals(channel1 + channel2, 100), - _ => false + _ => false, }; /// This color's first channel. @@ -138,7 +138,7 @@ class SassColor extends Value { @internal bool get isChannel2Powerless => switch (space) { ColorSpace.lch || ColorSpace.oklch => fuzzyEquals(channel1, 0), - _ => false + _ => false, }; /// This color's third channel. @@ -208,12 +208,15 @@ class SassColor extends Value { } /// Returns whether [value] is in-gamut for the given [channel]. - bool _isChannelInGamut(double value, ColorChannel channel) => + bool _isChannelInGamut( + double value, + ColorChannel channel, + ) => switch (channel) { LinearChannel(:var min, :var max) => fuzzyLessThanOrEquals(value, max) && fuzzyGreaterThanOrEquals(value, min), - _ => true + _ => true, }; /// Whether this color has any missing channels. @@ -283,10 +286,21 @@ class SassColor extends Value { /// /// @nodoc @internal - factory SassColor.rgbInternal(num? red, num? green, num? blue, - [num? alpha = 1, ColorFormat? format]) => - SassColor._forSpace(ColorSpace.rgb, red?.toDouble(), green?.toDouble(), - blue?.toDouble(), alpha?.toDouble(), format); + factory SassColor.rgbInternal( + num? red, + num? green, + num? blue, [ + num? alpha = 1, + ColorFormat? format, + ]) => + SassColor._forSpace( + ColorSpace.rgb, + red?.toDouble(), + green?.toDouble(), + blue?.toDouble(), + alpha?.toDouble(), + format, + ); /// Creates a color in [ColorSpace.hsl]. /// @@ -297,10 +311,19 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.hsl(num? hue, num? saturation, num? lightness, - [num? alpha = 1]) => - SassColor.forSpaceInternal(ColorSpace.hsl, hue?.toDouble(), - saturation?.toDouble(), lightness?.toDouble(), alpha?.toDouble()); + factory SassColor.hsl( + num? hue, + num? saturation, + num? lightness, [ + num? alpha = 1, + ]) => + SassColor.forSpaceInternal( + ColorSpace.hsl, + hue?.toDouble(), + saturation?.toDouble(), + lightness?.toDouble(), + alpha?.toDouble(), + ); /// Creates a color in [ColorSpace.hwb]. /// @@ -311,10 +334,19 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.hwb(num? hue, num? whiteness, num? blackness, - [num? alpha = 1]) => - SassColor.forSpaceInternal(ColorSpace.hwb, hue?.toDouble(), - whiteness?.toDouble(), blackness?.toDouble(), alpha?.toDouble()); + factory SassColor.hwb( + num? hue, + num? whiteness, + num? blackness, [ + num? alpha = 1, + ]) => + SassColor.forSpaceInternal( + ColorSpace.hwb, + hue?.toDouble(), + whiteness?.toDouble(), + blackness?.toDouble(), + alpha?.toDouble(), + ); /// Creates a color in [ColorSpace.srgb]. /// @@ -325,8 +357,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.srgb(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.srgb( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.srgb, red, green, blue, alpha); /// Creates a color in [ColorSpace.srgbLinear]. @@ -338,8 +374,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.srgbLinear(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.srgbLinear( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.srgbLinear, red, green, blue, alpha); /// Creates a color in [ColorSpace.displayP3]. @@ -351,8 +391,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.displayP3(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.displayP3( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.displayP3, red, green, blue, alpha); /// Creates a color in [ColorSpace.a98Rgb]. @@ -364,8 +408,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.a98Rgb(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.a98Rgb( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.a98Rgb, red, green, blue, alpha); /// Creates a color in [ColorSpace.prophotoRgb]. @@ -377,8 +425,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.prophotoRgb(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.prophotoRgb( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.prophotoRgb, red, green, blue, alpha); /// Creates a color in [ColorSpace.rec2020]. @@ -390,8 +442,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.rec2020(double? red, double? green, double? blue, - [double? alpha = 1]) => + factory SassColor.rec2020( + double? red, + double? green, + double? blue, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.rec2020, red, green, blue, alpha); /// Creates a color in [ColorSpace.xyzD50]. @@ -403,8 +459,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.xyzD50(double? x, double? y, double? z, - [double? alpha = 1]) => + factory SassColor.xyzD50( + double? x, + double? y, + double? z, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.xyzD50, x, y, z, alpha); /// Creates a color in [ColorSpace.xyzD65]. @@ -416,8 +476,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.xyzD65(double? x, double? y, double? z, - [double? alpha = 1]) => + factory SassColor.xyzD65( + double? x, + double? y, + double? z, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.xyzD65, x, y, z, alpha); /// Creates a color in [ColorSpace.lab]. @@ -429,8 +493,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.lab(double? lightness, double? a, double? b, - [double? alpha = 1]) => + factory SassColor.lab( + double? lightness, + double? a, + double? b, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.lab, lightness, a, b, alpha); /// Creates a color in [ColorSpace.lch]. @@ -442,8 +510,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.lch(double? lightness, double? chroma, double? hue, - [double? alpha = 1]) => + factory SassColor.lch( + double? lightness, + double? chroma, + double? hue, [ + double? alpha = 1, + ]) => SassColor.forSpaceInternal(ColorSpace.lch, lightness, chroma, hue, alpha); /// Creates a color in [ColorSpace.oklab]. @@ -455,8 +527,12 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.oklab(double? lightness, double? a, double? b, - [double? alpha = 1]) => + factory SassColor.oklab( + double? lightness, + double? a, + double? b, [ + double? alpha = 1, + ]) => SassColor._forSpace(ColorSpace.oklab, lightness, a, b, alpha); /// Creates a color in [ColorSpace.oklch]. @@ -468,10 +544,19 @@ class SassColor extends Value { /// [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1`. - factory SassColor.oklch(double? lightness, double? chroma, double? hue, - [double? alpha = 1]) => + factory SassColor.oklch( + double? lightness, + double? chroma, + double? hue, [ + double? alpha = 1, + ]) => SassColor.forSpaceInternal( - ColorSpace.oklch, lightness, chroma, hue, alpha); + ColorSpace.oklch, + lightness, + chroma, + hue, + alpha, + ); /// Creates a color in the color space named [space]. /// @@ -483,47 +568,79 @@ class SassColor extends Value { /// /// Throws a [RangeError] if [alpha] isn't between `0` and `1` or if /// [channels] is the wrong length for [space]. - factory SassColor.forSpace(ColorSpace space, List channels, - [double? alpha = 1]) => + factory SassColor.forSpace( + ColorSpace space, + List channels, [ + double? alpha = 1, + ]) => channels.length == space.channels.length ? SassColor.forSpaceInternal( - space, channels[0], channels[1], channels[2], alpha) - : throw RangeError.value(channels.length, "channels.length", - 'must be exactly ${space.channels.length} for color space "$space"'); + space, + channels[0], + channels[1], + channels[2], + alpha, + ) + : throw RangeError.value( + channels.length, + "channels.length", + 'must be exactly ${space.channels.length} for color space "$space"', + ); /// Like [forSpace], but takes three channels explicitly rather than wrapping /// and unwrapping them in an array. /// /// @nodoc - factory SassColor.forSpaceInternal(ColorSpace space, double? channel0, - double? channel1, double? channel2, - [double? alpha = 1]) => + factory SassColor.forSpaceInternal( + ColorSpace space, + double? channel0, + double? channel1, + double? channel2, [ + double? alpha = 1, + ]) => switch (space) { ColorSpace.hsl => SassColor._forSpace( space, - _normalizeHue(channel0, - invert: channel1 != null && fuzzyLessThan(channel1, 0)), + _normalizeHue( + channel0, + invert: channel1 != null && fuzzyLessThan(channel1, 0), + ), channel1?.abs(), channel2, - alpha), - ColorSpace.hwb => SassColor._forSpace(space, - _normalizeHue(channel0, invert: false), channel1, channel2, alpha), + alpha, + ), + ColorSpace.hwb => SassColor._forSpace( + space, + _normalizeHue(channel0, invert: false), + channel1, + channel2, + alpha, + ), ColorSpace.lch || ColorSpace.oklch => SassColor._forSpace( space, channel0, channel1?.abs(), - _normalizeHue(channel2, - invert: channel1 != null && fuzzyLessThan(channel1, 0)), - alpha), - _ => SassColor._forSpace(space, channel0, channel1, channel2, alpha) + _normalizeHue( + channel2, + invert: channel1 != null && fuzzyLessThan(channel1, 0), + ), + alpha, + ), + _ => SassColor._forSpace(space, channel0, channel1, channel2, alpha), }; /// Like [forSpaceInternal], but doesn't do _any_ pre-processing of any /// channels. - SassColor._forSpace(this._space, this.channel0OrNull, this.channel1OrNull, - this.channel2OrNull, double? alpha, [this.format]) - : alphaOrNull = - alpha.andThen((alpha) => fuzzyAssertRange(alpha, 0, 1, "alpha")) { + SassColor._forSpace( + this._space, + this.channel0OrNull, + this.channel1OrNull, + this.channel2OrNull, + double? alpha, [ + this.format, + ]) : alphaOrNull = alpha.andThen( + (alpha) => fuzzyAssertRange(alpha, 0, 1, "alpha"), + ) { assert(format == null || _space == ColorSpace.rgb); assert(space != ColorSpace.lms); } @@ -552,8 +669,9 @@ class SassColor extends Value { void assertLegacy([String? name]) { if (isLegacy) return; throw SassScriptException( - 'Expected $this to be in the legacy RGB, HSL, or HWB color space.', - name); + 'Expected $this to be in the legacy RGB, HSL, or HWB color space.', + name, + ); } /// Returns the value of the given [channel] in this color, or throws a @@ -570,7 +688,9 @@ class SassColor extends Value { if (channel == 'alpha') return alpha; throw SassScriptException( - "Color $this doesn't have a channel named \"$channel\".", channelName); + "Color $this doesn't have a channel named \"$channel\".", + channelName, + ); } /// Returns whether the given [channel] in this color is [missing]. @@ -580,8 +700,11 @@ class SassColor extends Value { /// If this came from a function argument, [colorName] is the argument name /// for this color and [channelName] is the argument name for [channel] /// (without the `$`). These are used for error reporting. - bool isChannelMissing(String channel, - {String? colorName, String? channelName}) { + bool isChannelMissing( + String channel, { + String? colorName, + String? channelName, + }) { var channels = space.channels; if (channel == channels[0].name) return isChannel0Missing; if (channel == channels[1].name) return isChannel1Missing; @@ -589,7 +712,9 @@ class SassColor extends Value { if (channel == 'alpha') return isAlphaMissing; throw SassScriptException( - "Color $this doesn't have a channel named \"$channel\".", channelName); + "Color $this doesn't have a channel named \"$channel\".", + channelName, + ); } /// Returns whether the given [channel] in this color is [powerless]. @@ -599,8 +724,11 @@ class SassColor extends Value { /// If this came from a function argument, [colorName] is the argument name /// for this color and [channelName] is the argument name for [channel] /// (without the `$`). These are used for error reporting. - bool isChannelPowerless(String channel, - {String? colorName, String? channelName}) { + bool isChannelPowerless( + String channel, { + String? colorName, + String? channelName, + }) { var channels = space.channels; if (channel == channels[0].name) return isChannel0Powerless; if (channel == channels[1].name) return isChannel1Powerless; @@ -608,7 +736,9 @@ class SassColor extends Value { if (channel == 'alpha') return false; throw SassScriptException( - "Color $this doesn't have a channel named \"$channel\".", channelName); + "Color $this doesn't have a channel named \"$channel\".", + channelName, + ); } /// If this is a legacy color, converts it to the given [space] and then @@ -618,8 +748,9 @@ class SassColor extends Value { double _legacyChannel(ColorSpace space, String channel) { if (!isLegacy) { throw SassScriptException( - "color.$channel() is only supported for legacy colors. Please use " - "color.channel() instead with an explicit \$space argument."); + "color.$channel() is only supported for legacy colors. Please use " + "color.channel() instead with an explicit \$space argument.", + ); } return toSpace(space).channel(channel); @@ -633,17 +764,26 @@ class SassColor extends Value { SassColor toSpace(ColorSpace space, {bool legacyMissing = true}) { if (this.space == space) return this; - var converted = this - .space - .convert(space, channel0OrNull, channel1OrNull, channel2OrNull, alpha); + var converted = this.space.convert( + space, + channel0OrNull, + channel1OrNull, + channel2OrNull, + alpha, + ); return !legacyMissing && converted.isLegacy && (converted.isChannel0Missing || converted.isChannel1Missing || converted.isChannel2Missing || converted.isAlphaMissing) - ? SassColor.forSpaceInternal(converted.space, converted.channel0, - converted.channel1, converted.channel2, converted.alpha) + ? SassColor.forSpaceInternal( + converted.space, + converted.channel0, + converted.channel1, + converted.channel2, + converted.alpha, + ) : converted; } @@ -656,15 +796,17 @@ class SassColor extends Value { SassColor changeRgb({int? red, int? green, int? blue, num? alpha}) { if (!isLegacy) { throw SassScriptException( - "color.changeRgb() is only supported for legacy colors. Please use " - "color.changeChannels() instead with an explicit \$space argument."); + "color.changeRgb() is only supported for legacy colors. Please use " + "color.changeChannels() instead with an explicit \$space argument.", + ); } return SassColor.rgb( - red?.toDouble() ?? channel('red'), - green?.toDouble() ?? channel('green'), - blue?.toDouble() ?? channel('blue'), - alpha?.toDouble() ?? this.alpha); + red?.toDouble() ?? channel('red'), + green?.toDouble() ?? channel('green'), + blue?.toDouble() ?? channel('blue'), + alpha?.toDouble() ?? this.alpha, + ); } /// Changes one or more of this color's HSL channels and returns the result. @@ -672,16 +814,17 @@ class SassColor extends Value { SassColor changeHsl({num? hue, num? saturation, num? lightness, num? alpha}) { if (!isLegacy) { throw SassScriptException( - "color.changeHsl() is only supported for legacy colors. Please use " - "color.changeChannels() instead with an explicit \$space argument."); + "color.changeHsl() is only supported for legacy colors. Please use " + "color.changeChannels() instead with an explicit \$space argument.", + ); } return SassColor.hsl( - hue?.toDouble() ?? this.hue, - saturation?.toDouble() ?? this.saturation, - lightness?.toDouble() ?? this.lightness, - alpha?.toDouble() ?? this.alpha) - .toSpace(space); + hue?.toDouble() ?? this.hue, + saturation?.toDouble() ?? this.saturation, + lightness?.toDouble() ?? this.lightness, + alpha?.toDouble() ?? this.alpha, + ).toSpace(space); } /// Changes one or more of this color's HWB channels and returns the result. @@ -689,21 +832,27 @@ class SassColor extends Value { SassColor changeHwb({num? hue, num? whiteness, num? blackness, num? alpha}) { if (!isLegacy) { throw SassScriptException( - "color.changeHsl() is only supported for legacy colors. Please use " - "color.changeChannels() instead with an explicit \$space argument."); + "color.changeHsl() is only supported for legacy colors. Please use " + "color.changeChannels() instead with an explicit \$space argument.", + ); } return SassColor.hwb( - hue?.toDouble() ?? this.hue, - whiteness?.toDouble() ?? this.whiteness, - blackness?.toDouble() ?? this.blackness, - alpha?.toDouble() ?? this.alpha + 0.0) - .toSpace(space); + hue?.toDouble() ?? this.hue, + whiteness?.toDouble() ?? this.whiteness, + blackness?.toDouble() ?? this.blackness, + alpha?.toDouble() ?? this.alpha + 0.0, + ).toSpace(space); } /// Returns a new copy of this color with the alpha channel set to [alpha]. SassColor changeAlpha(num alpha) => SassColor.forSpaceInternal( - space, channel0, channel1, channel2, alpha.toDouble()); + space, + channel0, + channel1, + channel2, + alpha.toDouble(), + ); /// Changes one or more of this color's channels and returns the result. /// @@ -718,14 +867,17 @@ class SassColor extends Value { /// /// If this color came from a function argument, [colorName] is the argument /// name (without the `$`). This is used for error reporting. - SassColor changeChannels(Map newValues, - {ColorSpace? space, String? colorName}) { + SassColor changeChannels( + Map newValues, { + ColorSpace? space, + String? colorName, + }) { if (newValues.isEmpty) return this; if (space != null && space != this.space) { - return toSpace(space) - .changeChannels(newValues, colorName: colorName) - .toSpace(this.space); + return toSpace( + space, + ).changeChannels(newValues, colorName: colorName).toSpace(this.space); } double? new0; @@ -737,9 +889,10 @@ class SassColor extends Value { void setChannel0(double value) { if (new0 != null) { throw SassScriptException( - 'Multiple values supplied for "${channels[0]}": $new0 and ' - '$value.', - colorName); + 'Multiple values supplied for "${channels[0]}": $new0 and ' + '$value.', + colorName, + ); } new0 = value; } @@ -747,9 +900,10 @@ class SassColor extends Value { void setChannel1(double value) { if (new1 != null) { throw SassScriptException( - 'Multiple values supplied for "${channels[1]}": $new1 and ' - '$value.', - colorName); + 'Multiple values supplied for "${channels[1]}": $new1 and ' + '$value.', + colorName, + ); } new1 = value; } @@ -757,9 +911,10 @@ class SassColor extends Value { void setChannel2(double value) { if (new2 != null) { throw SassScriptException( - 'Multiple values supplied for "${channels[2]}": $new2 and ' - '$value.', - colorName); + 'Multiple values supplied for "${channels[2]}": $new2 and ' + '$value.', + colorName, + ); } new2 = value; } @@ -775,20 +930,27 @@ class SassColor extends Value { } else if (channel == 'alpha') { if (alpha != null) { throw SassScriptException( - 'Multiple values supplied for "alpha": $alpha and ' - '${entry.value}.', - colorName); + 'Multiple values supplied for "alpha": $alpha and ' + '${entry.value}.', + colorName, + ); } alpha = entry.value; } else { throw SassScriptException( - "Color $this doesn't have a channel named \"$channel\".", - colorName); + "Color $this doesn't have a channel named \"$channel\".", + colorName, + ); } } - return SassColor.forSpaceInternal(this.space, new0 ?? channel0OrNull, - new1 ?? channel1OrNull, new2 ?? channel2OrNull, alpha ?? alphaOrNull); + return SassColor.forSpaceInternal( + this.space, + new0 ?? channel0OrNull, + new1 ?? channel1OrNull, + new2 ?? channel2OrNull, + alpha ?? alphaOrNull, + ); } /// Returns a color partway between `this` and [other] according to [method], @@ -801,8 +963,12 @@ class SassColor extends Value { /// /// If [legacyMissing] is false, this will convert missing channels in legacy /// color spaces to zero if a conversion occurs. - SassColor interpolate(SassColor other, InterpolationMethod method, - {double? weight, bool legacyMissing = true}) { + SassColor interpolate( + SassColor other, + InterpolationMethod method, { + double? weight, + bool legacyMissing = true, + }) { weight ??= 0.5; if (fuzzyEquals(weight, 0)) return other; @@ -859,7 +1025,8 @@ class SassColor extends Value { : _interpolateHues(channel1_0, channel2_0, method.hue!, weight), mixed1, mixed2, - mixedAlpha), + mixedAlpha, + ), ColorSpace.lch || ColorSpace.oklch => SassColor.forSpaceInternal( method.space, mixed0, @@ -867,9 +1034,15 @@ class SassColor extends Value { missing1_2 && missing2_2 ? null : _interpolateHues(channel1_2, channel2_2, method.hue!, weight), - mixedAlpha), + mixedAlpha, + ), _ => SassColor.forSpaceInternal( - method.space, mixed0, mixed1, mixed2, mixedAlpha) + method.space, + mixed0, + mixed1, + mixed2, + mixedAlpha, + ), } .toSpace(space, legacyMissing: legacyMissing); } @@ -881,13 +1054,17 @@ class SassColor extends Value { /// This includes channels that are analogous to missing channels in /// [original]. bool _isAnalogousChannelMissing( - SassColor original, SassColor output, int outputChannelIndex) { + SassColor original, + SassColor output, + int outputChannelIndex, + ) { if (output.channelsOrNull[outputChannelIndex] == null) return true; if (identical(original, output)) return false; var outputChannel = output.space.channels[outputChannelIndex]; - var originalChannel = - original.space.channels.firstWhereOrNull(outputChannel.isAnalogous); + var originalChannel = original.space.channels.firstWhereOrNull( + outputChannel.isAnalogous, + ); if (originalChannel == null) return false; return original.isChannelMissing(originalChannel.name); @@ -898,7 +1075,11 @@ class SassColor extends Value { /// The [weight] is a number between 0 and 1 that indicates how much of [hue1] /// should be in the resulting hue. double _interpolateHues( - double hue1, double hue2, HueInterpolationMethod method, double weight) { + double hue1, + double hue2, + HueInterpolationMethod method, + double weight, + ) { // Algorithms from https://www.w3.org/TR/css-color-4/#hue-interpolation switch (method) { case HueInterpolationMethod.shorter: diff --git a/lib/src/value/color/channel.dart b/lib/src/value/color/channel.dart index 63e279da7..6399d8080 100644 --- a/lib/src/value/color/channel.dart +++ b/lib/src/value/color/channel.dart @@ -34,8 +34,11 @@ class ColorChannel { /// @nodoc @internal - const ColorChannel(this.name, - {required this.isPolarAngle, this.associatedUnit}); + const ColorChannel( + this.name, { + required this.isPolarAngle, + this.associatedUnit, + }); /// Returns whether this channel is [analogous] to [other]. /// @@ -48,7 +51,7 @@ class ColorChannel { ("lightness", "lightness") || ("hue", "hue") => true, - _ => false + _ => false, }; } @@ -93,14 +96,17 @@ class LinearChannel extends ColorChannel { /// /// @nodoc @internal - const LinearChannel(super.name, this.min, this.max, - {this.requiresPercent = false, - this.lowerClamped = false, - this.upperClamped = false, - bool? conventionallyPercent}) - : super( - isPolarAngle: false, - associatedUnit: (conventionallyPercent ?? (min == 0 && max == 100)) - ? '%' - : null); + const LinearChannel( + super.name, + this.min, + this.max, { + this.requiresPercent = false, + this.lowerClamped = false, + this.upperClamped = false, + bool? conventionallyPercent, + }) : super( + isPolarAngle: false, + associatedUnit: + (conventionallyPercent ?? (min == 0 && max == 100)) ? '%' : null, + ); } diff --git a/lib/src/value/color/gamut_map_method.dart b/lib/src/value/color/gamut_map_method.dart index f934d5940..427e3e965 100644 --- a/lib/src/value/color/gamut_map_method.dart +++ b/lib/src/value/color/gamut_map_method.dart @@ -50,7 +50,9 @@ abstract base class GamutMapMethod { 'clip' => GamutMapMethod.clip, 'local-minde' => GamutMapMethod.localMinde, _ => throw SassScriptException( - 'Unknown gamut map method "$name".', argumentName) + 'Unknown gamut map method "$name".', + argumentName, + ), }; /// Maps [color] to its gamut using this method's algorithm. diff --git a/lib/src/value/color/gamut_map_method/clip.dart b/lib/src/value/color/gamut_map_method/clip.dart index 14420dea3..f145265a1 100644 --- a/lib/src/value/color/gamut_map_method/clip.dart +++ b/lib/src/value/color/gamut_map_method/clip.dart @@ -15,17 +15,18 @@ final class ClipGamutMap extends GamutMapMethod { const ClipGamutMap() : super("clip"); SassColor map(SassColor color) => SassColor.forSpaceInternal( - color.space, - _clampChannel(color.channel0OrNull, color.space.channels[0]), - _clampChannel(color.channel1OrNull, color.space.channels[1]), - _clampChannel(color.channel2OrNull, color.space.channels[2]), - color.alphaOrNull); + color.space, + _clampChannel(color.channel0OrNull, color.space.channels[0]), + _clampChannel(color.channel1OrNull, color.space.channels[1]), + _clampChannel(color.channel2OrNull, color.space.channels[2]), + color.alphaOrNull, + ); /// Clamps the channel value [value] within the bounds given by [channel]. double? _clampChannel(double? value, ColorChannel channel) => value == null ? null : switch (channel) { LinearChannel(:var min, :var max) => clampLikeCss(value, min, max), - _ => value + _ => value, }; } diff --git a/lib/src/value/color/gamut_map_method/local_minde.dart b/lib/src/value/color/gamut_map_method/local_minde.dart index a8b896d21..143007e55 100644 --- a/lib/src/value/color/gamut_map_method/local_minde.dart +++ b/lib/src/value/color/gamut_map_method/local_minde.dart @@ -52,8 +52,13 @@ final class LocalMindeGamutMap extends GamutMapMethod { // In the Color 4 algorithm `current` is in Oklch, but all its actual uses // other than modifying chroma convert it to `color.space` first so we // just store it in that space to begin with. - var current = - ColorSpace.oklch.convert(color.space, lightness, chroma, hue, alpha); + var current = ColorSpace.oklch.convert( + color.space, + lightness, + chroma, + hue, + alpha, + ); // Per [this comment], the intention of the algorithm is to fall through // this clause if `minInGamut = false` without checking @@ -86,8 +91,10 @@ final class LocalMindeGamutMap extends GamutMapMethod { var lab1 = color1.toSpace(ColorSpace.oklab); var lab2 = color2.toSpace(ColorSpace.oklab); - return math.sqrt(math.pow(lab1.channel0 - lab2.channel0, 2) + - math.pow(lab1.channel1 - lab2.channel1, 2) + - math.pow(lab1.channel2 - lab2.channel2, 2)); + return math.sqrt( + math.pow(lab1.channel0 - lab2.channel0, 2) + + math.pow(lab1.channel1 - lab2.channel1, 2) + + math.pow(lab1.channel2 - lab2.channel2, 2), + ); } } diff --git a/lib/src/value/color/interpolation_method.dart b/lib/src/value/color/interpolation_method.dart index 6a4a67535..04321d7e3 100644 --- a/lib/src/value/color/interpolation_method.dart +++ b/lib/src/value/color/interpolation_method.dart @@ -24,8 +24,9 @@ class InterpolationMethod { : hue = space.isPolar ? hue ?? HueInterpolationMethod.shorter : null { if (!space.isPolar && hue != null) { throw ArgumentError( - "Hue interpolation method may not be set for rectangular color space " - "$space."); + "Hue interpolation method may not be set for rectangular color space " + "$space.", + ); } } @@ -39,32 +40,42 @@ class InterpolationMethod { var list = value.assertCommonListStyle(name, allowSlash: false); if (list.isEmpty) { throw SassScriptException( - 'Expected a color interpolation method, got an empty list.', name); + 'Expected a color interpolation method, got an empty list.', + name, + ); } var space = ColorSpace.fromName( - (list.first.assertString(name)..assertUnquoted(name)).text, name); + (list.first.assertString(name)..assertUnquoted(name)).text, + name, + ); if (list.length == 1) return InterpolationMethod(space); var hueMethod = HueInterpolationMethod._fromValue(list[1], name); if (list.length == 2) { throw SassScriptException( - 'Expected unquoted string "hue" after $value.', name); + 'Expected unquoted string "hue" after $value.', + name, + ); } else if ((list[2].assertString(name)..assertUnquoted(name)) .text .toLowerCase() != 'hue') { throw SassScriptException( - 'Expected unquoted string "hue" at the end of $value, was ${list[2]}.', - name); + 'Expected unquoted string "hue" at the end of $value, was ${list[2]}.', + name, + ); } else if (list.length > 3) { throw SassScriptException( - 'Expected nothing after "hue" in $value.', name); + 'Expected nothing after "hue" in $value.', + name, + ); } else if (!space.isPolar) { throw SassScriptException( - 'Hue interpolation method "$hueMethod hue" may not be set for ' - 'rectangular color space $space.', - name); + 'Hue interpolation method "$hueMethod hue" may not be set for ' + 'rectangular color space $space.', + name, + ); } return InterpolationMethod(space, hueMethod); @@ -112,6 +123,8 @@ enum HueInterpolationMethod { 'increasing' => HueInterpolationMethod.increasing, 'decreasing' => HueInterpolationMethod.decreasing, _ => throw SassScriptException( - 'Unknown hue interpolation method $value.', name) + 'Unknown hue interpolation method $value.', + name, + ), }; } diff --git a/lib/src/value/color/space.dart b/lib/src/value/color/space.dart index 1d96ae705..6fbde0c51 100644 --- a/lib/src/value/color/space.dart +++ b/lib/src/value/color/space.dart @@ -142,7 +142,10 @@ abstract base class ColorSpace { /// /// If this came from a function argument, [argumentName] is the argument name /// (without the `$`). This is used for error reporting. - static ColorSpace fromName(String name, [String? argumentName]) => + static ColorSpace fromName( + String name, [ + String? argumentName, + ]) => switch (name.toLowerCase()) { 'rgb' => rgb, 'hwb' => hwb, @@ -160,7 +163,7 @@ abstract base class ColorSpace { 'oklab' => oklab, 'oklch' => oklch, _ => throw SassScriptException( - 'Unknown color space "$name".', argumentName) + 'Unknown color space "$name".', argumentName), }; /// Converts a color with the given channels from this color space to [dest]. @@ -172,8 +175,13 @@ abstract base class ColorSpace { /// /// @nodoc @internal - SassColor convert(ColorSpace dest, double? channel0, double? channel1, - double? channel2, double? alpha) => + SassColor convert( + ColorSpace dest, + double? channel0, + double? channel1, + double? channel2, + double? alpha, + ) => convertLinear(dest, channel0, channel1, channel2, alpha); /// The default implementation of [convert], which always starts with a linear @@ -185,17 +193,22 @@ abstract base class ColorSpace { @protected @nonVirtual SassColor convertLinear( - ColorSpace dest, double? red, double? green, double? blue, double? alpha, - {bool missingLightness = false, - bool missingChroma = false, - bool missingHue = false, - bool missingA = false, - bool missingB = false}) { + ColorSpace dest, + double? red, + double? green, + double? blue, + double? alpha, { + bool missingLightness = false, + bool missingChroma = false, + bool missingHue = false, + bool missingA = false, + bool missingB = false, + }) { var linearDest = switch (dest) { ColorSpace.hsl || ColorSpace.hwb => const SrgbColorSpace(), ColorSpace.lab || ColorSpace.lch => const XyzD50ColorSpace(), ColorSpace.oklab || ColorSpace.oklch => const LmsColorSpace(), - _ => dest + _ => dest, }; double? transformedRed; @@ -212,43 +225,65 @@ abstract base class ColorSpace { var matrix = transformationMatrix(linearDest); // (matrix * [linearRed, linearGreen, linearBlue]).map(linearDest.fromLinear) - transformedRed = linearDest.fromLinear(matrix[0] * linearRed + - matrix[1] * linearGreen + - matrix[2] * linearBlue); - transformedGreen = linearDest.fromLinear(matrix[3] * linearRed + - matrix[4] * linearGreen + - matrix[5] * linearBlue); - transformedBlue = linearDest.fromLinear(matrix[6] * linearRed + - matrix[7] * linearGreen + - matrix[8] * linearBlue); + transformedRed = linearDest.fromLinear( + matrix[0] * linearRed + + matrix[1] * linearGreen + + matrix[2] * linearBlue, + ); + transformedGreen = linearDest.fromLinear( + matrix[3] * linearRed + + matrix[4] * linearGreen + + matrix[5] * linearBlue, + ); + transformedBlue = linearDest.fromLinear( + matrix[6] * linearRed + + matrix[7] * linearGreen + + matrix[8] * linearBlue, + ); } return switch (dest) { ColorSpace.hsl || ColorSpace.hwb => const SrgbColorSpace().convert( - dest, transformedRed, transformedGreen, transformedBlue, alpha, + dest, + transformedRed, + transformedGreen, + transformedBlue, + alpha, missingLightness: missingLightness, missingChroma: missingChroma, - missingHue: missingHue), + missingHue: missingHue, + ), ColorSpace.lab || ColorSpace.lch => const XyzD50ColorSpace().convert( - dest, transformedRed, transformedGreen, transformedBlue, alpha, + dest, + transformedRed, + transformedGreen, + transformedBlue, + alpha, missingLightness: missingLightness, missingChroma: missingChroma, missingHue: missingHue, missingA: missingA, - missingB: missingB), + missingB: missingB, + ), ColorSpace.oklab || ColorSpace.oklch => const LmsColorSpace().convert( - dest, transformedRed, transformedGreen, transformedBlue, alpha, + dest, + transformedRed, + transformedGreen, + transformedBlue, + alpha, missingLightness: missingLightness, missingChroma: missingChroma, missingHue: missingHue, missingA: missingA, - missingB: missingB), + missingB: missingB, + ), _ => SassColor.forSpaceInternal( dest, red == null ? null : transformedRed, green == null ? null : transformedGreen, blue == null ? null : transformedBlue, - alpha) + alpha, + ), }; } @@ -268,7 +303,8 @@ abstract base class ColorSpace { @protected @internal double toLinear(double channel) => throw UnimplementedError( - "[BUG] Color space $this doesn't support linear conversions."); + "[BUG] Color space $this doesn't support linear conversions.", + ); /// Converts an element of a 3-element vector that can be linearly transformed /// into other color spaces into a channel in this color space. @@ -286,7 +322,8 @@ abstract base class ColorSpace { @protected @internal double fromLinear(double channel) => throw UnimplementedError( - "[BUG] Color space $this doesn't support linear conversions."); + "[BUG] Color space $this doesn't support linear conversions.", + ); /// Returns the matrix for performing a linear transformation from this color /// space to [dest]. @@ -302,7 +339,8 @@ abstract base class ColorSpace { @protected @internal Float64List transformationMatrix(ColorSpace dest) => throw UnimplementedError( - '[BUG] Color space conversion from $this to $dest not implemented.'); + '[BUG] Color space conversion from $this to $dest not implemented.', + ); String toString() => name; } diff --git a/lib/src/value/color/space/a98_rgb.dart b/lib/src/value/color/space/a98_rgb.dart index df61a6d50..77c540052 100644 --- a/lib/src/value/color/space/a98_rgb.dart +++ b/lib/src/value/color/space/a98_rgb.dart @@ -44,6 +44,6 @@ final class A98RgbColorSpace extends ColorSpace { ColorSpace.xyzD65 => linearA98RgbToXyzD65, ColorSpace.xyzD50 => linearA98RgbToXyzD50, ColorSpace.lms => linearA98RgbToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/display_p3.dart b/lib/src/value/color/space/display_p3.dart index b1c56df5d..9d3516d8c 100644 --- a/lib/src/value/color/space/display_p3.dart +++ b/lib/src/value/color/space/display_p3.dart @@ -39,6 +39,6 @@ final class DisplayP3ColorSpace extends ColorSpace { ColorSpace.xyzD65 => linearDisplayP3ToXyzD65, ColorSpace.xyzD50 => linearDisplayP3ToXyzD50, ColorSpace.lms => linearDisplayP3ToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/hsl.dart b/lib/src/value/color/space/hsl.dart index bc4a02164..6fdf6b3bd 100644 --- a/lib/src/value/color/space/hsl.dart +++ b/lib/src/value/color/space/hsl.dart @@ -22,13 +22,23 @@ final class HslColorSpace extends ColorSpace { const HslColorSpace() : super('hsl', const [ hueChannel, - LinearChannel('saturation', 0, 100, - requiresPercent: true, lowerClamped: true), - LinearChannel('lightness', 0, 100, requiresPercent: true) + LinearChannel( + 'saturation', + 0, + 100, + requiresPercent: true, + lowerClamped: true, + ), + LinearChannel('lightness', 0, 100, requiresPercent: true), ]); - SassColor convert(ColorSpace dest, double? hue, double? saturation, - double? lightness, double? alpha) { + SassColor convert( + ColorSpace dest, + double? hue, + double? saturation, + double? lightness, + double? alpha, + ) { // Algorithm from the CSS3 spec: https://www.w3.org/TR/css3-color/#hsl-color. var scaledHue = ((hue ?? 0) / 360) % 1; var scaledSaturation = (saturation ?? 0) / 100; @@ -42,13 +52,14 @@ final class HslColorSpace extends ColorSpace { var m1 = scaledLightness * 2 - m2; return const SrgbColorSpace().convert( - dest, - hueToRgb(m1, m2, scaledHue + 1 / 3), - hueToRgb(m1, m2, scaledHue), - hueToRgb(m1, m2, scaledHue - 1 / 3), - alpha, - missingLightness: lightness == null, - missingChroma: saturation == null, - missingHue: hue == null); + dest, + hueToRgb(m1, m2, scaledHue + 1 / 3), + hueToRgb(m1, m2, scaledHue), + hueToRgb(m1, m2, scaledHue - 1 / 3), + alpha, + missingLightness: lightness == null, + missingChroma: saturation == null, + missingHue: hue == null, + ); } } diff --git a/lib/src/value/color/space/hwb.dart b/lib/src/value/color/space/hwb.dart index 956768b19..b550a12ed 100644 --- a/lib/src/value/color/space/hwb.dart +++ b/lib/src/value/color/space/hwb.dart @@ -23,11 +23,16 @@ final class HwbColorSpace extends ColorSpace { : super('hwb', const [ hueChannel, LinearChannel('whiteness', 0, 100, requiresPercent: true), - LinearChannel('blackness', 0, 100, requiresPercent: true) + LinearChannel('blackness', 0, 100, requiresPercent: true), ]); - SassColor convert(ColorSpace dest, double? hue, double? whiteness, - double? blackness, double? alpha) { + SassColor convert( + ColorSpace dest, + double? hue, + double? whiteness, + double? blackness, + double? alpha, + ) { // From https://www.w3.org/TR/css-color-4/#hwb-to-rgb var scaledHue = (hue ?? 0) % 360 / 360; var scaledWhiteness = (whiteness ?? 0) / 100; @@ -44,8 +49,13 @@ final class HwbColorSpace extends ColorSpace { // Non-null because an in-gamut HSL color is guaranteed to be in-gamut for // HWB as well. - return const SrgbColorSpace().convert(dest, toRgb(scaledHue + 1 / 3), - toRgb(scaledHue), toRgb(scaledHue - 1 / 3), alpha, - missingHue: hue == null); + return const SrgbColorSpace().convert( + dest, + toRgb(scaledHue + 1 / 3), + toRgb(scaledHue), + toRgb(scaledHue - 1 / 3), + alpha, + missingHue: hue == null, + ); } } diff --git a/lib/src/value/color/space/lab.dart b/lib/src/value/color/space/lab.dart index 7766e706d..38a76921a 100644 --- a/lib/src/value/color/space/lab.dart +++ b/lib/src/value/color/space/lab.dart @@ -25,20 +25,35 @@ final class LabColorSpace extends ColorSpace { const LabColorSpace() : super('lab', const [ - LinearChannel('lightness', 0, 100, - lowerClamped: true, upperClamped: true), + LinearChannel( + 'lightness', + 0, + 100, + lowerClamped: true, + upperClamped: true, + ), LinearChannel('a', -125, 125), - LinearChannel('b', -125, 125) + LinearChannel('b', -125, 125), ]); SassColor convert( - ColorSpace dest, double? lightness, double? a, double? b, double? alpha, - {bool missingChroma = false, bool missingHue = false}) { + ColorSpace dest, + double? lightness, + double? a, + double? b, + double? alpha, { + bool missingChroma = false, + bool missingHue = false, + }) { switch (dest) { case ColorSpace.lab: var powerlessAB = lightness == null || fuzzyEquals(lightness, 0); - return SassColor.lab(lightness, a == null || powerlessAB ? null : a, - b == null || powerlessAB ? null : b, alpha); + return SassColor.lab( + lightness, + a == null || powerlessAB ? null : a, + b == null || powerlessAB ? null : b, + alpha, + ); case ColorSpace.lch: return labToLch(dest, lightness, a, b, alpha); @@ -51,19 +66,20 @@ final class LabColorSpace extends ColorSpace { var f1 = (lightness + 16) / 116; return const XyzD50ColorSpace().convert( - dest, - _convertFToXorZ((a ?? 0) / 500 + f1) * d50[0], - (lightness > labKappa * labEpsilon - ? math.pow((lightness + 16) / 116, 3) * 1.0 - : lightness / labKappa) * - d50[1], - _convertFToXorZ(f1 - (b ?? 0) / 200) * d50[2], - alpha, - missingLightness: missingLightness, - missingChroma: missingChroma, - missingHue: missingHue, - missingA: a == null, - missingB: b == null); + dest, + _convertFToXorZ((a ?? 0) / 500 + f1) * d50[0], + (lightness > labKappa * labEpsilon + ? math.pow((lightness + 16) / 116, 3) * 1.0 + : lightness / labKappa) * + d50[1], + _convertFToXorZ(f1 - (b ?? 0) / 200) * d50[2], + alpha, + missingLightness: missingLightness, + missingChroma: missingChroma, + missingHue: missingHue, + missingA: a == null, + missingB: b == null, + ); } } diff --git a/lib/src/value/color/space/lch.dart b/lib/src/value/color/space/lch.dart index 095babeef..9885fd124 100644 --- a/lib/src/value/color/space/lch.dart +++ b/lib/src/value/color/space/lch.dart @@ -24,22 +24,33 @@ final class LchColorSpace extends ColorSpace { const LchColorSpace() : super('lch', const [ - LinearChannel('lightness', 0, 100, - lowerClamped: true, upperClamped: true), + LinearChannel( + 'lightness', + 0, + 100, + lowerClamped: true, + upperClamped: true, + ), LinearChannel('chroma', 0, 150, lowerClamped: true), - hueChannel + hueChannel, ]); - SassColor convert(ColorSpace dest, double? lightness, double? chroma, - double? hue, double? alpha) { + SassColor convert( + ColorSpace dest, + double? lightness, + double? chroma, + double? hue, + double? alpha, + ) { var hueRadians = (hue ?? 0) * math.pi / 180; return const LabColorSpace().convert( - dest, - lightness, - (chroma ?? 0) * math.cos(hueRadians), - (chroma ?? 0) * math.sin(hueRadians), - alpha, - missingChroma: chroma == null, - missingHue: hue == null); + dest, + lightness, + (chroma ?? 0) * math.cos(hueRadians), + (chroma ?? 0) * math.sin(hueRadians), + alpha, + missingChroma: chroma == null, + missingHue: hue == null, + ); } } diff --git a/lib/src/value/color/space/lms.dart b/lib/src/value/color/space/lms.dart index 0ea82eb01..7152690d8 100644 --- a/lib/src/value/color/space/lms.dart +++ b/lib/src/value/color/space/lms.dart @@ -28,16 +28,21 @@ final class LmsColorSpace extends ColorSpace { : super('lms', const [ LinearChannel('long', 0, 1), LinearChannel('medium', 0, 1), - LinearChannel('short', 0, 1) + LinearChannel('short', 0, 1), ]); - SassColor convert(ColorSpace dest, double? long, double? medium, - double? short, double? alpha, - {bool missingLightness = false, - bool missingChroma = false, - bool missingHue = false, - bool missingA = false, - bool missingB = false}) { + SassColor convert( + ColorSpace dest, + double? long, + double? medium, + double? short, + double? alpha, { + bool missingLightness = false, + bool missingChroma = false, + bool missingHue = false, + bool missingA = false, + bool missingB = false, + }) { switch (dest) { case ColorSpace.oklab: // Algorithm from https://drafts.csswg.org/css-color-4/#color-conversion-code @@ -49,18 +54,19 @@ final class LmsColorSpace extends ColorSpace { lmsToOklab[2] * shortScaled; return SassColor.oklab( - missingLightness ? null : lightness, - missingA - ? null - : lmsToOklab[3] * longScaled + - lmsToOklab[4] * mediumScaled + - lmsToOklab[5] * shortScaled, - missingB - ? null - : lmsToOklab[6] * longScaled + - lmsToOklab[7] * mediumScaled + - lmsToOklab[8] * shortScaled, - alpha); + missingLightness ? null : lightness, + missingA + ? null + : lmsToOklab[3] * longScaled + + lmsToOklab[4] * mediumScaled + + lmsToOklab[5] * shortScaled, + missingB + ? null + : lmsToOklab[6] * longScaled + + lmsToOklab[7] * mediumScaled + + lmsToOklab[8] * shortScaled, + alpha, + ); case ColorSpace.oklch: // This is equivalent to converting to OKLab and then to OKLCH, but we @@ -70,29 +76,36 @@ final class LmsColorSpace extends ColorSpace { var mediumScaled = _cubeRootPreservingSign(medium ?? 0); var shortScaled = _cubeRootPreservingSign(short ?? 0); return labToLch( - dest, - missingLightness - ? null - : lmsToOklab[0] * longScaled + - lmsToOklab[1] * mediumScaled + - lmsToOklab[2] * shortScaled, - lmsToOklab[3] * longScaled + - lmsToOklab[4] * mediumScaled + - lmsToOklab[5] * shortScaled, - lmsToOklab[6] * longScaled + - lmsToOklab[7] * mediumScaled + - lmsToOklab[8] * shortScaled, - alpha, - missingChroma: missingChroma, - missingHue: missingHue); + dest, + missingLightness + ? null + : lmsToOklab[0] * longScaled + + lmsToOklab[1] * mediumScaled + + lmsToOklab[2] * shortScaled, + lmsToOklab[3] * longScaled + + lmsToOklab[4] * mediumScaled + + lmsToOklab[5] * shortScaled, + lmsToOklab[6] * longScaled + + lmsToOklab[7] * mediumScaled + + lmsToOklab[8] * shortScaled, + alpha, + missingChroma: missingChroma, + missingHue: missingHue, + ); default: - return super.convertLinear(dest, long, medium, short, alpha, - missingLightness: missingLightness, - missingChroma: missingChroma, - missingHue: missingHue, - missingA: missingA, - missingB: missingB); + return super.convertLinear( + dest, + long, + medium, + short, + alpha, + missingLightness: missingLightness, + missingChroma: missingChroma, + missingHue: missingHue, + missingA: missingA, + missingB: missingB, + ); } } @@ -119,6 +132,6 @@ final class LmsColorSpace extends ColorSpace { ColorSpace.rec2020 => lmsToLinearRec2020, ColorSpace.xyzD65 => lmsToXyzD65, ColorSpace.xyzD50 => lmsToXyzD50, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/oklab.dart b/lib/src/value/color/space/oklab.dart index c24806635..dfadd57b3 100644 --- a/lib/src/value/color/space/oklab.dart +++ b/lib/src/value/color/space/oklab.dart @@ -24,20 +24,37 @@ final class OklabColorSpace extends ColorSpace { const OklabColorSpace() : super('oklab', const [ - LinearChannel('lightness', 0, 1, - conventionallyPercent: true, - lowerClamped: true, - upperClamped: true), + LinearChannel( + 'lightness', + 0, + 1, + conventionallyPercent: true, + lowerClamped: true, + upperClamped: true, + ), LinearChannel('a', -0.4, 0.4), - LinearChannel('b', -0.4, 0.4) + LinearChannel('b', -0.4, 0.4), ]); SassColor convert( - ColorSpace dest, double? lightness, double? a, double? b, double? alpha, - {bool missingChroma = false, bool missingHue = false}) { + ColorSpace dest, + double? lightness, + double? a, + double? b, + double? alpha, { + bool missingChroma = false, + bool missingHue = false, + }) { if (dest == ColorSpace.oklch) { - return labToLch(dest, lightness, a, b, alpha, - missingChroma: missingChroma, missingHue: missingHue); + return labToLch( + dest, + lightness, + a, + b, + alpha, + missingChroma: missingChroma, + missingHue: missingHue, + ); } var missingLightness = lightness == null; @@ -48,30 +65,28 @@ final class OklabColorSpace extends ColorSpace { b ??= 0; // Algorithm from https://www.w3.org/TR/css-color-4/#color-conversion-code return const LmsColorSpace().convert( - dest, - math.pow( - oklabToLms[0] * lightness + - oklabToLms[1] * a + - oklabToLms[2] * b, - 3) + - 0.0, - math.pow( - oklabToLms[3] * lightness + - oklabToLms[4] * a + - oklabToLms[5] * b, - 3) + - 0.0, - math.pow( - oklabToLms[6] * lightness + - oklabToLms[7] * a + - oklabToLms[8] * b, - 3) + - 0.0, - alpha, - missingLightness: missingLightness, - missingChroma: missingChroma, - missingHue: missingHue, - missingA: missingA, - missingB: missingB); + dest, + math.pow( + oklabToLms[0] * lightness + oklabToLms[1] * a + oklabToLms[2] * b, + 3, + ) + + 0.0, + math.pow( + oklabToLms[3] * lightness + oklabToLms[4] * a + oklabToLms[5] * b, + 3, + ) + + 0.0, + math.pow( + oklabToLms[6] * lightness + oklabToLms[7] * a + oklabToLms[8] * b, + 3, + ) + + 0.0, + alpha, + missingLightness: missingLightness, + missingChroma: missingChroma, + missingHue: missingHue, + missingA: missingA, + missingB: missingB, + ); } } diff --git a/lib/src/value/color/space/oklch.dart b/lib/src/value/color/space/oklch.dart index bdf7fea65..4899b32d2 100644 --- a/lib/src/value/color/space/oklch.dart +++ b/lib/src/value/color/space/oklch.dart @@ -24,24 +24,34 @@ final class OklchColorSpace extends ColorSpace { const OklchColorSpace() : super('oklch', const [ - LinearChannel('lightness', 0, 1, - conventionallyPercent: true, - lowerClamped: true, - upperClamped: true), + LinearChannel( + 'lightness', + 0, + 1, + conventionallyPercent: true, + lowerClamped: true, + upperClamped: true, + ), LinearChannel('chroma', 0, 0.4, lowerClamped: true), - hueChannel + hueChannel, ]); - SassColor convert(ColorSpace dest, double? lightness, double? chroma, - double? hue, double? alpha) { + SassColor convert( + ColorSpace dest, + double? lightness, + double? chroma, + double? hue, + double? alpha, + ) { var hueRadians = (hue ?? 0) * math.pi / 180; return const OklabColorSpace().convert( - dest, - lightness, - (chroma ?? 0) * math.cos(hueRadians), - (chroma ?? 0) * math.sin(hueRadians), - alpha, - missingChroma: chroma == null, - missingHue: hue == null); + dest, + lightness, + (chroma ?? 0) * math.cos(hueRadians), + (chroma ?? 0) * math.sin(hueRadians), + alpha, + missingChroma: chroma == null, + missingHue: hue == null, + ); } } diff --git a/lib/src/value/color/space/prophoto_rgb.dart b/lib/src/value/color/space/prophoto_rgb.dart index 0de23ada9..7361cb66e 100644 --- a/lib/src/value/color/space/prophoto_rgb.dart +++ b/lib/src/value/color/space/prophoto_rgb.dart @@ -50,6 +50,6 @@ final class ProphotoRgbColorSpace extends ColorSpace { ColorSpace.xyzD65 => linearProphotoRgbToXyzD65, ColorSpace.xyzD50 => linearProphotoRgbToXyzD50, ColorSpace.lms => linearProphotoRgbToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/rgb.dart b/lib/src/value/color/space/rgb.dart index b12fd40bd..0031a7a9c 100644 --- a/lib/src/value/color/space/rgb.dart +++ b/lib/src/value/color/space/rgb.dart @@ -22,17 +22,23 @@ final class RgbColorSpace extends ColorSpace { LinearChannel('red', 0, 255, lowerClamped: true, upperClamped: true), LinearChannel('green', 0, 255, lowerClamped: true, upperClamped: true), - LinearChannel('blue', 0, 255, lowerClamped: true, upperClamped: true) + LinearChannel('blue', 0, 255, lowerClamped: true, upperClamped: true), ]); - SassColor convert(ColorSpace dest, double? red, double? green, double? blue, - double? alpha) => + SassColor convert( + ColorSpace dest, + double? red, + double? green, + double? blue, + double? alpha, + ) => ColorSpace.srgb.convert( - dest, - red == null ? null : red / 255, - green == null ? null : green / 255, - blue == null ? null : blue / 255, - alpha); + dest, + red == null ? null : red / 255, + green == null ? null : green / 255, + blue == null ? null : blue / 255, + alpha, + ); @protected double toLinear(double channel) => srgbAndDisplayP3ToLinear(channel / 255); diff --git a/lib/src/value/color/space/srgb.dart b/lib/src/value/color/space/srgb.dart index 963cdf733..b901c4325 100644 --- a/lib/src/value/color/space/srgb.dart +++ b/lib/src/value/color/space/srgb.dart @@ -28,10 +28,15 @@ final class SrgbColorSpace extends ColorSpace { const SrgbColorSpace() : super('srgb', rgbChannels); SassColor convert( - ColorSpace dest, double? red, double? green, double? blue, double? alpha, - {bool missingLightness = false, - bool missingChroma = false, - bool missingHue = false}) { + ColorSpace dest, + double? red, + double? green, + double? blue, + double? alpha, { + bool missingLightness = false, + bool missingChroma = false, + bool missingHue = false, + }) { switch (dest) { case ColorSpace.hsl || ColorSpace.hwb: red ??= 0; @@ -67,40 +72,54 @@ final class SrgbColorSpace extends ColorSpace { } return SassColor.forSpaceInternal( - dest, - missingHue || fuzzyEquals(saturation, 0) ? null : hue % 360, - missingChroma ? null : saturation, - missingLightness ? null : lightness * 100, - alpha); + dest, + missingHue || fuzzyEquals(saturation, 0) ? null : hue % 360, + missingChroma ? null : saturation, + missingLightness ? null : lightness * 100, + alpha, + ); } else { var whiteness = min * 100; var blackness = 100 - max * 100; return SassColor.forSpaceInternal( - dest, - missingHue || fuzzyGreaterThanOrEquals(whiteness + blackness, 100) - ? null - : hue % 360, - whiteness, - blackness, - alpha); + dest, + missingHue || fuzzyGreaterThanOrEquals(whiteness + blackness, 100) + ? null + : hue % 360, + whiteness, + blackness, + alpha, + ); } case ColorSpace.rgb: return SassColor.rgb( - red == null ? null : red * 255, - green == null ? null : green * 255, - blue == null ? null : blue * 255, - alpha); + red == null ? null : red * 255, + green == null ? null : green * 255, + blue == null ? null : blue * 255, + alpha, + ); case ColorSpace.srgbLinear: - return SassColor.forSpaceInternal(dest, red.andThen(toLinear), - green.andThen(toLinear), blue.andThen(toLinear), alpha); + return SassColor.forSpaceInternal( + dest, + red.andThen(toLinear), + green.andThen(toLinear), + blue.andThen(toLinear), + alpha, + ); default: - return super.convertLinear(dest, red, green, blue, alpha, - missingLightness: missingLightness, - missingChroma: missingChroma, - missingHue: missingHue); + return super.convertLinear( + dest, + red, + green, + blue, + alpha, + missingLightness: missingLightness, + missingChroma: missingChroma, + missingHue: missingHue, + ); } } diff --git a/lib/src/value/color/space/srgb_linear.dart b/lib/src/value/color/space/srgb_linear.dart index 3e5151da4..6f4d8c0d1 100644 --- a/lib/src/value/color/space/srgb_linear.dart +++ b/lib/src/value/color/space/srgb_linear.dart @@ -24,20 +24,26 @@ final class SrgbLinearColorSpace extends ColorSpace { const SrgbLinearColorSpace() : super('srgb-linear', rgbChannels); - SassColor convert(ColorSpace dest, double? red, double? green, double? blue, - double? alpha) => + SassColor convert( + ColorSpace dest, + double? red, + double? green, + double? blue, + double? alpha, + ) => switch (dest) { ColorSpace.rgb || ColorSpace.hsl || ColorSpace.hwb || ColorSpace.srgb => ColorSpace.srgb.convert( - dest, - red.andThen(srgbAndDisplayP3FromLinear), - green.andThen(srgbAndDisplayP3FromLinear), - blue.andThen(srgbAndDisplayP3FromLinear), - alpha), - _ => super.convert(dest, red, green, blue, alpha) + dest, + red.andThen(srgbAndDisplayP3FromLinear), + green.andThen(srgbAndDisplayP3FromLinear), + blue.andThen(srgbAndDisplayP3FromLinear), + alpha, + ), + _ => super.convert(dest, red, green, blue, alpha), }; @protected @@ -55,6 +61,6 @@ final class SrgbLinearColorSpace extends ColorSpace { ColorSpace.xyzD65 => linearSrgbToXyzD65, ColorSpace.xyzD50 => linearSrgbToXyzD50, ColorSpace.lms => linearSrgbToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/utils.dart b/lib/src/value/color/space/utils.dart index a5be17e22..4c86a2f6d 100644 --- a/lib/src/value/color/space/utils.dart +++ b/lib/src/value/color/space/utils.dart @@ -14,21 +14,24 @@ const labKappa = 24389 / 27; // 29^3/3^3; const labEpsilon = 216 / 24389; // 6^3/29^3; /// The hue channel shared across all polar color spaces. -const hueChannel = - ColorChannel('hue', isPolarAngle: true, associatedUnit: 'deg'); +const hueChannel = ColorChannel( + 'hue', + isPolarAngle: true, + associatedUnit: 'deg', +); /// The color channels shared across all RGB color spaces (except the legacy RGB space). const rgbChannels = [ LinearChannel('red', 0, 1), LinearChannel('green', 0, 1), - LinearChannel('blue', 0, 1) + LinearChannel('blue', 0, 1), ]; /// The color channels shared across both XYZ color spaces. const xyzChannels = [ LinearChannel('x', 0, 1), LinearChannel('y', 0, 1), - LinearChannel('z', 0, 1) + LinearChannel('z', 0, 1), ]; /// Converts a legacy HSL/HWB hue to an RGB channel. @@ -43,7 +46,7 @@ double hueToRgb(double m1, double m2, double hue) { < 1 / 6 => m1 + (m2 - m1) * hue * 6, < 1 / 2 => m2, < 2 / 3 => m1 + (m2 - m1) * (2 / 3 - hue) * 6, - _ => m1 + _ => m1, }; } @@ -72,8 +75,14 @@ double srgbAndDisplayP3FromLinear(double channel) { /// The [missingChroma] and [missingHue] arguments indicate whether this came /// from a color that was missing its chroma or hue channels, respectively. SassColor labToLch( - ColorSpace dest, double? lightness, double? a, double? b, double? alpha, - {bool missingChroma = false, bool missingHue = false}) { + ColorSpace dest, + double? lightness, + double? a, + double? b, + double? alpha, { + bool missingChroma = false, + bool missingHue = false, +}) { // Algorithm from https://www.w3.org/TR/css-color-4/#color-conversion-code var chroma = math.sqrt(math.pow(a ?? 0, 2) + math.pow(b ?? 0, 2)); var hue = missingHue || fuzzyEquals(chroma, 0) @@ -81,9 +90,10 @@ SassColor labToLch( : math.atan2(b ?? 0, a ?? 0) * 180 / math.pi; return SassColor.forSpaceInternal( - dest, - lightness, - missingChroma ? null : chroma, - hue == null || hue >= 0 ? hue : hue + 360, - alpha); + dest, + lightness, + missingChroma ? null : chroma, + hue == null || hue >= 0 ? hue : hue + 360, + alpha, + ); } diff --git a/lib/src/value/color/space/xyz_d50.dart b/lib/src/value/color/space/xyz_d50.dart index fab064cb9..e21723c64 100644 --- a/lib/src/value/color/space/xyz_d50.dart +++ b/lib/src/value/color/space/xyz_d50.dart @@ -25,12 +25,17 @@ final class XyzD50ColorSpace extends ColorSpace { const XyzD50ColorSpace() : super('xyz-d50', xyzChannels); SassColor convert( - ColorSpace dest, double? x, double? y, double? z, double? alpha, - {bool missingLightness = false, - bool missingChroma = false, - bool missingHue = false, - bool missingA = false, - bool missingB = false}) { + ColorSpace dest, + double? x, + double? y, + double? z, + double? alpha, { + bool missingLightness = false, + bool missingChroma = false, + bool missingHue = false, + bool missingA = false, + bool missingB = false, + }) { switch (dest) { case ColorSpace.lab || ColorSpace.lch: // Algorithm from https://www.w3.org/TR/css-color-4/#color-conversion-code @@ -44,17 +49,34 @@ final class XyzD50ColorSpace extends ColorSpace { return dest == ColorSpace.lab ? SassColor.lab( - lightness, missingA ? null : a, missingB ? null : b, alpha) - : labToLch(ColorSpace.lch, lightness, a, b, alpha, - missingChroma: missingChroma, missingHue: missingHue); + lightness, + missingA ? null : a, + missingB ? null : b, + alpha, + ) + : labToLch( + ColorSpace.lch, + lightness, + a, + b, + alpha, + missingChroma: missingChroma, + missingHue: missingHue, + ); default: - return super.convertLinear(dest, x, y, z, alpha, - missingLightness: missingLightness, - missingChroma: missingChroma, - missingHue: missingHue, - missingA: missingA, - missingB: missingB); + return super.convertLinear( + dest, + x, + y, + z, + alpha, + missingLightness: missingLightness, + missingChroma: missingChroma, + missingHue: missingHue, + missingA: missingA, + missingB: missingB, + ); } } @@ -81,6 +103,6 @@ final class XyzD50ColorSpace extends ColorSpace { ColorSpace.rec2020 => xyzD50ToLinearRec2020, ColorSpace.xyzD65 => xyzD50ToXyzD65, ColorSpace.lms => xyzD50ToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/color/space/xyz_d65.dart b/lib/src/value/color/space/xyz_d65.dart index 4915a8cbc..f6535c910 100644 --- a/lib/src/value/color/space/xyz_d65.dart +++ b/lib/src/value/color/space/xyz_d65.dart @@ -39,6 +39,6 @@ final class XyzD65ColorSpace extends ColorSpace { ColorSpace.rec2020 => xyzD65ToLinearRec2020, ColorSpace.xyzD50 => xyzD65ToXyzD50, ColorSpace.lms => xyzD65ToLms, - _ => super.transformationMatrix(dest) + _ => super.transformationMatrix(dest), }; } diff --git a/lib/src/value/list.dart b/lib/src/value/list.dart index 78c993840..8d1f4687b 100644 --- a/lib/src/value/list.dart +++ b/lib/src/value/list.dart @@ -54,7 +54,8 @@ class SassList extends Value { _hasBrackets = brackets { if (separator == ListSeparator.undecided && asList.length > 1) { throw ArgumentError( - "A list with more than one element must have an explicit separator."); + "A list with more than one element must have an explicit separator.", + ); } } diff --git a/lib/src/value/map.dart b/lib/src/value/map.dart index b9895eda4..3a846d6d3 100644 --- a/lib/src/value/map.dart +++ b/lib/src/value/map.dart @@ -32,7 +32,7 @@ class SassMap extends Value { List get asList => [ for (var (key, value) in contents.pairs) - SassList([key, value], ListSeparator.space) + SassList([key, value], ListSeparator.space), ]; /// @nodoc diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index d4cb2b1b8..7fa5d94e5 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -86,61 +86,23 @@ const _conversions = { }, // Rotation - "deg": { - "deg": 1.0, - "grad": 9 / 10, - "rad": 180 / pi, - "turn": 360.0, - }, - "grad": { - "deg": 10 / 9, - "grad": 1.0, - "rad": 200 / pi, - "turn": 400.0, - }, - "rad": { - "deg": pi / 180, - "grad": pi / 200, - "rad": 1.0, - "turn": 2 * pi, - }, - "turn": { - "deg": 1 / 360, - "grad": 1 / 400, - "rad": 1 / (2 * pi), - "turn": 1.0, - }, + "deg": {"deg": 1.0, "grad": 9 / 10, "rad": 180 / pi, "turn": 360.0}, + "grad": {"deg": 10 / 9, "grad": 1.0, "rad": 200 / pi, "turn": 400.0}, + "rad": {"deg": pi / 180, "grad": pi / 200, "rad": 1.0, "turn": 2 * pi}, + "turn": {"deg": 1 / 360, "grad": 1 / 400, "rad": 1 / (2 * pi), "turn": 1.0}, // Time - "s": { - "s": 1.0, - "ms": 1 / 1000, - }, - "ms": { - "s": 1000.0, - "ms": 1.0, - }, + "s": {"s": 1.0, "ms": 1 / 1000}, + "ms": {"s": 1000.0, "ms": 1.0}, // Frequency "Hz": {"Hz": 1.0, "kHz": 1000.0}, "kHz": {"Hz": 1 / 1000, "kHz": 1.0}, // Pixel density - "dpi": { - "dpi": 1.0, - "dpcm": 2.54, - "dppx": 96.0, - }, - "dpcm": { - "dpi": 1 / 2.54, - "dpcm": 1.0, - "dppx": 96 / 2.54, - }, - "dppx": { - "dpi": 1 / 96, - "dpcm": 2.54 / 96, - "dppx": 1.0, - }, + "dpi": {"dpi": 1.0, "dpcm": 2.54, "dppx": 96.0}, + "dpcm": {"dpi": 1 / 2.54, "dpcm": 1.0, "dppx": 96 / 2.54}, + "dppx": {"dpi": 1 / 96, "dpcm": 2.54 / 96, "dppx": 1.0}, }; /// A map from human-readable names of unit types to the convertible units that @@ -150,13 +112,13 @@ const _unitsByType = { "angle": ["deg", "grad", "rad", "turn"], "time": ["s", "ms"], "frequency": ["Hz", "kHz"], - "pixel density": ["dpi", "dpcm", "dppx"] + "pixel density": ["dpi", "dpcm", "dppx"], }; /// A map from units to the human-readable names of those unit types. final _typesByUnit = { for (var (type, units) in _unitsByType.pairs) - for (var unit in units) unit: type + for (var unit in units) unit: type, }; /// Returns the number of [unit1]s per [unit2]. @@ -255,8 +217,11 @@ abstract class SassNumber extends Value { : SingleUnitSassNumber(value.toDouble(), unit); /// Creates a number with full [numeratorUnits] and [denominatorUnits]. - factory SassNumber.withUnits(num value, - {List? numeratorUnits, List? denominatorUnits}) { + factory SassNumber.withUnits( + num value, { + List? numeratorUnits, + List? denominatorUnits, + }) { var valueDouble = value.toDouble(); switch ((numeratorUnits, denominatorUnits)) { case (null || [], null || []): @@ -266,10 +231,16 @@ abstract class SassNumber extends Value { // TODO(dart-lang/language#3160): Remove extra null checks case (var numerators?, null || []): return ComplexSassNumber( - valueDouble, List.unmodifiable(numerators), const []); + valueDouble, + List.unmodifiable(numerators), + const [], + ); case (null || [], var denominators?): return ComplexSassNumber( - valueDouble, const [], List.unmodifiable(denominators)); + valueDouble, + const [], + List.unmodifiable(denominators), + ); } // dart-lang/language#3160 as well @@ -293,8 +264,11 @@ abstract class SassNumber extends Value { return switch ((numerators, denominators)) { ([], []) => UnitlessSassNumber(valueDouble), ([var unit], []) => SingleUnitSassNumber(valueDouble, unit), - _ => ComplexSassNumber(valueDouble, List.unmodifiable(numerators), - List.unmodifiable(denominators)) + _ => ComplexSassNumber( + valueDouble, + List.unmodifiable(numerators), + List.unmodifiable(denominators), + ), }; } @@ -346,8 +320,9 @@ abstract class SassNumber extends Value { double valueInRange(num min, num max, [String? name]) { if (fuzzyCheckRange(value, min, max) case var result?) return result; throw SassScriptException( - "Expected $this to be within $min$unitString and $max$unitString.", - name); + "Expected $this to be within $min$unitString and $max$unitString.", + name, + ); } /// Like [valueInRange], but with an explicit unit for the expected upper and @@ -362,7 +337,9 @@ abstract class SassNumber extends Value { double valueInRangeWithUnit(num min, num max, String name, String unit) { if (fuzzyCheckRange(value, min, max) case var result?) return result; throw SassScriptException( - "Expected $this to be within $min$unit and $max$unit.", name); + "Expected $this to be within $min$unit and $max$unit.", + name, + ); } /// Returns whether `this` has [unit] as its only unit (and as a numerator). @@ -419,10 +396,16 @@ abstract class SassNumber extends Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - SassNumber convert(List newNumerators, List newDenominators, - [String? name]) => - SassNumber.withUnits(convertValue(newNumerators, newDenominators, name), - numeratorUnits: newNumerators, denominatorUnits: newDenominators); + SassNumber convert( + List newNumerators, + List newDenominators, [ + String? name, + ]) => + SassNumber.withUnits( + convertValue(newNumerators, newDenominators, name), + numeratorUnits: newNumerators, + denominatorUnits: newDenominators, + ); /// Returns [value], converted to the units represented by [newNumerators] and /// [newDenominators]. @@ -432,10 +415,17 @@ abstract class SassNumber extends Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - double convertValue(List newNumerators, List newDenominators, - [String? name]) => - _coerceOrConvertValue(newNumerators, newDenominators, - coerceUnitless: false, name: name); + double convertValue( + List newNumerators, + List newDenominators, [ + String? name, + ]) => + _coerceOrConvertValue( + newNumerators, + newDenominators, + coerceUnitless: false, + name: name, + ); /// A shorthand for [convertValue] with only one numerator unit. double convertValueToUnit(String unit, [String? name]) => @@ -453,11 +443,16 @@ abstract class SassNumber extends Value { /// If this came from a function argument, [name] is the argument name /// (without the `$`) and [otherName] is the argument name for [other]. These /// are used for error reporting. - SassNumber convertToMatch(SassNumber other, - [String? name, String? otherName]) => - SassNumber.withUnits(convertValueToMatch(other, name, otherName), - numeratorUnits: other.numeratorUnits, - denominatorUnits: other.denominatorUnits); + SassNumber convertToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => + SassNumber.withUnits( + convertValueToMatch(other, name, otherName), + numeratorUnits: other.numeratorUnits, + denominatorUnits: other.denominatorUnits, + ); /// Returns [value], converted to the same units as [other]. /// @@ -468,13 +463,19 @@ abstract class SassNumber extends Value { /// If this came from a function argument, [name] is the argument name /// (without the `$`) and [otherName] is the argument name for [other]. These /// are used for error reporting. - double convertValueToMatch(SassNumber other, - [String? name, String? otherName]) => - _coerceOrConvertValue(other.numeratorUnits, other.denominatorUnits, - coerceUnitless: false, - name: name, - other: other, - otherName: otherName); + double convertValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => + _coerceOrConvertValue( + other.numeratorUnits, + other.denominatorUnits, + coerceUnitless: false, + name: name, + other: other, + otherName: otherName, + ); /// Returns a copy of this number, converted to the units represented by /// [newNumerators] and [newDenominators]. @@ -492,10 +493,16 @@ abstract class SassNumber extends Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - SassNumber coerce(List newNumerators, List newDenominators, - [String? name]) => - SassNumber.withUnits(coerceValue(newNumerators, newDenominators, name), - numeratorUnits: newNumerators, denominatorUnits: newDenominators); + SassNumber coerce( + List newNumerators, + List newDenominators, [ + String? name, + ]) => + SassNumber.withUnits( + coerceValue(newNumerators, newDenominators, name), + numeratorUnits: newNumerators, + denominatorUnits: newDenominators, + ); /// Returns [value], converted to the units represented by [newNumerators] and /// [newDenominators]. @@ -510,10 +517,17 @@ abstract class SassNumber extends Value { /// /// If this came from a function argument, [name] is the argument name /// (without the `$`). It's used for error reporting. - double coerceValue(List newNumerators, List newDenominators, - [String? name]) => - _coerceOrConvertValue(newNumerators, newDenominators, - coerceUnitless: true, name: name); + double coerceValue( + List newNumerators, + List newDenominators, [ + String? name, + ]) => + _coerceOrConvertValue( + newNumerators, + newDenominators, + coerceUnitless: true, + name: name, + ); /// A shorthand for [coerceValue] with only one numerator unit. double coerceValueToUnit(String unit, [String? name]) => @@ -535,11 +549,16 @@ abstract class SassNumber extends Value { /// If this came from a function argument, [name] is the argument name /// (without the `$`) and [otherName] is the argument name for [other]. These /// are used for error reporting. - SassNumber coerceToMatch(SassNumber other, - [String? name, String? otherName]) => - SassNumber.withUnits(coerceValueToMatch(other, name, otherName), - numeratorUnits: other.numeratorUnits, - denominatorUnits: other.denominatorUnits); + SassNumber coerceToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => + SassNumber.withUnits( + coerceValueToMatch(other, name, otherName), + numeratorUnits: other.numeratorUnits, + denominatorUnits: other.denominatorUnits, + ); /// Returns [value], converted to the same units as [other]. /// @@ -554,16 +573,28 @@ abstract class SassNumber extends Value { /// If this came from a function argument, [name] is the argument name /// (without the `$`) and [otherName] is the argument name for [other]. These /// are used for error reporting. - double coerceValueToMatch(SassNumber other, - [String? name, String? otherName]) => - _coerceOrConvertValue(other.numeratorUnits, other.denominatorUnits, - coerceUnitless: true, name: name, other: other, otherName: otherName); + double coerceValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => + _coerceOrConvertValue( + other.numeratorUnits, + other.denominatorUnits, + coerceUnitless: true, + name: name, + other: other, + otherName: otherName, + ); /// This has been renamed [coerceValue] for consistency with [coerceToMatch], /// [coerceValueToMatch], [convertToMatch], and [convertValueToMatch]. @Deprecated("Use coerceValue instead.") - double valueInUnits(List newNumerators, List newDenominators, - [String? name]) => + double valueInUnits( + List newNumerators, + List newDenominators, [ + String? name, + ]) => coerceValue(newNumerators, newDenominators, name); /// Converts [value] to [newNumerators] and [newDenominators]. @@ -577,17 +608,20 @@ abstract class SassNumber extends Value { /// function parameter names of `this` and [other], respectively, used for /// error reporting. double _coerceOrConvertValue( - List newNumerators, List newDenominators, - {required bool coerceUnitless, - String? name, - SassNumber? other, - String? otherName}) { + List newNumerators, + List newDenominators, { + required bool coerceUnitless, + String? name, + SassNumber? other, + String? otherName, + }) { assert( - other == null || - (listEquals(other.numeratorUnits, newNumerators) && - listEquals(other.denominatorUnits, newDenominators)), - "Expected $other to have units " - "${_unitString(newNumerators, newDenominators)}."); + other == null || + (listEquals(other.numeratorUnits, newNumerators) && + listEquals(other.denominatorUnits, newDenominators)), + "Expected $other to have units " + "${_unitString(newNumerators, newDenominators)}.", + ); if (listEquals(numeratorUnits, newNumerators) && listEquals(denominatorUnits, newDenominators)) { @@ -615,18 +649,22 @@ abstract class SassNumber extends Value { // If we're converting to a unit of a named type, use that type name // and make it clear exactly which units are convertible. return SassScriptException( - "Expected $this to have ${a(type)} unit " - "(${_unitsByType[type]!.join(', ')}).", - name); + "Expected $this to have ${a(type)} unit " + "(${_unitsByType[type]!.join(', ')}).", + name, + ); } } - var unit = - pluralize('unit', newNumerators.length + newDenominators.length); + var unit = pluralize( + 'unit', + newNumerators.length + newDenominators.length, + ); return SassScriptException( - "Expected $this to have $unit " - "${_unitString(newNumerators, newDenominators)}.", - name); + "Expected $this to have $unit " + "${_unitString(newNumerators, newDenominators)}.", + name, + ); } } @@ -746,7 +784,10 @@ abstract class SassNumber extends Value { if (other is SassNumber) { if (!other.hasUnits) return withValue(value * other.value); return multiplyUnits( - value * other.value, other.numeratorUnits, other.denominatorUnits); + value * other.value, + other.numeratorUnits, + other.denominatorUnits, + ); } throw SassScriptException('Undefined operation "$this * $other".'); } @@ -757,7 +798,10 @@ abstract class SassNumber extends Value { if (other is SassNumber) { if (!other.hasUnits) return withValue(value / other.value); return multiplyUnits( - value / other.value, other.denominatorUnits, other.numeratorUnits); + value / other.value, + other.denominatorUnits, + other.numeratorUnits, + ); } return super.dividedBy(other); } @@ -791,22 +835,28 @@ abstract class SassNumber extends Value { /// /// @nodoc @protected - SassNumber multiplyUnits(double value, List otherNumerators, - List otherDenominators) { + SassNumber multiplyUnits( + double value, + List otherNumerators, + List otherDenominators, + ) { // Short-circuit without allocating any new unit lists if possible. switch (( numeratorUnits, denominatorUnits, otherNumerators, - otherDenominators + otherDenominators, )) { case (var numerators, var denominators, [], []) || ([], [], var numerators, var denominators): case ([], var denominators, var numerators, []) || (var numerators, [], [], var denominators) when !_areAnyConvertible(numerators, denominators): - return SassNumber.withUnits(value, - numeratorUnits: numerators, denominatorUnits: denominators); + return SassNumber.withUnits( + value, + numeratorUnits: numerators, + denominatorUnits: denominators, + ); } var newNumerators = []; @@ -830,19 +880,23 @@ abstract class SassNumber extends Value { }, orElse: () => newNumerators.add(numerator)); } - return SassNumber.withUnits(value, - numeratorUnits: newNumerators, - denominatorUnits: mutableDenominatorUnits - ..addAll(mutableOtherDenominators)); + return SassNumber.withUnits( + value, + numeratorUnits: newNumerators, + denominatorUnits: mutableDenominatorUnits + ..addAll(mutableOtherDenominators), + ); } /// Returns whether there exists a unit in [units1] that can be converted to a /// unit in [units2]. bool _areAnyConvertible(List units1, List units2) => - units1.any((unit1) => switch (_conversions[unit1]) { - var innerMap? => units2.any(innerMap.containsKey), - _ => units2.contains(unit1) - }); + units1.any( + (unit1) => switch (_conversions[unit1]) { + var innerMap? => units2.any(innerMap.containsKey), + _ => units2.contains(unit1), + }, + ); /// Returns a human-readable string representation of [numerators] and /// [denominators]. @@ -864,25 +918,32 @@ abstract class SassNumber extends Value { } if (!hasUnits) return fuzzyEquals(value, other.value); - if (!listEquals(_canonicalizeUnitList(numeratorUnits), - _canonicalizeUnitList(other.numeratorUnits)) || - !listEquals(_canonicalizeUnitList(denominatorUnits), - _canonicalizeUnitList(other.denominatorUnits))) { + if (!listEquals( + _canonicalizeUnitList(numeratorUnits), + _canonicalizeUnitList(other.numeratorUnits), + ) || + !listEquals( + _canonicalizeUnitList(denominatorUnits), + _canonicalizeUnitList(other.denominatorUnits), + )) { return false; } return fuzzyEquals( + value * + _canonicalMultiplier(numeratorUnits) / + _canonicalMultiplier(denominatorUnits), + other.value * + _canonicalMultiplier(other.numeratorUnits) / + _canonicalMultiplier(other.denominatorUnits), + ); + } + + int get hashCode => hashCache ??= fuzzyHashCode( value * _canonicalMultiplier(numeratorUnits) / _canonicalMultiplier(denominatorUnits), - other.value * - _canonicalMultiplier(other.numeratorUnits) / - _canonicalMultiplier(other.denominatorUnits)); - } - - int get hashCode => hashCache ??= fuzzyHashCode(value * - _canonicalMultiplier(numeratorUnits) / - _canonicalMultiplier(denominatorUnits)); + ); /// Converts a unit list (such as [numeratorUnits]) into an equivalent list in /// a canonical form, to make it easier to check whether two numbers have @@ -906,7 +967,9 @@ abstract class SassNumber extends Value { /// That is, if `X units1 == Y units2`, `X * _canonicalMultiplier(units1) == Y /// * _canonicalMultiplier(units2)`. double _canonicalMultiplier(List units) => units.fold( - 1, (multiplier, unit) => multiplier * canonicalMultiplierForUnit(unit)); + 1, + (multiplier, unit) => multiplier * canonicalMultiplierForUnit(unit), + ); /// Returns a multiplier that encapsulates unit equivalence with [unit]. /// diff --git a/lib/src/value/number/complex.dart b/lib/src/value/number/complex.dart index 71c143b0d..391ebf158 100644 --- a/lib/src/value/number/complex.dart +++ b/lib/src/value/number/complex.dart @@ -26,13 +26,17 @@ class ComplexSassNumber extends SassNumber { bool get hasComplexUnits => true; ComplexSassNumber( - double value, List numeratorUnits, List denominatorUnits) - : this._(value, numeratorUnits, denominatorUnits); + double value, + List numeratorUnits, + List denominatorUnits, + ) : this._(value, numeratorUnits, denominatorUnits); ComplexSassNumber._( - double value, this._numeratorUnits, this._denominatorUnits, - [(SassNumber, SassNumber)? asSlash]) - : super.protected(value, asSlash) { + double value, + this._numeratorUnits, + this._denominatorUnits, [ + (SassNumber, SassNumber)? asSlash, + ]) : super.protected(value, asSlash) { assert(numeratorUnits.length > 1 || denominatorUnits.isNotEmpty); } @@ -45,13 +49,16 @@ class ComplexSassNumber extends SassNumber { // This logic is well-defined, and we could implement it in principle. // However, it would be fairly complex and there's no clear need for it yet. throw UnimplementedError( - "ComplexSassNumber.hasPossiblyCompatibleUnits is not implemented."); + "ComplexSassNumber.hasPossiblyCompatibleUnits is not implemented.", + ); } SassNumber withValue(num value) => ComplexSassNumber._(value.toDouble(), numeratorUnits, denominatorUnits); SassNumber withSlash(SassNumber numerator, SassNumber denominator) => - ComplexSassNumber._( - value, numeratorUnits, denominatorUnits, (numerator, denominator)); + ComplexSassNumber._(value, numeratorUnits, denominatorUnits, ( + numerator, + denominator, + )); } diff --git a/lib/src/value/number/single_unit.dart b/lib/src/value/number/single_unit.dart index e5fc09814..94a05a9b9 100644 --- a/lib/src/value/number/single_unit.dart +++ b/lib/src/value/number/single_unit.dart @@ -21,18 +21,18 @@ const _knownCompatibilities = [ "rlh", "vw", "lvw", "svw", "dvw", "vh", "lvh", "svh", "dvh", "vi", "lvi", // "svi", "dvi", "vb", "lvb", "svb", "dvb", "vmin", "lvmin", "svmin", // "dvmin", "vmax", "lvmax", "svmax", "dvmax", "cqw", "cqh", "cqi", "cqb", // - "cqmin", "cqmax", "cm", "mm", "q", "in", "pt", "pc", "px" + "cqmin", "cqmax", "cm", "mm", "q", "in", "pt", "pc", "px", }, {"deg", "grad", "rad", "turn"}, {"s", "ms"}, {"hz", "khz"}, - {"dpi", "dpcm", "dppx"} + {"dpi", "dpcm", "dppx"}, ]; /// A map from units to the other units they're known to be compatible with. final _knownCompatibilitiesByUnit = { for (var set in _knownCompatibilities) - for (var unit in set) unit: set + for (var unit in set) unit: set, }; /// A specialized subclass of [SassNumber] for numbers that have exactly one @@ -50,9 +50,11 @@ class SingleUnitSassNumber extends SassNumber { bool get hasUnits => true; bool get hasComplexUnits => false; - SingleUnitSassNumber(double value, this._unit, - [(SassNumber, SassNumber)? asSlash]) - : super.protected(value, asSlash); + SingleUnitSassNumber( + double value, + this._unit, [ + (SassNumber, SassNumber)? asSlash, + ]) : super.protected(value, asSlash); SassNumber withValue(num value) => SingleUnitSassNumber(value.toDouble(), _unit); @@ -79,14 +81,20 @@ class SingleUnitSassNumber extends SassNumber { bool compatibleWithUnit(String unit) => conversionFactor(_unit, unit) != null; - SassNumber coerceToMatch(SassNumber other, - [String? name, String? otherName]) => + SassNumber coerceToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => (other is SingleUnitSassNumber ? _coerceToUnit(other._unit) : null) ?? // Call this to generate a consistent error message. super.coerceToMatch(other, name, otherName); - double coerceValueToMatch(SassNumber other, - [String? name, String? otherName]) => + double coerceValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => (other is SingleUnitSassNumber ? _coerceValueToUnit(other._unit) : null) ?? @@ -98,30 +106,42 @@ class SingleUnitSassNumber extends SassNumber { // Call this to generate a consistent error message. super.convertValueToUnit(unit, name); - SassNumber convertToMatch(SassNumber other, - [String? name, String? otherName]) => + SassNumber convertToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => (other is SingleUnitSassNumber ? _coerceToUnit(other._unit) : null) ?? // Call this to generate a consistent error message. super.convertToMatch(other, name, otherName); - double convertValueToMatch(SassNumber other, - [String? name, String? otherName]) => + double convertValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => (other is SingleUnitSassNumber ? _coerceValueToUnit(other._unit) : null) ?? // Call this to generate a consistent error message. super.convertValueToMatch(other, name, otherName); - SassNumber coerce(List newNumerators, List newDenominators, - [String? name]) => + SassNumber coerce( + List newNumerators, + List newDenominators, [ + String? name, + ]) => (newNumerators.length == 1 && newDenominators.isEmpty ? _coerceToUnit(newNumerators[0]) : null) ?? // Call this to generate a consistent error message. super.coerce(newNumerators, newDenominators, name); - double coerceValue(List newNumerators, List newDenominators, - [String? name]) => + double coerceValue( + List newNumerators, + List newDenominators, [ + String? name, + ]) => (newNumerators.length == 1 && newDenominators.isEmpty ? _coerceValueToUnit(newNumerators[0]) : null) ?? @@ -137,8 +157,10 @@ class SingleUnitSassNumber extends SassNumber { /// returns `null` if coercion fails. SassNumber? _coerceToUnit(String unit) { if (_unit == unit) return this; - return conversionFactor(unit, _unit) - .andThen((factor) => SingleUnitSassNumber(value * factor, unit)); + return conversionFactor( + unit, + _unit, + ).andThen((factor) => SingleUnitSassNumber(value * factor, unit)); } /// Like [coerceValueToUnit], except that it returns `null` if coercion fails. @@ -146,21 +168,30 @@ class SingleUnitSassNumber extends SassNumber { conversionFactor(unit, _unit).andThen((factor) => value * factor); SassNumber multiplyUnits( - num value, List otherNumerators, List otherDenominators) { + num value, + List otherNumerators, + List otherDenominators, + ) { var newNumerators = otherNumerators; var mutableOtherDenominators = otherDenominators.toList(); - removeFirstWhere(mutableOtherDenominators, (denominator) { - var factor = conversionFactor(denominator, _unit); - if (factor == null) return false; - value *= factor; - return true; - }, orElse: () { - newNumerators = [_unit, ...newNumerators]; - }); - - return SassNumber.withUnits(value, - numeratorUnits: newNumerators, - denominatorUnits: mutableOtherDenominators); + removeFirstWhere( + mutableOtherDenominators, + (denominator) { + var factor = conversionFactor(denominator, _unit); + if (factor == null) return false; + value *= factor; + return true; + }, + orElse: () { + newNumerators = [_unit, ...newNumerators]; + }, + ); + + return SassNumber.withUnits( + value, + numeratorUnits: newNumerators, + denominatorUnits: mutableOtherDenominators, + ); } Value unaryMinus() => SingleUnitSassNumber(-value, _unit); diff --git a/lib/src/value/number/unitless.dart b/lib/src/value/number/unitless.dart index a81d1d9a9..31e5a1353 100644 --- a/lib/src/value/number/unitless.dart +++ b/lib/src/value/number/unitless.dart @@ -36,35 +36,56 @@ class UnitlessSassNumber extends SassNumber { bool compatibleWithUnit(String unit) => true; - SassNumber coerceToMatch(SassNumber other, - [String? name, String? otherName]) => + SassNumber coerceToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => other.withValue(value); - double coerceValueToMatch(SassNumber other, - [String? name, String? otherName]) => + double coerceValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => value; - SassNumber convertToMatch(SassNumber other, - [String? name, String? otherName]) => + SassNumber convertToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => other.hasUnits // Call this to generate a consistent error message. ? super.convertToMatch(other, name, otherName) : this; - double convertValueToMatch(SassNumber other, - [String? name, String? otherName]) => + double convertValueToMatch( + SassNumber other, [ + String? name, + String? otherName, + ]) => other.hasUnits // Call this to generate a consistent error message. ? super.convertValueToMatch(other, name, otherName) : value; - SassNumber coerce(List newNumerators, List newDenominators, - [String? name]) => - SassNumber.withUnits(value, - numeratorUnits: newNumerators, denominatorUnits: newDenominators); - - double coerceValue(List newNumerators, List newDenominators, - [String? name]) => + SassNumber coerce( + List newNumerators, + List newDenominators, [ + String? name, + ]) => + SassNumber.withUnits( + value, + numeratorUnits: newNumerators, + denominatorUnits: newDenominators, + ); + + double coerceValue( + List newNumerators, + List newDenominators, [ + String? name, + ]) => value; double coerceValueToUnit(String unit, [String? name]) => value; @@ -128,9 +149,11 @@ class UnitlessSassNumber extends SassNumber { Value dividedBy(Value other) { if (other is SassNumber) { return other.hasUnits - ? SassNumber.withUnits(value / other.value, + ? SassNumber.withUnits( + value / other.value, numeratorUnits: other.denominatorUnits, - denominatorUnits: other.numeratorUnits) + denominatorUnits: other.numeratorUnits, + ) : UnitlessSassNumber(value / other.value); } return super.dividedBy(other); diff --git a/lib/src/value/string.dart b/lib/src/value/string.dart index e3455d442..164db2e24 100644 --- a/lib/src/value/string.dart +++ b/lib/src/value/string.dart @@ -79,7 +79,7 @@ class SassString extends Value { $a || $A => equalsLetterIgnoreCase($l, text.codeUnitAt(2)) && equalsLetterIgnoreCase($c, text.codeUnitAt(3)) && text.codeUnitAt(4) == $lparen, - _ => false + _ => false, }, $v || $V => equalsLetterIgnoreCase($a, text.codeUnitAt(1)) && equalsLetterIgnoreCase($r, text.codeUnitAt(2)) && @@ -92,9 +92,9 @@ class SassString extends Value { text.codeUnitAt(3) == $lparen, $i || $I => equalsLetterIgnoreCase($n, text.codeUnitAt(2)) && text.codeUnitAt(3) == $lparen, - _ => false + _ => false, }, - _ => false + _ => false, }; } @@ -168,7 +168,9 @@ class SassString extends Value { /// argument name (without the `$`). It's used for error reporting. int sassIndexToStringIndex(Value sassIndex, [String? name]) => codepointIndexToCodeUnitIndex( - text, sassIndexToRuneIndex(sassIndex, name)); + text, + sassIndexToRuneIndex(sassIndex, name), + ); /// Converts [sassIndex] into a Dart-style index into [text]`.runes`. /// @@ -187,8 +189,9 @@ class SassString extends Value { throw SassScriptException("String index may not be 0.", name); } else if (index.abs() > sassLength) { throw SassScriptException( - "Invalid index $sassIndex for a string with $sassLength characters.", - name); + "Invalid index $sassIndex for a string with $sassLength characters.", + name, + ); } return index < 0 ? sassLength + index : index - 1; diff --git a/lib/src/visitor/any_selector.dart b/lib/src/visitor/any_selector.dart index dc6b4591f..a0479f293 100644 --- a/lib/src/visitor/any_selector.dart +++ b/lib/src/visitor/any_selector.dart @@ -13,8 +13,9 @@ import 'interface/selector.dart'; /// Each method returns `false` by default. @internal mixin AnySelectorVisitor implements SelectorVisitor { - bool visitComplexSelector(ComplexSelector complex) => complex.components - .any((component) => visitCompoundSelector(component.selector)); + bool visitComplexSelector(ComplexSelector complex) => complex.components.any( + (component) => visitCompoundSelector(component.selector), + ); bool visitCompoundSelector(CompoundSelector compound) => compound.components.any((simple) => simple.accept(this)); diff --git a/lib/src/visitor/ast_search.dart b/lib/src/visitor/ast_search.dart index 72e45d499..649defdae 100644 --- a/lib/src/visitor/ast_search.dart +++ b/lib/src/visitor/ast_search.dart @@ -51,21 +51,27 @@ mixin AstSearchVisitor on StatementSearchVisitor visitExpression(node.to) ?? super.visitForRule(node); - T? visitForwardRule(ForwardRule node) => node.configuration - .search((variable) => visitExpression(variable.expression)); + T? visitForwardRule(ForwardRule node) => node.configuration.search( + (variable) => visitExpression(variable.expression), + ); T? visitIfRule(IfRule node) => - node.clauses.search((clause) => - visitExpression(clause.expression) ?? - clause.children.search((child) => child.accept(this))) ?? - node.lastClause.andThen((lastClause) => - lastClause.children.search((child) => child.accept(this))); - - T? visitImportRule(ImportRule node) => - node.imports.search((import) => import is StaticImport - ? visitInterpolation(import.url) ?? - import.modifiers.andThen(visitInterpolation) - : null); + node.clauses.search( + (clause) => + visitExpression(clause.expression) ?? + clause.children.search((child) => child.accept(this)), + ) ?? + node.lastClause.andThen( + (lastClause) => + lastClause.children.search((child) => child.accept(this)), + ); + + T? visitImportRule(ImportRule node) => node.imports.search( + (import) => import is StaticImport + ? visitInterpolation(import.url) ?? + import.modifiers.andThen(visitInterpolation) + : null, + ); T? visitIncludeRule(IncludeRule node) => visitArgumentList(node.arguments) ?? super.visitIncludeRule(node); @@ -83,8 +89,9 @@ mixin AstSearchVisitor on StatementSearchVisitor T? visitSupportsRule(SupportsRule node) => visitSupportsCondition(node.condition) ?? super.visitSupportsRule(node); - T? visitUseRule(UseRule node) => node.configuration - .search((variable) => visitExpression(variable.expression)); + T? visitUseRule(UseRule node) => node.configuration.search( + (variable) => visitExpression(variable.expression), + ); T? visitVariableDeclaration(VariableDeclaration node) => visitExpression(node.expression); @@ -142,7 +149,8 @@ mixin AstSearchVisitor on StatementSearchVisitor @protected T? visitCallableDeclaration(CallableDeclaration node) => node.parameters.parameters.search( - (parameter) => parameter.defaultValue.andThen(visitExpression)) ?? + (parameter) => parameter.defaultValue.andThen(visitExpression), + ) ?? super.visitCallableDeclaration(node); /// Visits each expression in an [invocation]. @@ -151,10 +159,12 @@ mixin AstSearchVisitor on StatementSearchVisitor /// argument invocation in a statement. @protected T? visitArgumentList(ArgumentList invocation) => - invocation.positional - .search((expression) => visitExpression(expression)) ?? - invocation.named.values - .search((expression) => visitExpression(expression)) ?? + invocation.positional.search( + (expression) => visitExpression(expression), + ) ?? + invocation.named.values.search( + (expression) => visitExpression(expression), + ) ?? invocation.rest.andThen(visitExpression) ?? invocation.keywordRest.andThen(visitExpression); @@ -170,7 +180,7 @@ mixin AstSearchVisitor on StatementSearchVisitor SupportsInterpolation() => visitExpression(condition.expression), SupportsDeclaration() => visitExpression(condition.name) ?? visitExpression(condition.value), - _ => null + _ => null, }; /// Visits each expression in an [interpolation]. diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index c03d970c2..70dfb415a 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -78,22 +78,24 @@ typedef _ScopeCallback = Future Function( /// declarations. /// /// Throws a [SassRuntimeException] if evaluation fails. -Future evaluateAsync(Stylesheet stylesheet, - {AsyncImportCache? importCache, - NodeImporter? nodeImporter, - AsyncImporter? importer, - Iterable? functions, - Logger? logger, - bool quietDeps = false, - bool sourceMap = false}) => +Future evaluateAsync( + Stylesheet stylesheet, { + AsyncImportCache? importCache, + NodeImporter? nodeImporter, + AsyncImporter? importer, + Iterable? functions, + Logger? logger, + bool quietDeps = false, + bool sourceMap = false, +}) => _EvaluateVisitor( - importCache: importCache, - nodeImporter: nodeImporter, - functions: functions, - logger: logger, - quietDeps: quietDeps, - sourceMap: sourceMap) - .run(importer, stylesheet); + importCache: importCache, + nodeImporter: nodeImporter, + functions: functions, + logger: logger, + quietDeps: quietDeps, + sourceMap: sourceMap, + ).run(importer, stylesheet); /// A class that can evaluate multiple independent statements and expressions /// in the context of a single module. @@ -107,13 +109,16 @@ final class AsyncEvaluator { /// Creates an evaluator. /// /// Arguments are the same as for [evaluateAsync]. - AsyncEvaluator( - {AsyncImportCache? importCache, - AsyncImporter? importer, - Iterable? functions, - Logger? logger}) - : _visitor = _EvaluateVisitor( - importCache: importCache, functions: functions, logger: logger), + AsyncEvaluator({ + AsyncImportCache? importCache, + AsyncImporter? importer, + Iterable? functions, + Logger? logger, + }) : _visitor = _EvaluateVisitor( + importCache: importCache, + functions: functions, + logger: logger, + ), _importer = importer; Future use(UseRule use) => _visitor.runStatement(_importer, use); @@ -331,14 +336,14 @@ final class _EvaluateVisitor /// Creates a new visitor. /// /// Most arguments are the same as those to [evaluateAsync]. - _EvaluateVisitor( - {AsyncImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? functions, - Logger? logger, - bool quietDeps = false, - bool sourceMap = false}) - : _importCache = importCache ?? + _EvaluateVisitor({ + AsyncImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? functions, + Logger? logger, + bool quietDeps = false, + bool sourceMap = false, + }) : _importCache = importCache ?? (nodeImporter == null ? AsyncImportCache.none() : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), @@ -351,43 +356,60 @@ final class _EvaluateVisitor // These functions are defined in the context of the evaluator because // they need access to the [_environment] or other local state. BuiltInCallable.function( - "global-variable-exists", r"$name, $module: null", (arguments) { - var variable = arguments[0].assertString("name"); - var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.globalVariableExists( - variable.text.replaceAll("_", "-"), - namespace: module?.text)); - }, url: "sass:meta"), + "global-variable-exists", + r"$name, $module: null", + (arguments) { + var variable = arguments[0].assertString("name"); + var module = arguments[1].realNull?.assertString("module"); + return SassBoolean( + _environment.globalVariableExists( + variable.text.replaceAll("_", "-"), + namespace: module?.text, + ), + ); + }, + url: "sass:meta", + ), BuiltInCallable.function("variable-exists", r"$name", (arguments) { var variable = arguments[0].assertString("name"); return SassBoolean( - _environment.variableExists(variable.text.replaceAll("_", "-"))); + _environment.variableExists(variable.text.replaceAll("_", "-")), + ); }, url: "sass:meta"), - BuiltInCallable.function("function-exists", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("function-exists", r"$name, $module: null", ( + arguments, + ) { var variable = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.functionExists( + return SassBoolean( + _environment.functionExists( variable.text.replaceAll("_", "-"), - namespace: module?.text) || - _builtInFunctions.containsKey(variable.text)); + namespace: module?.text, + ) || + _builtInFunctions.containsKey(variable.text), + ); }, url: "sass:meta"), - BuiltInCallable.function("mixin-exists", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("mixin-exists", r"$name, $module: null", ( + arguments, + ) { var variable = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.mixinExists( + return SassBoolean( + _environment.mixinExists( variable.text.replaceAll("_", "-"), - namespace: module?.text)); + namespace: module?.text, + ), + ); }, url: "sass:meta"), BuiltInCallable.function("content-exists", "", (arguments) { if (!_environment.inMixin) { throw SassScriptException( - "content-exists() may only be called within a mixin."); + "content-exists() may only be called within a mixin.", + ); } return SassBoolean(_environment.content != null); }, url: "sass:meta"), @@ -401,7 +423,7 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.variables.pairs) - SassString(name): value + SassString(name): value, }); }, url: "sass:meta"), @@ -414,7 +436,7 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.functions.pairs) - SassString(name): SassFunction(value) + SassString(name): SassFunction(value), }); }, url: "sass:meta"), @@ -427,78 +449,98 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.mixins.pairs) - SassString(name): SassMixin(value) + SassString(name): SassMixin(value), }); }, url: "sass:meta"), BuiltInCallable.function( - "get-function", r"$name, $css: false, $module: null", (arguments) { - var name = arguments[0].assertString("name"); - var css = arguments[1].isTruthy; - var module = arguments[2].realNull?.assertString("module"); - - if (css) { - if (module != null) { - throw r"$css and $module may not both be passed at once."; + "get-function", + r"$name, $css: false, $module: null", + (arguments) { + var name = arguments[0].assertString("name"); + var css = arguments[1].isTruthy; + var module = arguments[2].realNull?.assertString("module"); + + if (css) { + if (module != null) { + throw r"$css and $module may not both be passed at once."; + } + return SassFunction(PlainCssCallable(name.text)); } - return SassFunction(PlainCssCallable(name.text)); - } - var callable = _addExceptionSpan(_callableNode!, () { - var normalizedName = name.text.replaceAll("_", "-"); - var namespace = module?.text; - var local = - _environment.getFunction(normalizedName, namespace: namespace); - if (local != null || namespace != null) return local; - return _builtInFunctions[normalizedName]; - }); - if (callable == null) throw "Function not found: $name"; + var callable = _addExceptionSpan(_callableNode!, () { + var normalizedName = name.text.replaceAll("_", "-"); + var namespace = module?.text; + var local = _environment.getFunction( + normalizedName, + namespace: namespace, + ); + if (local != null || namespace != null) return local; + return _builtInFunctions[normalizedName]; + }); + if (callable == null) throw "Function not found: $name"; - return SassFunction(callable); - }, url: "sass:meta"), + return SassFunction(callable); + }, + url: "sass:meta", + ), - BuiltInCallable.function("get-mixin", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("get-mixin", r"$name, $module: null", ( + arguments, + ) { var name = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); var callable = _addExceptionSpan( - _callableNode!, - () => _environment.getMixin(name.text.replaceAll("_", "-"), - namespace: module?.text)); + _callableNode!, + () => _environment.getMixin( + name.text.replaceAll("_", "-"), + namespace: module?.text, + ), + ); if (callable == null) throw "Mixin not found: $name"; return SassMixin(callable); }, url: "sass:meta"), - AsyncBuiltInCallable.function("call", r"$function, $args...", - (arguments) async { + AsyncBuiltInCallable.function("call", r"$function, $args...", ( + arguments, + ) async { var function = arguments[0]; var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentList([], {}, callableNode.span, - rest: ValueExpression(args, callableNode.span), - keywordRest: args.keywords.isEmpty - ? null - : ValueExpression( - SassMap({ - for (var (name, value) in args.keywords.pairs) - SassString(name, quotes: false): value - }), - callableNode.span)); + var invocation = ArgumentList( + [], + {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + keywordRest: args.keywords.isEmpty + ? null + : ValueExpression( + SassMap({ + for (var (name, value) in args.keywords.pairs) + SassString(name, quotes: false): value, + }), + callableNode.span, + ), + ); if (function is SassString) { warnForDeprecation( - "Passing a string to call() is deprecated and will be illegal in " - "Dart Sass 2.0.0.\n" - "\n" - "Recommendation: call(get-function($function))", - Deprecation.callString); + "Passing a string to call() is deprecated and will be illegal in " + "Dart Sass 2.0.0.\n" + "\n" + "Recommendation: call(get-function($function))", + Deprecation.callString, + ); var callableNode = _callableNode!; - var expression = - FunctionExpression(function.text, invocation, callableNode.span); + var expression = FunctionExpression( + function.text, + invocation, + callableNode.span, + ); return await expression.accept(this); } @@ -506,18 +548,23 @@ final class _EvaluateVisitor // ignore: unnecessary_type_check if (callable is AsyncCallable) { return await _runFunctionCallable( - invocation, callable, _callableNode!); + invocation, + callable, + _callableNode!, + ); } else { throw SassScriptException( - "The function ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + "The function ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin.", + ); } - }, url: "sass:meta") + }, url: "sass:meta"), ]; var metaMixins = [ - AsyncBuiltInCallable.mixin("load-css", r"$url, $with: null", - (arguments) async { + AsyncBuiltInCallable.mixin("load-css", r"$url, $with: null", ( + arguments, + ) async { var url = Uri.parse(arguments[0].assertString("url").text); var withMap = arguments[1].realNull?.assertMap("with").contents; @@ -538,44 +585,61 @@ final class _EvaluateVisitor configuration = ExplicitConfiguration(values, callableNode); } - await _loadModule(url, "load-css()", callableNode, - (module, _) => _combineCss(module, clone: true).accept(this), - baseUrl: callableNode.span.sourceUrl, - configuration: configuration, - namesInErrors: true); + await _loadModule( + url, + "load-css()", + callableNode, + (module, _) => _combineCss(module, clone: true).accept(this), + baseUrl: callableNode.span.sourceUrl, + configuration: configuration, + namesInErrors: true, + ); _assertConfigurationIsEmpty(configuration, nameInError: true); }, url: "sass:meta"), - AsyncBuiltInCallable.mixin("apply", r"$mixin, $args...", - (arguments) async { - var mixin = arguments[0]; - var args = arguments[1] as SassArgumentList; - - var callableNode = _callableNode!; - var invocation = ArgumentList( - const [], - const {}, - callableNode.span, - rest: ValueExpression(args, callableNode.span), - ); - - var callable = mixin.assertMixin("mixin").callable; - var content = _environment.content; + AsyncBuiltInCallable.mixin( + "apply", + r"$mixin, $args...", + (arguments) async { + var mixin = arguments[0]; + var args = arguments[1] as SassArgumentList; - // ignore: unnecessary_type_check - if (callable is AsyncCallable) { - await _applyMixin( - callable, content, invocation, callableNode, callableNode); - } else { - throw SassScriptException( + var callableNode = _callableNode!; + var invocation = ArgumentList( + const [], + const {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + ); + + var callable = mixin.assertMixin("mixin").callable; + var content = _environment.content; + + // ignore: unnecessary_type_check + if (callable is AsyncCallable) { + await _applyMixin( + callable, + content, + invocation, + callableNode, + callableNode, + ); + } else { + throw SassScriptException( "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); - } - }, url: "sass:meta", acceptsContent: true), + "This is probably caused by a bug in a Sass plugin.", + ); + } + }, + url: "sass:meta", + acceptsContent: true, + ), ]; - var metaModule = BuiltInModule("meta", - functions: [...meta.moduleFunctions, ...metaFunctions], - mixins: metaMixins); + var metaModule = BuiltInModule( + "meta", + functions: [...meta.moduleFunctions, ...metaFunctions], + mixins: metaMixins, + ); for (var module in [...coreModules, metaModule]) { _builtInModules[module.url] = module; @@ -586,8 +650,8 @@ final class _EvaluateVisitor ...globalFunctions, ...[ for (var function in metaFunctions) - function.withDeprecationWarning('meta') - ] + function.withDeprecationWarning('meta'), + ], ]; for (var function in functions) { _builtInFunctions[function.name.replaceAll("_", "-")] = function; @@ -596,16 +660,20 @@ final class _EvaluateVisitor Future run(AsyncImporter? importer, Stylesheet node) async { try { - return await withEvaluationContext(_EvaluationContext(this, node), - () async { - if (node.span.sourceUrl case var url?) { - _activeModules[url] = null; - if (!(_asNodeSass && url.toString() == 'stdin')) _loadedUrls.add(url); - } + return await withEvaluationContext( + _EvaluationContext(this, node), + () async { + if (node.span.sourceUrl case var url?) { + _activeModules[url] = null; + if (!(_asNodeSass && url.toString() == 'stdin')) { + _loadedUrls.add(url); + } + } - var module = await _addExceptionTrace(() => _execute(importer, node)); - return (stylesheet: _combineCss(module), loadedUrls: _loadedUrls); - }); + var module = await _addExceptionTrace(() => _execute(importer, node)); + return (stylesheet: _combineCss(module), loadedUrls: _loadedUrls); + }, + ); } on SassException catch (error, stackTrace) { throwWithTrace(error.withLoadedUrls(_loadedUrls), error, stackTrace); } @@ -613,15 +681,23 @@ final class _EvaluateVisitor Future runExpression(AsyncImporter? importer, Expression expression) => withEvaluationContext( - _EvaluationContext(this, expression), - () => _withFakeStylesheet(importer, expression, - () => _addExceptionTrace(() => expression.accept(this)))); + _EvaluationContext(this, expression), + () => _withFakeStylesheet( + importer, + expression, + () => _addExceptionTrace(() => expression.accept(this)), + ), + ); Future runStatement(AsyncImporter? importer, Statement statement) => withEvaluationContext( - _EvaluationContext(this, statement), - () => _withFakeStylesheet(importer, statement, - () => _addExceptionTrace(() => statement.accept(this)))); + _EvaluationContext(this, statement), + () => _withFakeStylesheet( + importer, + statement, + () => _addExceptionTrace(() => statement.accept(this)), + ), + ); /// Asserts that [value] is not `null` and returns it. /// @@ -635,8 +711,11 @@ final class _EvaluateVisitor /// Runs [callback] with [importer] as [_importer] and a fake [_stylesheet] /// with [nodeWithSpan]'s source span. - Future _withFakeStylesheet(AsyncImporter? importer, - AstNode nodeWithSpan, FutureOr callback()) async { + Future _withFakeStylesheet( + AsyncImporter? importer, + AstNode nodeWithSpan, + FutureOr callback(), + ) async { var oldImporter = _importer; _importer = importer; @@ -669,31 +748,40 @@ final class _EvaluateVisitor /// /// The [stackFrame] and [nodeWithSpan] are used for the name and location of /// the stack frame for the duration of the [callback]. - Future _loadModule(Uri url, String stackFrame, AstNode nodeWithSpan, - FutureOr callback(Module module, bool firstLoad), - {Uri? baseUrl, - Configuration? configuration, - bool namesInErrors = false}) async { + Future _loadModule( + Uri url, + String stackFrame, + AstNode nodeWithSpan, + FutureOr callback(Module module, bool firstLoad), { + Uri? baseUrl, + Configuration? configuration, + bool namesInErrors = false, + }) async { if (_builtInModules[url] case var builtInModule?) { if (configuration is ExplicitConfiguration) { throw _exception( - namesInErrors - ? "Built-in module $url can't be configured." - : "Built-in modules can't be configured.", - configuration.nodeWithSpan.span); + namesInErrors + ? "Built-in module $url can't be configured." + : "Built-in modules can't be configured.", + configuration.nodeWithSpan.span, + ); } // Always consider built-in stylesheets to be "already loaded", since they // never require additional execution to load and never produce CSS. await _addExceptionSpanAsync( - nodeWithSpan, () => callback(builtInModule, false)); + nodeWithSpan, + () => callback(builtInModule, false), + ); return; } await _withStackFrame(stackFrame, nodeWithSpan, () async { var (stylesheet, :importer, :isDependency) = await _loadStylesheet( - url.toString(), nodeWithSpan.span, - baseUrl: baseUrl); + url.toString(), + nodeWithSpan.span, + baseUrl: baseUrl, + ); var canonicalUrl = stylesheet.span.sourceUrl; if (canonicalUrl != null) { @@ -703,9 +791,11 @@ final class _EvaluateVisitor "loaded." : "Module loop: this module is already being loaded."; - throw _activeModules[canonicalUrl].andThen((previousLoad) => - _multiSpanException(message, "new load", - {previousLoad.span: "original load"})) ?? + throw _activeModules[canonicalUrl].andThen( + (previousLoad) => _multiSpanException(message, "new load", { + previousLoad.span: "original load", + }), + ) ?? _exception(message); } else { _activeModules[canonicalUrl] = nodeWithSpan; @@ -717,18 +807,23 @@ final class _EvaluateVisitor _inDependency = isDependency; Module module; try { - module = await _execute(importer, stylesheet, - configuration: configuration, - nodeWithSpan: nodeWithSpan, - namesInErrors: namesInErrors); + module = await _execute( + importer, + stylesheet, + configuration: configuration, + nodeWithSpan: nodeWithSpan, + namesInErrors: namesInErrors, + ); } finally { _activeModules.remove(canonicalUrl); _inDependency = oldInDependency; } await _addExceptionSpanAsync( - nodeWithSpan, () => callback(module, firstLoad), - addStackFrame: false); + nodeWithSpan, + () => callback(module, firstLoad), + addStackFrame: false, + ); }); } @@ -740,10 +835,13 @@ final class _EvaluateVisitor /// If [namesInErrors] is `true`, this includes the names of modules in errors /// relating to them. This should only be `true` if the names won't be obvious /// from the source span. - Future _execute(AsyncImporter? importer, Stylesheet stylesheet, - {Configuration? configuration, - AstNode? nodeWithSpan, - bool namesInErrors = false}) async { + Future _execute( + AsyncImporter? importer, + Stylesheet stylesheet, { + Configuration? configuration, + AstNode? nodeWithSpan, + bool namesInErrors = false, + }) async { var url = stylesheet.span.sourceUrl; if (_modules[url] case var alreadyLoaded?) { @@ -762,7 +860,7 @@ final class _EvaluateVisitor : null; var secondarySpans = { if (existingSpan != null) existingSpan: "original load", - if (configurationSpan != null) configurationSpan: "configuration" + if (configurationSpan != null) configurationSpan: "configuration", }; throw secondarySpans.isEmpty @@ -832,7 +930,10 @@ final class _EvaluateVisitor }); var module = environment.toModule( - css, preModuleComments ?? const {}, extensionStore); + css, + preModuleComments ?? const {}, + extensionStore, + ); if (url != null) { _modules[url] = module; _moduleConfigurations[url] = _configuration; @@ -850,8 +951,8 @@ final class _EvaluateVisitor var outOfOrderImports => [ ..._root.children.take(_endOfImports), ...outOfOrderImports, - ..._root.children.skip(_endOfImports) - ] + ..._root.children.skip(_endOfImports), + ], }; /// Returns a new stylesheet containing [root]'s CSS as well as the CSS of all @@ -943,11 +1044,15 @@ final class _EvaluateVisitor // Add all as-yet-unsatisfied extensions before adding downstream // [ExtensionStore]s, because those are all in [unsatisfiedExtensions] // already. - unsatisfiedExtensions.addAll(module.extensionStore.extensionsWhereTarget( - (target) => !originalSelectors.contains(target))); + unsatisfiedExtensions.addAll( + module.extensionStore.extensionsWhereTarget( + (target) => !originalSelectors.contains(target), + ), + ); - downstreamExtensionStores[module.url] - .andThen(module.extensionStore.addExtensions); + downstreamExtensionStores[module.url].andThen( + module.extensionStore.addExtensions, + ); if (module.extensionStore.isEmpty) continue; for (var upstream in module.upstream) { @@ -961,8 +1066,9 @@ final class _EvaluateVisitor // Remove all extensions that are now satisfied after adding downstream // [ExtensionStore]s so it counts any downstream extensions that have been // newly satisfied. - unsatisfiedExtensions.removeAll(module.extensionStore - .extensionsWhereTarget(originalSelectors.contains)); + unsatisfiedExtensions.removeAll( + module.extensionStore.extensionsWhereTarget(originalSelectors.contains), + ); } if (unsatisfiedExtensions.isNotEmpty) { @@ -973,9 +1079,10 @@ final class _EvaluateVisitor /// Throws an exception indicating that [extension] is unsatisfied. Never _throwForUnsatisfiedExtension(Extension extension) { throw SassException( - 'The target selector was not found.\n' - 'Use "@extend ${extension.target} !optional" to avoid this error.', - extension.span); + 'The target selector was not found.\n' + 'Use "@extend ${extension.target} !optional" to avoid this error.', + extension.span, + ); } /// Returns the index of the first node in [statements] that comes after all @@ -1010,7 +1117,8 @@ final class _EvaluateVisitor // module's definition, even if their assignments aren't reached. for (var (name, span) in node.globalVariables.pairs) { visitVariableDeclaration( - VariableDeclaration(name, NullExpression(span), span, guarded: true)); + VariableDeclaration(name, NullExpression(span), span, guarded: true), + ); } return null; @@ -1019,8 +1127,10 @@ final class _EvaluateVisitor Future visitAtRootRule(AtRootRule node) async { var query = AtRootQuery.defaultQuery; if (node.query case var unparsedQuery?) { - var (resolved, map) = - await _performInterpolationWithMap(unparsedQuery, warnForColor: true); + var (resolved, map) = await _performInterpolationWithMap( + unparsedQuery, + warnForColor: true, + ); query = AtRootQuery.parse(resolved, interpolationMap: map); } @@ -1033,7 +1143,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw StateError( - "CssNodes must have a CssStylesheet transitive parent node."); + "CssNodes must have a CssStylesheet transitive parent node.", + ); } } var root = _trimIncluded(included); @@ -1094,7 +1205,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw ArgumentError( - "Expected ${nodes[i]} to be an ancestor of $this."); + "Expected ${nodes[i]} to be an ancestor of $this.", + ); } } innermostContiguous ??= i; @@ -1118,10 +1230,11 @@ final class _EvaluateVisitor /// duration, based on which rules are excluded by [query]. It always assigns /// [_parent] to [newParent]. _ScopeCallback _scopeForAtRoot( - AtRootRule node, - ModifiableCssParentNode newParent, - AtRootQuery query, - List included) { + AtRootRule node, + ModifiableCssParentNode newParent, + AtRootQuery query, + List included, + ) { var scope = (Future callback()) async { // We can't use [_withParent] here because it'll add the node to the tree // in the wrong place. @@ -1171,7 +1284,8 @@ final class _EvaluateVisitor } Future visitContentBlock(ContentBlock node) => throw UnsupportedError( - "Evaluation handles @include and its content block together."); + "Evaluation handles @include and its content block together.", + ); Future visitContentRule(ContentRule node) async { var content = _environment.content; @@ -1190,20 +1304,24 @@ final class _EvaluateVisitor Future visitDebugRule(DebugRule node) async { var value = await node.expression.accept(this); _logger.debug( - value is SassString ? value.text : serializeValue(value, inspect: true), - node.span); + value is SassString ? value.text : serializeValue(value, inspect: true), + node.span, + ); return null; } Future visitDeclaration(Declaration node) async { if (_styleRule == null && !_inUnknownAtRule && !_inKeyframes) { throw _exception( - "Declarations may only be used within style rules.", node.span); + "Declarations may only be used within style rules.", + node.span, + ); } if (_declarationName != null && node.isCustomProperty) { throw _exception( - 'Declarations whose names begin with "--" may not be nested.', - node.span); + 'Declarations whose names begin with "--" may not be nested.', + node.span, + ); } var siblings = _parent.parent!.children; @@ -1226,18 +1344,20 @@ final class _EvaluateVisitor // add no specificity and they're nested in the same parent as this // declaration. _warn( - "Sass's behavior for declarations that appear after nested\n" - "rules will be changing to match the behavior specified by CSS " - "in an upcoming\n" - "version. To keep the existing behavior, move the declaration " - "above the nested\n" - "rule. To opt into the new behavior, wrap the declaration in " - "`& {}`.\n" - "\n" - "More info: https://sass-lang.com/d/mixed-decls", - MultiSpan( - node.span, 'declaration', {sibling.span: 'nested rule'}), - Deprecation.mixedDecls); + "Sass's behavior for declarations that appear after nested\n" + "rules will be changing to match the behavior specified by CSS " + "in an upcoming\n" + "version. To keep the existing behavior, move the declaration " + "above the nested\n" + "rule. To opt into the new behavior, wrap the declaration in " + "`& {}`.\n" + "\n" + "More info: https://sass-lang.com/d/mixed-decls", + MultiSpan(node.span, 'declaration', { + sibling.span: 'nested rule', + }), + Deprecation.mixedDecls, + ); interleavedRules.clear(); break; } @@ -1254,16 +1374,23 @@ final class _EvaluateVisitor // If the value is an empty list, preserve it, because converting it to CSS // will throw an error that we want the user to see. if (!value.isBlank || _isEmptyList(value)) { - _parent.addChild(ModifiableCssDeclaration( - name, CssValue(value, expression.span), node.span, + _parent.addChild( + ModifiableCssDeclaration( + name, + CssValue(value, expression.span), + node.span, parsedAsCustomProperty: node.isCustomProperty, interleavedRules: interleavedRules, trace: interleavedRules.isEmpty ? null : _stackTrace(node.span), valueSpanForMap: - _sourceMap ? node.value.andThen(_expressionNode)?.span : null)); + _sourceMap ? node.value.andThen(_expressionNode)?.span : null, + ), + ); } else if (name.value.startsWith('--')) { throw _exception( - "Custom property values may not be empty.", expression.span); + "Custom property values may not be empty.", + expression.span, + ); } } @@ -1289,15 +1416,20 @@ final class _EvaluateVisitor var nodeWithSpan = _expressionNode(node.list); var setVariables = switch (node.variables) { [var variable] => (Value value) => _environment.setLocalVariable( - variable, _withoutSlash(value, nodeWithSpan), nodeWithSpan), + variable, + _withoutSlash(value, nodeWithSpan), + nodeWithSpan, + ), var variables => (Value value) => - _setMultipleVariables(variables, value, nodeWithSpan) + _setMultipleVariables(variables, value, nodeWithSpan), }; return _environment.scope(() { return _handleReturn(list.asList, (element) { setVariables(element); return _handleReturn( - node.children, (child) => child.accept(this)); + node.children, + (child) => child.accept(this), + ); }); }, semiGlobal: true); } @@ -1305,12 +1437,18 @@ final class _EvaluateVisitor /// Destructures [value] and assigns it to [variables], as in an `@each` /// statement. void _setMultipleVariables( - List variables, Value value, AstNode nodeWithSpan) { + List variables, + Value value, + AstNode nodeWithSpan, + ) { var list = value.asList; var minLength = math.min(variables.length, list.length); for (var i = 0; i < minLength; i++) { _environment.setLocalVariable( - variables[i], _withoutSlash(list[i], nodeWithSpan), nodeWithSpan); + variables[i], + _withoutSlash(list[i], nodeWithSpan), + nodeWithSpan, + ); } for (var i = minLength; i < variables.length; i++) { _environment.setLocalVariable(variables[i], sassNull, nodeWithSpan); @@ -1319,35 +1457,46 @@ final class _EvaluateVisitor Future visitErrorRule(ErrorRule node) async { throw _exception( - (await node.expression.accept(this)).toString(), node.span); + (await node.expression.accept(this)).toString(), + node.span, + ); } Future visitExtendRule(ExtendRule node) async { var styleRule = _styleRule; if (styleRule == null || _declarationName != null) { throw _exception( - "@extend may only be used within style rules.", node.span); + "@extend may only be used within style rules.", + node.span, + ); } for (var complex in styleRule.originalSelector.components) { if (!complex.isBogus) continue; _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS and ' + - (complex.isUseless ? "can't" : "shouldn't") + - ' be an extender.\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - MultiSpan(complex.span.trimRight(), 'invalid selector', - {node.span: '@extend rule'}), - Deprecation.bogusCombinators); + 'The selector "${complex.toString().trim()}" is invalid CSS and ' + + (complex.isUseless ? "can't" : "shouldn't") + + ' be an extender.\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + MultiSpan(complex.span.trimRight(), 'invalid selector', { + node.span: '@extend rule', + }), + Deprecation.bogusCombinators, + ); } - var (targetText, targetMap) = - await _performInterpolationWithMap(node.selector, warnForColor: true); + var (targetText, targetMap) = await _performInterpolationWithMap( + node.selector, + warnForColor: true, + ); - var list = SelectorList.parse(trimAscii(targetText, excludeEscape: true), - interpolationMap: targetMap, allowParent: false); + var list = SelectorList.parse( + trimAscii(targetText, excludeEscape: true), + interpolationMap: targetMap, + allowParent: false, + ); for (var complex in list.components) { var compound = complex.singleCompound; @@ -1355,20 +1504,27 @@ final class _EvaluateVisitor // If the selector was a compound selector but not a simple // selector, emit a more explicit error. throw SassFormatException( - "complex selectors may not be extended.", complex.span); + "complex selectors may not be extended.", + complex.span, + ); } var simple = compound.singleSimple; if (simple == null) { throw SassFormatException( - "compound selectors may no longer be extended.\n" - "Consider `@extend ${compound.components.join(', ')}` instead.\n" - "See https://sass-lang.com/d/extend-compound for details.\n", - compound.span); + "compound selectors may no longer be extended.\n" + "Consider `@extend ${compound.components.join(', ')}` instead.\n" + "See https://sass-lang.com/d/extend-compound for details.\n", + compound.span, + ); } _extensionStore.addExtension( - styleRule.selector, simple, node, _mediaQueries); + styleRule.selector, + simple, + node, + _mediaQueries, + ); } return null; @@ -1380,18 +1536,22 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "At-rules may not be used within nested declarations.", node.span); + "At-rules may not be used within nested declarations.", + node.span, + ); } var name = await _interpolationToValue(node.name); - var value = await node.value.andThen((value) => - _interpolationToValue(value, trim: true, warnForColor: true)); + var value = await node.value.andThen( + (value) => _interpolationToValue(value, trim: true, warnForColor: true), + ); var children = node.children; if (children == null) { _parent.addChild( - ModifiableCssAtRule(name, node.span, childless: true, value: value)); + ModifiableCssAtRule(name, node.span, childless: true, value: value), + ); return null; } @@ -1403,29 +1563,31 @@ final class _EvaluateVisitor _inUnknownAtRule = true; } - await _withParent(ModifiableCssAtRule(name, node.span, value: value), - () async { - var styleRule = _styleRule; - if (styleRule == null || _inKeyframes || name.value == 'font-face') { - // Special-cased at-rules within style blocks are pulled out to the - // root. Equivalent to prepending "@at-root" on them. - for (var child in children) { - await child.accept(this); - } - } else { - // If we're in a style rule, copy it into the at-rule so that - // declarations immediately inside it have somewhere to go. - // - // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". - await _withParent(styleRule.copyWithoutChildren(), () async { + await _withParent( + ModifiableCssAtRule(name, node.span, value: value), + () async { + var styleRule = _styleRule; + if (styleRule == null || _inKeyframes || name.value == 'font-face') { + // Special-cased at-rules within style blocks are pulled out to the + // root. Equivalent to prepending "@at-root" on them. for (var child in children) { await child.accept(this); } - }, scopeWhen: false); - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + } else { + // If we're in a style rule, copy it into the at-rule so that + // declarations immediately inside it have somewhere to go. + // + // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". + await _withParent(styleRule.copyWithoutChildren(), () async { + for (var child in children) { + await child.accept(this); + } + }, scopeWhen: false); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); _inUnknownAtRule = wasInUnknownAtRule; _inKeyframes = wasInKeyframes; @@ -1434,16 +1596,21 @@ final class _EvaluateVisitor Future visitForRule(ForRule node) async { var fromNumber = await _addExceptionSpanAsync( - node.from, () async => (await node.from.accept(this)).assertNumber()); + node.from, + () async => (await node.from.accept(this)).assertNumber(), + ); var toNumber = await _addExceptionSpanAsync( - node.to, () async => (await node.to.accept(this)).assertNumber()); + node.to, + () async => (await node.to.accept(this)).assertNumber(), + ); var from = _addExceptionSpan(node.from, () => fromNumber.assertInt()); var to = _addExceptionSpan( - node.to, - () => toNumber - .coerce(fromNumber.numeratorUnits, fromNumber.denominatorUnits) - .assertInt()); + node.to, + () => toNumber + .coerce(fromNumber.numeratorUnits, fromNumber.denominatorUnits) + .assertInt(), + ); var direction = from > to ? -1 : 1; if (!node.isExclusive) to += direction; @@ -1453,13 +1620,18 @@ final class _EvaluateVisitor var nodeWithSpan = _expressionNode(node.from); for (var i = from; i != to; i += direction) { _environment.setLocalVariable( - node.variable, - SassNumber.withUnits(i, - numeratorUnits: fromNumber.numeratorUnits, - denominatorUnits: fromNumber.denominatorUnits), - nodeWithSpan); + node.variable, + SassNumber.withUnits( + i, + numeratorUnits: fromNumber.numeratorUnits, + denominatorUnits: fromNumber.denominatorUnits, + ), + nodeWithSpan, + ); if (await _handleReturn( - node.children, (child) => child.accept(this)) + node.children, + (child) => child.accept(this), + ) case var result?) { return result; } @@ -1473,26 +1645,31 @@ final class _EvaluateVisitor var adjustedConfiguration = oldConfiguration.throughForward(node); if (node.configuration.isNotEmpty) { - var newConfiguration = - await _addForwardConfiguration(adjustedConfiguration, node); + var newConfiguration = await _addForwardConfiguration( + adjustedConfiguration, + node, + ); await _loadModule(node.url, "@forward", node, (module, firstLoad) { if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }, configuration: newConfiguration); - _removeUsedConfiguration(adjustedConfiguration, newConfiguration, - except: { - for (var variable in node.configuration) - if (!variable.isGuarded) variable.name - }); + _removeUsedConfiguration( + adjustedConfiguration, + newConfiguration, + except: { + for (var variable in node.configuration) + if (!variable.isGuarded) variable.name, + }, + ); // Remove all the variables that weren't configured by this particular // `@forward` before checking that the configuration is empty. Errors for // outer `with` clauses will be thrown once those clauses finish // executing. var configuredVariables = { - for (var variable in node.configuration) variable.name + for (var variable in node.configuration) variable.name, }; for (var name in newConfiguration.values.keys.toList()) { if (!configuredVariables.contains(name)) newConfiguration.remove(name); @@ -1514,7 +1691,9 @@ final class _EvaluateVisitor /// Updates [configuration] to include [node]'s configuration and returns the /// result. Future _addForwardConfiguration( - Configuration configuration, ForwardRule node) async { + Configuration configuration, + ForwardRule node, + ) async { var newValues = Map.of(configuration.values); for (var variable in node.configuration) { if (variable.isGuarded) { @@ -1527,10 +1706,13 @@ final class _EvaluateVisitor var variableNodeWithSpan = _expressionNode(variable.expression); newValues[variable.name] = ConfiguredValue.explicit( - _withoutSlash( - await variable.expression.accept(this), variableNodeWithSpan), - variable.span, - variableNodeWithSpan); + _withoutSlash( + await variable.expression.accept(this), + variableNodeWithSpan, + ), + variable.span, + variableNodeWithSpan, + ); } if (configuration is ExplicitConfiguration || configuration.isEmpty) { @@ -1557,8 +1739,10 @@ final class _EvaluateVisitor /// Remove configured values from [upstream] that have been removed from /// [downstream], unless they match a name in [except]. void _removeUsedConfiguration( - Configuration upstream, Configuration downstream, - {required Set except}) { + Configuration upstream, + Configuration downstream, { + required Set except, + }) { for (var name in upstream.values.keys.toList()) { if (except.contains(name)) continue; if (!downstream.values.containsKey(name)) upstream.remove(name); @@ -1573,8 +1757,10 @@ final class _EvaluateVisitor /// If [nameInError] is `true`, this includes the name of the configured /// variable in the error message. This should only be `true` if the name /// won't be obvious from the source span. - void _assertConfigurationIsEmpty(Configuration configuration, - {bool nameInError = false}) { + void _assertConfigurationIsEmpty( + Configuration configuration, { + bool nameInError = false, + }) { // By definition, implicit configurations are allowed to only use a subset // of their values. if (configuration is! ExplicitConfiguration) return; @@ -1582,17 +1768,23 @@ final class _EvaluateVisitor var (name, value) = configuration.values.pairs.first; throw _exception( - nameInError - ? "\$$name was not declared with !default in the @used " - "module." - : "This variable was not declared with !default in the @used " - "module.", - value.configurationSpan); + nameInError + ? "\$$name was not declared with !default in the @used " + "module." + : "This variable was not declared with !default in the @used " + "module.", + value.configurationSpan, + ); } Future visitFunctionRule(FunctionRule node) async { - _environment.setFunction(UserDefinedCallable(node, _environment.closure(), - inDependency: _inDependency)); + _environment.setFunction( + UserDefinedCallable( + node, + _environment.closure(), + inDependency: _inDependency, + ), + ); return null; } @@ -1605,11 +1797,16 @@ final class _EvaluateVisitor } } - return await clause.andThen>((clause) => _environment.scope( + return await clause.andThen>( + (clause) => _environment.scope( () => _handleReturn( - clause.children, (child) => child.accept(this)), + clause.children, + (child) => child.accept(this), + ), semiGlobal: true, - when: clause.hasDeclarations)); + when: clause.hasDeclarations, + ), + ); } Future visitImportRule(ImportRule node) async { @@ -1626,15 +1823,22 @@ final class _EvaluateVisitor /// Adds the stylesheet imported by [import] to the current document. Future _visitDynamicImport(DynamicImport import) { return _withStackFrame("@import", import, () async { - var (stylesheet, :importer, :isDependency) = - await _loadStylesheet(import.urlString, import.span, forImport: true); + var (stylesheet, :importer, :isDependency) = await _loadStylesheet( + import.urlString, + import.span, + forImport: true, + ); var url = stylesheet.span.sourceUrl; if (url != null) { if (_activeModules.containsKey(url)) { - throw _activeModules[url].andThen((previousLoad) => - _multiSpanException("This file is already being loaded.", - "new load", {previousLoad.span: "original load"})) ?? + throw _activeModules[url].andThen( + (previousLoad) => _multiSpanException( + "This file is already being loaded.", + "new load", + {previousLoad.span: "original load"}, + ), + ) ?? _exception("This file is already being loaded."); } _activeModules[url] = import; @@ -1721,9 +1925,10 @@ final class _EvaluateVisitor // clone all modules' CSS. Otherwise, it's possible that they'll be // used or imported from another location that shouldn't have the same // extensions applied. - await _combineCss(module, - clone: module.transitivelyContainsExtensions) - .accept(this); + await _combineCss( + module, + clone: module.transitivelyContainsExtensions, + ).accept(this); } var visitor = _ImportedCssVisitor(this); @@ -1741,23 +1946,32 @@ final class _EvaluateVisitor /// /// This first tries loading [url] relative to [baseUrl], which defaults to /// `_stylesheet.span.sourceUrl`. - Future<_LoadedStylesheet> _loadStylesheet(String url, FileSpan span, - {Uri? baseUrl, bool forImport = false}) async { + Future<_LoadedStylesheet> _loadStylesheet( + String url, + FileSpan span, { + Uri? baseUrl, + bool forImport = false, + }) async { try { assert(_importSpan == null); _importSpan = span; if (_importCache case var importCache?) { baseUrl ??= _stylesheet.span.sourceUrl; - if (await importCache.canonicalize(Uri.parse(url), - baseImporter: _importer, baseUrl: baseUrl, forImport: forImport) + if (await importCache.canonicalize( + Uri.parse(url), + baseImporter: _importer, + baseUrl: baseUrl, + forImport: forImport, + ) case (var importer, var canonicalUrl, :var originalUrl)) { if (canonicalUrl.scheme == '') { _logger.warnForDeprecation( - Deprecation.relativeCanonical, - "Importer $importer canonicalized $url to $canonicalUrl.\n" - "Relative canonical URLs are deprecated and will eventually be " - "disallowed."); + Deprecation.relativeCanonical, + "Importer $importer canonicalized $url to $canonicalUrl.\n" + "Relative canonical URLs are deprecated and will eventually be " + "disallowed.", + ); } // Make sure we record the canonical URL as "loaded" even if the // actual load fails, because watchers should watch it to see if it @@ -1765,8 +1979,11 @@ final class _EvaluateVisitor _loadedUrls.add(canonicalUrl); var isDependency = _inDependency || importer != _importer; - if (await importCache.importCanonical(importer, canonicalUrl, - originalUrl: originalUrl) + if (await importCache.importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + ) case var stylesheet?) { return (stylesheet, importer: importer, isDependency: isDependency); } @@ -1775,7 +1992,10 @@ final class _EvaluateVisitor if (_nodeImporter != null) { if (await _importLikeNode( - url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) + url, + baseUrl ?? _stylesheet.span.sourceUrl, + forImport, + ) case var result?) { result.$1.span.sourceUrl.andThen(_loadedUrls.add); return result; @@ -1804,7 +2024,10 @@ final class _EvaluateVisitor /// /// Returns the [Stylesheet], or `null` if the import failed. Future<_LoadedStylesheet?> _importLikeNode( - String originalUrl, Uri? previous, bool forImport) async { + String originalUrl, + Uri? previous, + bool forImport, + ) async { var result = _nodeImporter!.loadRelative(originalUrl, previous, forImport); bool isDependency; @@ -1819,10 +2042,12 @@ final class _EvaluateVisitor var (contents, url) = result; return ( Stylesheet.parse( - contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, - url: url), + contents, + url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, + url: url, + ), importer: null, - isDependency: isDependency + isDependency: isDependency, ); } @@ -1832,9 +2057,12 @@ final class _EvaluateVisitor // here should be mirrored there. var node = ModifiableCssImport( - await _interpolationToValue(import.url), import.span, - modifiers: await import.modifiers - .andThen>?>(_interpolationToValue)); + await _interpolationToValue(import.url), + import.span, + modifiers: await import.modifiers.andThen>?>( + _interpolationToValue, + ), + ); if (_parent != _root) { _parent.addChild(node); @@ -1848,11 +2076,12 @@ final class _EvaluateVisitor /// Evaluate a given [mixin] with [arguments] and [contentCallable] Future _applyMixin( - AsyncCallable? mixin, - UserDefinedCallable? contentCallable, - ArgumentList arguments, - AstNode nodeWithSpan, - AstNode nodeWithSpanWithoutContent) async { + AsyncCallable? mixin, + UserDefinedCallable? contentCallable, + ArgumentList arguments, + AstNode nodeWithSpan, + AstNode nodeWithSpanWithoutContent, + ) async { switch (mixin) { case null: throw _exception("Undefined mixin.", nodeWithSpan.span); @@ -1862,45 +2091,58 @@ final class _EvaluateVisitor { var evaluated = await _evaluateArguments(arguments); var (overload, _) = mixin.callbackFor( - evaluated.positional.length, MapKeySet(evaluated.named)); + evaluated.positional.length, + MapKeySet(evaluated.named), + ); throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - nodeWithSpanWithoutContent.span, - "invocation", - {overload.spanWithName: "declaration"}, - _stackTrace(nodeWithSpanWithoutContent.span)); + "Mixin doesn't accept a content block.", + nodeWithSpanWithoutContent.span, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(nodeWithSpanWithoutContent.span), + ); } case AsyncBuiltInCallable(): await _environment.withContent(contentCallable, () async { await _environment.asMixin(() async { await _runBuiltInCallable( - arguments, mixin, nodeWithSpanWithoutContent); + arguments, + mixin, + nodeWithSpanWithoutContent, + ); }); }); case UserDefinedCallable( - declaration: MixinRule(hasContent: false) + declaration: MixinRule(hasContent: false), ) when contentCallable != null: throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - nodeWithSpanWithoutContent.span, - "invocation", - {mixin.declaration.parameters.spanWithName: "declaration"}, - _stackTrace(nodeWithSpanWithoutContent.span)); + "Mixin doesn't accept a content block.", + nodeWithSpanWithoutContent.span, + "invocation", + {mixin.declaration.parameters.spanWithName: "declaration"}, + _stackTrace(nodeWithSpanWithoutContent.span), + ); case UserDefinedCallable(): await _runUserDefinedCallable( - arguments, mixin, nodeWithSpanWithoutContent, () async { - await _environment.withContent(contentCallable, () async { - await _environment.asMixin(() async { - for (var statement in mixin.declaration.children) { - await _addErrorSpan( - nodeWithSpanWithoutContent, () => statement.accept(this)); - } + arguments, + mixin, + nodeWithSpanWithoutContent, + () async { + await _environment.withContent(contentCallable, () async { + await _environment.asMixin(() async { + for (var statement in mixin.declaration.children) { + await _addErrorSpan( + nodeWithSpanWithoutContent, + () => statement.accept(this), + ); + } + }); }); - }); - }); + }, + ); case _: throw UnsupportedError("Unknown callable type $mixin."); @@ -1908,36 +2150,54 @@ final class _EvaluateVisitor } Future visitIncludeRule(IncludeRule node) async { - var mixin = _addExceptionSpan(node, - () => _environment.getMixin(node.name, namespace: node.namespace)); + var mixin = _addExceptionSpan( + node, + () => _environment.getMixin(node.name, namespace: node.namespace), + ); if (node.originalName.startsWith('--') && mixin is UserDefinedCallable && !mixin.declaration.originalName.startsWith('--')) { _warn( - 'Sass @mixin names beginning with -- are deprecated for forward-' - 'compatibility with plain CSS mixins.\n' - '\n' - 'For details, see https://sass-lang.com/d/css-function-mixin', - node.nameSpan, - Deprecation.cssFunctionMixin); + 'Sass @mixin names beginning with -- are deprecated for forward-' + 'compatibility with plain CSS mixins.\n' + '\n' + 'For details, see https://sass-lang.com/d/css-function-mixin', + node.nameSpan, + Deprecation.cssFunctionMixin, + ); } - var contentCallable = node.content.andThen((content) => UserDefinedCallable( - content, _environment.closure(), - inDependency: _inDependency)); + var contentCallable = node.content.andThen( + (content) => UserDefinedCallable( + content, + _environment.closure(), + inDependency: _inDependency, + ), + ); - var nodeWithSpanWithoutContent = - AstNode.fake(() => node.spanWithoutContent); + var nodeWithSpanWithoutContent = AstNode.fake( + () => node.spanWithoutContent, + ); - await _applyMixin(mixin, contentCallable, node.arguments, node, - nodeWithSpanWithoutContent); + await _applyMixin( + mixin, + contentCallable, + node.arguments, + node, + nodeWithSpanWithoutContent, + ); return null; } Future visitMixinRule(MixinRule node) async { - _environment.setMixin(UserDefinedCallable(node, _environment.closure(), - inDependency: _inDependency)); + _environment.setMixin( + UserDefinedCallable( + node, + _environment.closure(), + inDependency: _inDependency, + ), + ); return null; } @@ -1965,12 +2225,15 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Media rules may not be used within nested declarations.", node.span); + "Media rules may not be used within nested declarations.", + node.span, + ); } var queries = await _visitMediaQueries(node.query); - var mergedQueries = _mediaQueries - .andThen((mediaQueries) => _mergeMediaQueries(mediaQueries, queries)); + var mergedQueries = _mediaQueries.andThen( + (mediaQueries) => _mergeMediaQueries(mediaQueries, queries), + ); if (mergedQueries != null && mergedQueries.isEmpty) return null; var mergedSources = mergedQueries == null @@ -1978,33 +2241,38 @@ final class _EvaluateVisitor : {..._mediaQuerySources!, ..._mediaQueries!, ...queries}; await _withParent( - ModifiableCssMediaRule(mergedQueries ?? queries, node.span), () async { - await _withMediaQueries(mergedQueries ?? queries, mergedSources, + ModifiableCssMediaRule(mergedQueries ?? queries, node.span), + () async { + await _withMediaQueries( + mergedQueries ?? queries, + mergedSources, () async { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the media query so that - // declarations immediately inside @media have somewhere to go. - // - // For example, "a {@media screen {b: c}}" should produce - // "@media screen {a {b: c}}". - await _withParent(styleRule.copyWithoutChildren(), () async { - for (var child in node.children) { - await child.accept(this); + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the media query so that + // declarations immediately inside @media have somewhere to go. + // + // For example, "a {@media screen {b: c}}" should produce + // "@media screen {a {b: c}}". + await _withParent(styleRule.copyWithoutChildren(), () async { + for (var child in node.children) { + await child.accept(this); + } + }, scopeWhen: false); + } else { + for (var child in node.children) { + await child.accept(this); + } } - }, scopeWhen: false); - } else { - for (var child in node.children) { - await child.accept(this); - } - } - }); - }, - through: (node) => - node is CssStyleRule || - (mergedSources.isNotEmpty && - node is CssMediaRule && - node.queries.every(mergedSources.contains)), - scopeWhen: node.hasDeclarations); + }, + ); + }, + through: (node) => + node is CssStyleRule || + (mergedSources.isNotEmpty && + node is CssMediaRule && + node.queries.every(mergedSources.contains)), + scopeWhen: node.hasDeclarations, + ); return null; } @@ -2012,9 +2280,12 @@ final class _EvaluateVisitor /// Evaluates [interpolation] and parses the result as a list of media /// queries. Future> _visitMediaQueries( - Interpolation interpolation) async { - var (resolved, map) = - await _performInterpolationWithMap(interpolation, warnForColor: true); + Interpolation interpolation, + ) async { + var (resolved, map) = await _performInterpolationWithMap( + interpolation, + warnForColor: true, + ); return CssMediaQuery.parseList(resolved, interpolationMap: map); } @@ -2025,7 +2296,9 @@ final class _EvaluateVisitor /// and [queries2], or `null` if there are contexts that can't be represented /// by media queries. List? _mergeMediaQueries( - Iterable queries1, Iterable queries2) { + Iterable queries1, + Iterable queries2, + ) { var queries = []; for (var query1 in queries1) { inner: @@ -2054,71 +2327,97 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Style rules may not be used within nested declarations.", node.span); + "Style rules may not be used within nested declarations.", + node.span, + ); } else if (_inKeyframes && _parent is CssKeyframeBlock) { throw _exception( - "Style rules may not be used within keyframe blocks.", node.span); + "Style rules may not be used within keyframe blocks.", + node.span, + ); } - var (selectorText, selectorMap) = - await _performInterpolationWithMap(node.selector, warnForColor: true); + var (selectorText, selectorMap) = await _performInterpolationWithMap( + node.selector, + warnForColor: true, + ); if (_inKeyframes) { // NOTE: this logic is largely duplicated in [visitCssKeyframeBlock]. Most // changes here should be mirrored there. - var parsedSelector = - KeyframeSelectorParser(selectorText, interpolationMap: selectorMap) - .parse(); + var parsedSelector = KeyframeSelectorParser( + selectorText, + interpolationMap: selectorMap, + ).parse(); var rule = ModifiableCssKeyframeBlock( - CssValue(List.unmodifiable(parsedSelector), node.selector.span), - node.span); - await _withParent(rule, () async { - for (var child in node.children) { - await child.accept(this); - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + CssValue(List.unmodifiable(parsedSelector), node.selector.span), + node.span, + ); + await _withParent( + rule, + () async { + for (var child in node.children) { + await child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); return null; } - var parsedSelector = SelectorList.parse(selectorText, - interpolationMap: selectorMap, plainCss: _stylesheet.plainCss); + var parsedSelector = SelectorList.parse( + selectorText, + interpolationMap: selectorMap, + plainCss: _stylesheet.plainCss, + ); var nest = !(_styleRule?.fromPlainCss ?? false); if (nest) { if (_stylesheet.plainCss) { for (var complex in parsedSelector.components) { - if (complex.leadingCombinators case [var first, ...] - when _stylesheet.plainCss) { + if (complex.leadingCombinators + case [ + var first, + ..., + ] when _stylesheet.plainCss) { throw _exception( - "Top-level leading combinators aren't allowed in plain CSS.", - first.span); + "Top-level leading combinators aren't allowed in plain CSS.", + first.span, + ); } } } parsedSelector = parsedSelector.nestWithin( - _styleRuleIgnoringAtRoot?.originalSelector, - implicitParent: !_atRootExcludingStyleRule, - preserveParentSelectors: _stylesheet.plainCss); + _styleRuleIgnoringAtRoot?.originalSelector, + implicitParent: !_atRootExcludingStyleRule, + preserveParentSelectors: _stylesheet.plainCss, + ); } var selector = _extensionStore.addSelector(parsedSelector, _mediaQueries); - var rule = ModifiableCssStyleRule(selector, node.span, - originalSelector: parsedSelector, fromPlainCss: _stylesheet.plainCss); + var rule = ModifiableCssStyleRule( + selector, + node.span, + originalSelector: parsedSelector, + fromPlainCss: _stylesheet.plainCss, + ); var oldAtRootExcludingStyleRule = _atRootExcludingStyleRule; _atRootExcludingStyleRule = false; - await _withParent(rule, () async { - await _withStyleRule(rule, () async { - for (var child in node.children) { - await child.accept(this); - } - }); - }, - through: nest ? (node) => node is CssStyleRule : null, - scopeWhen: node.hasDeclarations); + await _withParent( + rule, + () async { + await _withStyleRule(rule, () async { + for (var child in node.children) { + await child.accept(this); + } + }); + }, + through: nest ? (node) => node is CssStyleRule : null, + scopeWhen: node.hasDeclarations, + ); _atRootExcludingStyleRule = oldAtRootExcludingStyleRule; _warnForBogusCombinators(rule); @@ -2139,42 +2438,45 @@ final class _EvaluateVisitor if (complex.isUseless) { _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS. It ' - 'will be omitted from the generated CSS.\n' + 'The selector "${complex.toString().trim()}" is invalid CSS. It ' + 'will be omitted from the generated CSS.\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + complex.span.trimRight(), + Deprecation.bogusCombinators, + ); + } else if (complex.leadingCombinators.isNotEmpty) { + if (!_stylesheet.plainCss) { + _warn( + 'The selector "${complex.toString().trim()}" is invalid CSS.\n' 'This will be an error in Dart Sass 2.0.0.\n' '\n' 'More info: https://sass-lang.com/d/bogus-combinators', complex.span.trimRight(), - Deprecation.bogusCombinators); - } else if (complex.leadingCombinators.isNotEmpty) { - if (!_stylesheet.plainCss) { - _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS.\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - complex.span.trimRight(), - Deprecation.bogusCombinators); + Deprecation.bogusCombinators, + ); } } else { _warn( - 'The selector "${complex.toString().trim()}" is only valid for ' - "nesting and shouldn't\n" - 'have children other than style rules.' + - (complex.isBogusOtherThanLeadingCombinator - ? ' It will be omitted from the generated CSS.' - : '') + - '\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - MultiSpan(complex.span.trimRight(), 'invalid selector', { - rule.children.first.span: "this is not a style rule" + - (rule.children.every((child) => child is CssComment) - ? '\n(try converting to a //-style comment)' - : '') - }), - Deprecation.bogusCombinators); + 'The selector "${complex.toString().trim()}" is only valid for ' + "nesting and shouldn't\n" + 'have children other than style rules.' + + (complex.isBogusOtherThanLeadingCombinator + ? ' It will be omitted from the generated CSS.' + : '') + + '\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + MultiSpan(complex.span.trimRight(), 'invalid selector', { + rule.children.first.span: "this is not a style rule" + + (rule.children.every((child) => child is CssComment) + ? '\n(try converting to a //-style comment)' + : ''), + }), + Deprecation.bogusCombinators, + ); } } } @@ -2186,33 +2488,38 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Supports rules may not be used within nested declarations.", - node.span); + "Supports rules may not be used within nested declarations.", + node.span, + ); } var condition = CssValue( - await _visitSupportsCondition(node.condition), node.condition.span); - await _withParent(ModifiableCssSupportsRule(condition, node.span), - () async { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the supports rule so that - // declarations immediately inside @supports have somewhere to go. - // - // For example, "a {@supports (a: b) {b: c}}" should produce "@supports - // (a: b) {a {b: c}}". - await _withParent(styleRule.copyWithoutChildren(), () async { + await _visitSupportsCondition(node.condition), + node.condition.span, + ); + await _withParent( + ModifiableCssSupportsRule(condition, node.span), + () async { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the supports rule so that + // declarations immediately inside @supports have somewhere to go. + // + // For example, "a {@supports (a: b) {b: c}}" should produce "@supports + // (a: b) {a {b: c}}". + await _withParent(styleRule.copyWithoutChildren(), () async { + for (var child in node.children) { + await child.accept(this); + } + }); + } else { for (var child in node.children) { await child.accept(this); } - }); - } else { - for (var child in node.children) { - await child.accept(this); } - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); return null; } @@ -2226,19 +2533,23 @@ final class _EvaluateVisitor "${await _parenthesize(operation.right, operation.operator)}", SupportsNegation negation => "not ${await _parenthesize(negation.condition)}", - SupportsInterpolation interpolation => - await _evaluateToCss(interpolation.expression, quote: false), + SupportsInterpolation interpolation => await _evaluateToCss( + interpolation.expression, + quote: false, + ), SupportsDeclaration declaration => await _withSupportsDeclaration( () async => "(${await _evaluateToCss(declaration.name)}:" "${declaration.isCustomProperty ? '' : ' '}" - "${await _evaluateToCss(declaration.value)})"), + "${await _evaluateToCss(declaration.value)})", + ), SupportsFunction function => "${await _performInterpolation(function.name)}(" "${await _performInterpolation(function.arguments)})", SupportsAnything anything => "(${await _performInterpolation(anything.contents)})", var condition => throw ArgumentError( - "Unknown supports condition type ${condition.runtimeType}.") + "Unknown supports condition type ${condition.runtimeType}.", + ), }; /// Runs [callback] in a context where [_inSupportsDeclaration] is true. @@ -2258,8 +2569,10 @@ final class _EvaluateVisitor /// If [operator] is passed, it's the operator for the surrounding /// [SupportsOperation], and is used to determine whether parentheses are /// necessary if [condition] is also a [SupportsOperation]. - Future _parenthesize(SupportsCondition condition, - [String? operator]) async { + Future _parenthesize( + SupportsCondition condition, [ + String? operator, + ]) async { switch (condition) { case SupportsNegation(): case SupportsOperation() @@ -2278,42 +2591,54 @@ final class _EvaluateVisitor when override.value != sassNull) { _addExceptionSpan(node, () { _environment.setVariable( - node.name, override.value, override.assignmentNode, - global: true); + node.name, + override.value, + override.assignmentNode, + global: true, + ); }); return null; } } - var value = _addExceptionSpan(node, - () => _environment.getVariable(node.name, namespace: node.namespace)); + var value = _addExceptionSpan( + node, + () => _environment.getVariable(node.name, namespace: node.namespace), + ); if (value != null && value != sassNull) return null; } if (node.isGlobal && !_environment.globalVariableExists(node.name)) { _warn( - _environment.atRoot - ? "As of Dart Sass 2.0.0, !global assignments won't be able to " - "declare new variables.\n" - "\n" - "Since this assignment is at the root of the stylesheet, the " - "!global flag is\n" - "unnecessary and can safely be removed." - : "As of Dart Sass 2.0.0, !global assignments won't be able to " - "declare new variables.\n" - "\n" - "Recommendation: add `${node.originalName}: null` at the " - "stylesheet root.", - node.span, - Deprecation.newGlobal); + _environment.atRoot + ? "As of Dart Sass 2.0.0, !global assignments won't be able to " + "declare new variables.\n" + "\n" + "Since this assignment is at the root of the stylesheet, the " + "!global flag is\n" + "unnecessary and can safely be removed." + : "As of Dart Sass 2.0.0, !global assignments won't be able to " + "declare new variables.\n" + "\n" + "Recommendation: add `${node.originalName}: null` at the " + "stylesheet root.", + node.span, + Deprecation.newGlobal, + ); } - var value = - _withoutSlash(await node.expression.accept(this), node.expression); + var value = _withoutSlash( + await node.expression.accept(this), + node.expression, + ); _addExceptionSpan(node, () { _environment.setVariable( - node.name, value, _expressionNode(node.expression), - namespace: node.namespace, global: node.isGlobal); + node.name, + value, + _expressionNode(node.expression), + namespace: node.namespace, + global: node.isGlobal, + ); }); return null; } @@ -2325,10 +2650,13 @@ final class _EvaluateVisitor for (var variable in node.configuration) { var variableNodeWithSpan = _expressionNode(variable.expression); values[variable.name] = ConfiguredValue.explicit( - _withoutSlash( - await variable.expression.accept(this), variableNodeWithSpan), - variable.span, - variableNodeWithSpan); + _withoutSlash( + await variable.expression.accept(this), + variableNodeWithSpan, + ), + variable.span, + variableNodeWithSpan, + ); } configuration = ExplicitConfiguration(values, node); } @@ -2343,25 +2671,34 @@ final class _EvaluateVisitor } Future visitWarnRule(WarnRule node) async { - var value = - await _addExceptionSpanAsync(node, () => node.expression.accept(this)); + var value = await _addExceptionSpanAsync( + node, + () => node.expression.accept(this), + ); _logger.warn( - value is SassString ? value.text : _serialize(value, node.expression), - trace: _stackTrace(node.span)); + value is SassString ? value.text : _serialize(value, node.expression), + trace: _stackTrace(node.span), + ); return null; } Future visitWhileRule(WhileRule node) { - return _environment.scope(() async { - while ((await node.condition.accept(this)).isTruthy) { - if (await _handleReturn( - node.children, (child) => child.accept(this)) - case var result?) { - return result; + return _environment.scope( + () async { + while ((await node.condition.accept(this)).isTruthy) { + if (await _handleReturn( + node.children, + (child) => child.accept(this), + ) + case var result?) { + return result; + } } - } - return null; - }, semiGlobal: true, when: node.hasDeclarations); + return null; + }, + semiGlobal: true, + when: node.hasDeclarations, + ); } // ## Expressions @@ -2371,35 +2708,46 @@ final class _EvaluateVisitor node.operator != BinaryOperator.singleEquals && node.operator != BinaryOperator.dividedBy) { throw _exception( - "Operators aren't allowed in plain CSS.", node.operatorSpan); + "Operators aren't allowed in plain CSS.", + node.operatorSpan, + ); } return _addExceptionSpanAsync(node, () async { var left = await node.left.accept(this); return switch (node.operator) { - BinaryOperator.singleEquals => - left.singleEquals(await node.right.accept(this)), + BinaryOperator.singleEquals => left.singleEquals( + await node.right.accept(this), + ), BinaryOperator.or => left.isTruthy ? left : await node.right.accept(this), BinaryOperator.and => left.isTruthy ? await node.right.accept(this) : left, - BinaryOperator.equals => - SassBoolean(left == await node.right.accept(this)), - BinaryOperator.notEquals => - SassBoolean(left != await node.right.accept(this)), - BinaryOperator.greaterThan => - left.greaterThan(await node.right.accept(this)), - BinaryOperator.greaterThanOrEquals => - left.greaterThanOrEquals(await node.right.accept(this)), + BinaryOperator.equals => SassBoolean( + left == await node.right.accept(this), + ), + BinaryOperator.notEquals => SassBoolean( + left != await node.right.accept(this), + ), + BinaryOperator.greaterThan => left.greaterThan( + await node.right.accept(this), + ), + BinaryOperator.greaterThanOrEquals => left.greaterThanOrEquals( + await node.right.accept(this), + ), BinaryOperator.lessThan => left.lessThan(await node.right.accept(this)), - BinaryOperator.lessThanOrEquals => - left.lessThanOrEquals(await node.right.accept(this)), + BinaryOperator.lessThanOrEquals => left.lessThanOrEquals( + await node.right.accept(this), + ), BinaryOperator.plus => left.plus(await node.right.accept(this)), BinaryOperator.minus => left.minus(await node.right.accept(this)), BinaryOperator.times => left.times(await node.right.accept(this)), - BinaryOperator.dividedBy => - _slash(left, await node.right.accept(this), node), - BinaryOperator.modulo => left.modulo(await node.right.accept(this)) + BinaryOperator.dividedBy => _slash( + left, + await node.right.accept(this), + node, + ), + BinaryOperator.modulo => left.modulo(await node.right.accept(this)), }; }); } @@ -2420,24 +2768,25 @@ final class _EvaluateVisitor BinaryOperationExpression( operator: BinaryOperator.dividedBy, :var left, - :var right + :var right, ) => "math.div(${recommendation(left)}, ${recommendation(right)})", ParenthesizedExpression() => expression.expression.toString(), - _ => expression.toString() + _ => expression.toString(), }; _warn( - "Using / for division outside of calc() is deprecated " - "and will be removed in Dart Sass 2.0.0.\n" - "\n" - "Recommendation: ${recommendation(node)} or " - "${expressionToCalc(node)}\n" - "\n" - "More info and automated migrator: " - "https://sass-lang.com/d/slash-div", - node.span, - Deprecation.slashDiv); + "Using / for division outside of calc() is deprecated " + "and will be removed in Dart Sass 2.0.0.\n" + "\n" + "Recommendation: ${recommendation(node)} or " + "${expressionToCalc(node)}\n" + "\n" + "More info and automated migrator: " + "https://sass-lang.com/d/slash-div", + node.span, + Deprecation.slashDiv, + ); return result; case _: @@ -2456,28 +2805,31 @@ final class _EvaluateVisitor const { "calc", "clamp", "hypot", "sin", "cos", "tan", "asin", "acos", // "atan", "sqrt", "exp", "sign", "mod", "rem", "atan2", "pow", // - "log", "calc-size" + "log", "calc-size", }.contains(node.name.toLowerCase()) && _environment.getFunction(node.name) == null); Future visitValueExpression(ValueExpression node) async => node.value; Future visitVariableExpression(VariableExpression node) async { - var result = _addExceptionSpan(node, - () => _environment.getVariable(node.name, namespace: node.namespace)); + var result = _addExceptionSpan( + node, + () => _environment.getVariable(node.name, namespace: node.namespace), + ); if (result != null) return result; throw _exception("Undefined variable.", node.span); } Future visitUnaryOperationExpression( - UnaryOperationExpression node) async { + UnaryOperationExpression node, + ) async { var operand = await node.operand.accept(this); return _addExceptionSpan(node, () { return switch (node.operator) { UnaryOperator.plus => operand.unaryPlus(), UnaryOperator.minus => operand.unaryMinus(), UnaryOperator.divide => operand.unaryDivide(), - UnaryOperator.not => operand.unaryNot() + UnaryOperator.not => operand.unaryNot(), }; }); } @@ -2506,17 +2858,22 @@ final class _EvaluateVisitor Future visitParenthesizedExpression(ParenthesizedExpression node) => _stylesheet.plainCss ? throw _exception( - "Parentheses aren't allowed in plain CSS.", node.span) + "Parentheses aren't allowed in plain CSS.", + node.span, + ) : node.expression.accept(this); Future visitColorExpression(ColorExpression node) async => node.value; Future visitListExpression(ListExpression node) async => SassList( - await mapAsync( - node.contents, (Expression expression) => expression.accept(this)), - node.separator, - brackets: node.hasBrackets); + await mapAsync( + node.contents, + (Expression expression) => expression.accept(this), + ), + node.separator, + brackets: node.hasBrackets, + ); Future visitMapExpression(MapExpression node) async { var map = {}; @@ -2528,11 +2885,12 @@ final class _EvaluateVisitor if (map.containsKey(keyValue)) { var oldValueSpan = keyNodes[keyValue]?.span; throw MultiSpanSassRuntimeException( - 'Duplicate key.', - key.span, - 'second key', - {if (oldValueSpan != null) oldValueSpan: 'first key'}, - _stackTrace(key.span)); + 'Duplicate key.', + key.span, + 'second key', + {if (oldValueSpan != null) oldValueSpan: 'first key'}, + _stackTrace(key.span), + ); } map[keyValue] = valueValue; keyNodes[keyValue] = key; @@ -2545,8 +2903,11 @@ final class _EvaluateVisitor ? null : _addExceptionSpan( node, - () => - _environment.getFunction(node.name, namespace: node.namespace)); + () => _environment.getFunction( + node.name, + namespace: node.namespace, + ), + ); if (function == null) { if (node.namespace != null) { throw _exception("Undefined function.", node.span); @@ -2558,8 +2919,9 @@ final class _EvaluateVisitor case ("min" || "max" || "round" || "abs") && var name when node.arguments.named.isEmpty && node.arguments.rest == null && - node.arguments.positional - .every((argument) => argument.isCalculationSafe): + node.arguments.positional.every( + (argument) => argument.isCalculationSafe, + ): return await _visitCalculation(node, inLegacySassFunction: name); case "calc" || @@ -2603,7 +2965,9 @@ final class _EvaluateVisitor var oldInFunction = _inFunction; _inFunction = true; var result = await _addErrorSpan( - node, () => _runFunctionCallable(node.arguments, function, node)); + node, + () => _runFunctionCallable(node.arguments, function, node), + ); _inFunction = oldInFunction; return result; } @@ -2615,21 +2979,29 @@ final class _EvaluateVisitor /// with the old global `min()`, `max()`, `round()`, and `abs()` functions. /// The parameter is the name of the function, which is used for reporting /// deprecation warnings. - Future _visitCalculation(FunctionExpression node, - {String? inLegacySassFunction}) async { + Future _visitCalculation( + FunctionExpression node, { + String? inLegacySassFunction, + }) async { if (node.arguments.named.isNotEmpty) { throw _exception( - "Keyword arguments can't be used with calculations.", node.span); + "Keyword arguments can't be used with calculations.", + node.span, + ); } else if (node.arguments.rest != null) { throw _exception( - "Rest arguments can't be used with calculations.", node.span); + "Rest arguments can't be used with calculations.", + node.span, + ); } _checkCalculationArguments(node); var arguments = [ for (var argument in node.arguments.positional) - await _visitCalculationExpression(argument, - inLegacySassFunction: inLegacySassFunction) + await _visitCalculationExpression( + argument, + inLegacySassFunction: inLegacySassFunction, + ), ]; if (_inSupportsDeclaration) { return SassCalculation.unsimplified(node.name, arguments); @@ -2654,27 +3026,45 @@ final class _EvaluateVisitor "min" => SassCalculation.min(arguments), "max" => SassCalculation.max(arguments), "hypot" => SassCalculation.hypot(arguments), - "pow" => - SassCalculation.pow(arguments[0], arguments.elementAtOrNull(1)), - "atan2" => - SassCalculation.atan2(arguments[0], arguments.elementAtOrNull(1)), - "log" => - SassCalculation.log(arguments[0], arguments.elementAtOrNull(1)), - "mod" => - SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)), - "rem" => - SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)), - "round" => SassCalculation.roundInternal(arguments[0], - arguments.elementAtOrNull(1), arguments.elementAtOrNull(2), + "pow" => SassCalculation.pow( + arguments[0], + arguments.elementAtOrNull(1), + ), + "atan2" => SassCalculation.atan2( + arguments[0], + arguments.elementAtOrNull(1), + ), + "log" => SassCalculation.log( + arguments[0], + arguments.elementAtOrNull(1), + ), + "mod" => SassCalculation.mod( + arguments[0], + arguments.elementAtOrNull(1), + ), + "rem" => SassCalculation.rem( + arguments[0], + arguments.elementAtOrNull(1), + ), + "round" => SassCalculation.roundInternal( + arguments[0], + arguments.elementAtOrNull(1), + arguments.elementAtOrNull(2), span: node.span, inLegacySassFunction: inLegacySassFunction, warn: (message, [deprecation]) => - _warn(message, node.span, deprecation)), - "clamp" => SassCalculation.clamp(arguments[0], - arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)), - "calc-size" => - SassCalculation.calcSize(arguments[0], arguments.elementAtOrNull(1)), - _ => throw UnsupportedError('Unknown calculation name "${node.name}".') + _warn(message, node.span, deprecation), + ), + "clamp" => SassCalculation.clamp( + arguments[0], + arguments.elementAtOrNull(1), + arguments.elementAtOrNull(2), + ), + "calc-size" => SassCalculation.calcSize( + arguments[0], + arguments.elementAtOrNull(1), + ), + _ => throw UnsupportedError('Unknown calculation name "${node.name}".'), }; } on SassScriptException catch (error, stackTrace) { // The simplification logic in the [SassCalculation] static methods will @@ -2697,12 +3087,16 @@ final class _EvaluateVisitor } else if (maxArgs != null && node.arguments.positional.length > maxArgs) { throw _exception( - "Only $maxArgs ${pluralize('argument', maxArgs)} allowed, but " - "${node.arguments.positional.length} " + - pluralize('was', node.arguments.positional.length, - plural: 'were') + - " passed.", - node.span); + "Only $maxArgs ${pluralize('argument', maxArgs)} allowed, but " + "${node.arguments.positional.length} " + + pluralize( + 'was', + node.arguments.positional.length, + plural: 'were', + ) + + " passed.", + node.span, + ); } } @@ -2735,14 +3129,18 @@ final class _EvaluateVisitor /// /// The [nodesWithSpans] should correspond to the spans for [args]. void _verifyCompatibleNumbers( - List args, List nodesWithSpans) { + List args, + List nodesWithSpans, + ) { // Note: this logic is largely duplicated in // SassCalculation._verifyCompatibleNumbers and most changes here should // also be reflected there. for (var i = 0; i < args.length; i++) { if (args[i] case SassNumber arg when arg.hasComplexUnits) { - throw _exception("Number $arg isn't compatible with CSS calculations.", - nodesWithSpans[i].span); + throw _exception( + "Number $arg isn't compatible with CSS calculations.", + nodesWithSpans[i].span, + ); } } @@ -2756,11 +3154,12 @@ final class _EvaluateVisitor if (number1.hasPossiblyCompatibleUnits(number2)) continue; throw MultiSpanSassRuntimeException( - "$number1 and $number2 are incompatible.", - nodesWithSpans[i].span, - number1.toString(), - {nodesWithSpans[j].span: number2.toString()}, - _stackTrace(nodesWithSpans[i].span)); + "$number1 and $number2 are incompatible.", + nodesWithSpans[i].span, + number1.toString(), + {nodesWithSpans[j].span: number2.toString()}, + _stackTrace(nodesWithSpans[i].span), + ); } } } @@ -2772,12 +3171,16 @@ final class _EvaluateVisitor /// with the old global `min()`, `max()`, `round()`, and `abs()` functions. /// The parameter is the name of the function, which is used for reporting /// deprecation warnings. - Future _visitCalculationExpression(Expression node, - {required String? inLegacySassFunction}) async { + Future _visitCalculationExpression( + Expression node, { + required String? inLegacySassFunction, + }) async { switch (node) { case ParenthesizedExpression(expression: var inner): - var result = await _visitCalculationExpression(inner, - inLegacySassFunction: inLegacySassFunction); + var result = await _visitCalculationExpression( + inner, + inLegacySassFunction: inLegacySassFunction, + ); return result is SassString ? SassString('(${result.text})', quotes: false) : result; @@ -2790,23 +3193,32 @@ final class _EvaluateVisitor 'infinity' => SassNumber(double.infinity), '-infinity' => SassNumber(double.negativeInfinity), 'nan' => SassNumber(double.nan), - _ => SassString(await _performInterpolation(node.text), quotes: false) + _ => SassString( + await _performInterpolation(node.text), + quotes: false, + ), }; case BinaryOperationExpression(:var operator, :var left, :var right): _checkWhitespaceAroundCalculationOperator(node); return await _addExceptionSpanAsync( - node, - () async => SassCalculation.operateInternal( - _binaryOperatorToCalculationOperator(operator, node), - await _visitCalculationExpression(left, - inLegacySassFunction: inLegacySassFunction), - await _visitCalculationExpression(right, - inLegacySassFunction: inLegacySassFunction), - inLegacySassFunction: inLegacySassFunction, - simplify: !_inSupportsDeclaration, - warn: (message, [deprecation]) => - _warn(message, node.span, deprecation))); + node, + () async => SassCalculation.operateInternal( + _binaryOperatorToCalculationOperator(operator, node), + await _visitCalculationExpression( + left, + inLegacySassFunction: inLegacySassFunction, + ), + await _visitCalculationExpression( + right, + inLegacySassFunction: inLegacySassFunction, + ), + inLegacySassFunction: inLegacySassFunction, + simplify: !_inSupportsDeclaration, + warn: (message, [deprecation]) => + _warn(message, node.span, deprecation), + ), + ); case NumberExpression() || VariableExpression() || @@ -2817,18 +3229,22 @@ final class _EvaluateVisitor SassCalculation result => result, SassString result when !result.hasQuotes => result, var result => throw _exception( - "Value $result can't be used in a calculation.", node.span) + "Value $result can't be used in a calculation.", + node.span, + ), }; case ListExpression( hasBrackets: false, separator: ListSeparator.space, - contents: [_, _, ...] + contents: [_, _, ...], ): var elements = [ for (var element in node.contents) - await _visitCalculationExpression(element, - inLegacySassFunction: inLegacySassFunction) + await _visitCalculationExpression( + element, + inLegacySassFunction: inLegacySassFunction, + ), ]; _checkAdjacentCalculationValues(elements, node); @@ -2845,14 +3261,17 @@ final class _EvaluateVisitor case _: assert(!node.isCalculationSafe); throw _exception( - "This expression can't be used in a calculation.", node.span); + "This expression can't be used in a calculation.", + node.span, + ); } } /// Throws an error if [node] requires whitespace around its operator in a /// calculation but doesn't have it. void _checkWhitespaceAroundCalculationOperator( - BinaryOperationExpression node) { + BinaryOperationExpression node, + ) { if (node.operator != BinaryOperator.plus && node.operator != BinaryOperator.minus) { return; @@ -2864,33 +3283,42 @@ final class _EvaluateVisitor if (node.left.span.file != node.right.span.file) return; if (node.left.span.end.offset >= node.right.span.start.offset) return; - var textBetweenOperands = node.left.span.file - .getText(node.left.span.end.offset, node.right.span.start.offset); + var textBetweenOperands = node.left.span.file.getText( + node.left.span.end.offset, + node.right.span.start.offset, + ); var first = textBetweenOperands.codeUnitAt(0); var last = textBetweenOperands.codeUnitAt(textBetweenOperands.length - 1); if (!(first.isWhitespace || first == $slash) || !(last.isWhitespace || last == $slash)) { throw _exception( - '"+" and "-" must be surrounded by whitespace in calculations.', - node.operatorSpan); + '"+" and "-" must be surrounded by whitespace in calculations.', + node.operatorSpan, + ); } } /// Returns the [CalculationOperator] that corresponds to [operator]. CalculationOperator _binaryOperatorToCalculationOperator( - BinaryOperator operator, BinaryOperationExpression node) => + BinaryOperator operator, + BinaryOperationExpression node, + ) => switch (operator) { BinaryOperator.plus => CalculationOperator.plus, BinaryOperator.minus => CalculationOperator.minus, BinaryOperator.times => CalculationOperator.times, BinaryOperator.dividedBy => CalculationOperator.dividedBy, _ => throw _exception( - "This operation can't be used in a calculation.", node.operatorSpan) + "This operation can't be used in a calculation.", + node.operatorSpan, + ), }; /// Throws an error if [elements] contains two adjacent non-string values. void _checkAdjacentCalculationValues( - List elements, ListExpression node) { + List elements, + ListExpression node, + ) { assert(elements.length > 1); for (var i = 1; i < elements.length; i++) { @@ -2902,7 +3330,7 @@ final class _EvaluateVisitor var currentNode = node.contents[i]; if (currentNode case UnaryOperationExpression( - operator: UnaryOperator.minus || UnaryOperator.plus + operator: UnaryOperator.minus || UnaryOperator.plus, ) || NumberExpression(value: < 0)) { // `calc(1 -2)` parses as a space-separated list whose second value is a @@ -2910,22 +3338,28 @@ final class _EvaluateVisitor // expression doesn't help the user understand what's going wrong. We // add special case error handling to help clarify the issue. throw _exception( - '"+" and "-" must be surrounded by whitespace in calculations.', - currentNode.span.subspan(0, 1)); + '"+" and "-" must be surrounded by whitespace in calculations.', + currentNode.span.subspan(0, 1), + ); } else { - throw _exception('Missing math operator.', - previousNode.span.expand(currentNode.span)); + throw _exception( + 'Missing math operator.', + previousNode.span.expand(currentNode.span), + ); } } } Future visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) async { + InterpolatedFunctionExpression node, + ) async { var function = PlainCssCallable(await _performInterpolation(node.name)); var oldInFunction = _inFunction; _inFunction = true; var result = await _addErrorSpan( - node, () => _runFunctionCallable(node.arguments, function, node)); + node, + () => _runFunctionCallable(node.arguments, function, node), + ); _inFunction = oldInFunction; return result; } @@ -2933,10 +3367,11 @@ final class _EvaluateVisitor /// Evaluates the arguments in [arguments] as applied to [callable], and /// invokes [run] in a scope with those arguments defined. Future _runUserDefinedCallable( - ArgumentList arguments, - UserDefinedCallable callable, - AstNode nodeWithSpan, - Future run()) async { + ArgumentList arguments, + UserDefinedCallable callable, + AstNode nodeWithSpan, + Future run(), + ) async { // TODO(nweiz): Set [trackSpans] to `null` once we're no longer emitting // deprecation warnings for /-as-division. var evaluated = await _evaluateArguments(arguments); @@ -2953,15 +3388,24 @@ final class _EvaluateVisitor // don't affect the underlying environment closure. return _withEnvironment(callable.environment.closure(), () { return _environment.scope(() async { - _verifyArguments(evaluated.positional.length, evaluated.named, - callable.declaration.parameters, nodeWithSpan); + _verifyArguments( + evaluated.positional.length, + evaluated.named, + callable.declaration.parameters, + nodeWithSpan, + ); var parameters = callable.declaration.parameters.parameters; - var minLength = - math.min(evaluated.positional.length, parameters.length); + var minLength = math.min( + evaluated.positional.length, + parameters.length, + ); for (var i = 0; i < minLength; i++) { - _environment.setLocalVariable(parameters[i].name, - evaluated.positional[i], evaluated.positionalNodes[i]); + _environment.setLocalVariable( + parameters[i].name, + evaluated.positional[i], + evaluated.positionalNodes[i], + ); } for (var i = evaluated.positional.length; @@ -2970,13 +3414,15 @@ final class _EvaluateVisitor var parameter = parameters[i]; var value = evaluated.named.remove(parameter.name) ?? _withoutSlash( - await parameter.defaultValue!.accept>(this), - _expressionNode(parameter.defaultValue!)); + await parameter.defaultValue!.accept>(this), + _expressionNode(parameter.defaultValue!), + ); _environment.setLocalVariable( - parameter.name, - value, - evaluated.namedNodes[parameter.name] ?? - _expressionNode(parameter.defaultValue!)); + parameter.name, + value, + evaluated.namedNodes[parameter.name] ?? + _expressionNode(parameter.defaultValue!), + ); } SassArgumentList? argumentList; @@ -2986,13 +3432,17 @@ final class _EvaluateVisitor ? evaluated.positional.sublist(parameters.length) : const []; argumentList = SassArgumentList( - rest, - evaluated.named, - evaluated.separator == ListSeparator.undecided - ? ListSeparator.comma - : evaluated.separator); + rest, + evaluated.named, + evaluated.separator == ListSeparator.undecided + ? ListSeparator.comma + : evaluated.separator, + ); _environment.setLocalVariable( - restParameter, argumentList, nodeWithSpan); + restParameter, + argumentList, + nodeWithSpan, + ); } var result = await run(); @@ -3001,16 +3451,21 @@ final class _EvaluateVisitor if (evaluated.named.isEmpty) return result; if (argumentList.wereKeywordsAccessed) return result; - var parameterWord = - pluralize('parameter', evaluated.named.keys.length); - var parameterNames = - toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or'); + var parameterWord = pluralize( + 'parameter', + evaluated.named.keys.length, + ); + var parameterNames = toSentence( + evaluated.named.keys.map((name) => "\$$name"), + 'or', + ); throw MultiSpanSassRuntimeException( - "No $parameterWord named $parameterNames.", - nodeWithSpan.span, - "invocation", - {callable.declaration.parameters.spanWithName: "declaration"}, - _stackTrace(nodeWithSpan.span)); + "No $parameterWord named $parameterNames.", + nodeWithSpan.span, + "invocation", + {callable.declaration.parameters.spanWithName: "declaration"}, + _stackTrace(nodeWithSpan.span), + ); }); }); }); @@ -3020,27 +3475,39 @@ final class _EvaluateVisitor } /// Evaluates [arguments] as applied to [callable]. - Future _runFunctionCallable(ArgumentList arguments, - AsyncCallable? callable, AstNode nodeWithSpan) async { + Future _runFunctionCallable( + ArgumentList arguments, + AsyncCallable? callable, + AstNode nodeWithSpan, + ) async { if (callable is AsyncBuiltInCallable) { return _withoutSlash( - await _runBuiltInCallable(arguments, callable, nodeWithSpan), - nodeWithSpan); + await _runBuiltInCallable(arguments, callable, nodeWithSpan), + nodeWithSpan, + ); } else if (callable is UserDefinedCallable) { - return await _runUserDefinedCallable(arguments, callable, nodeWithSpan, - () async { - for (var statement in callable.declaration.children) { - var returnValue = await statement.accept(this); - if (returnValue is Value) return returnValue; - } + return await _runUserDefinedCallable( + arguments, + callable, + nodeWithSpan, + () async { + for (var statement in callable.declaration.children) { + var returnValue = await statement.accept(this); + if (returnValue is Value) return returnValue; + } - throw _exception( - "Function finished without @return.", callable.declaration.span); - }); + throw _exception( + "Function finished without @return.", + callable.declaration.span, + ); + }, + ); } else if (callable is PlainCssCallable) { if (arguments.named.isNotEmpty || arguments.keywordRest != null) { - throw _exception("Plain CSS functions don't support keyword arguments.", - nodeWithSpan.span); + throw _exception( + "Plain CSS functions don't support keyword arguments.", + nodeWithSpan.span, + ); } var buffer = StringBuffer("${callable.name}("); @@ -3065,11 +3532,12 @@ final class _EvaluateVisitor } on SassRuntimeException catch (error) { if (!error.message.endsWith("isn't a valid CSS value.")) rethrow; throw MultiSpanSassRuntimeException( - error.message, - error.span, - "value", - {nodeWithSpan.span: "unknown function treated as plain CSS"}, - error.trace); + error.message, + error.span, + "value", + {nodeWithSpan.span: "unknown function treated as plain CSS"}, + error.trace, + ); } buffer.writeCharCode($rparen); @@ -3081,25 +3549,36 @@ final class _EvaluateVisitor /// Evaluates [invocation] as applied to [callable], and invokes [callable]'s /// body. - Future _runBuiltInCallable(ArgumentList arguments, - AsyncBuiltInCallable callable, AstNode nodeWithSpan) async { + Future _runBuiltInCallable( + ArgumentList arguments, + AsyncBuiltInCallable callable, + AstNode nodeWithSpan, + ) async { var evaluated = await _evaluateArguments(arguments); var oldCallableNode = _callableNode; _callableNode = nodeWithSpan; var namedSet = MapKeySet(evaluated.named); - var (overload, callback) = - callable.callbackFor(evaluated.positional.length, namedSet); - _addExceptionSpan(nodeWithSpan, - () => overload.verify(evaluated.positional.length, namedSet)); + var (overload, callback) = callable.callbackFor( + evaluated.positional.length, + namedSet, + ); + _addExceptionSpan( + nodeWithSpan, + () => overload.verify(evaluated.positional.length, namedSet), + ); var parameters = overload.parameters; for (var i = evaluated.positional.length; i < parameters.length; i++) { var parameter = parameters[i]; - evaluated.positional.add(evaluated.named.remove(parameter.name) ?? - _withoutSlash(await parameter.defaultValue!.accept(this), - parameter.defaultValue!)); + evaluated.positional.add( + evaluated.named.remove(parameter.name) ?? + _withoutSlash( + await parameter.defaultValue!.accept(this), + parameter.defaultValue!, + ), + ); } SassArgumentList? argumentList; @@ -3107,28 +3586,36 @@ final class _EvaluateVisitor var rest = const []; if (evaluated.positional.length > parameters.length) { rest = evaluated.positional.sublist(parameters.length); - evaluated.positional - .removeRange(parameters.length, evaluated.positional.length); + evaluated.positional.removeRange( + parameters.length, + evaluated.positional.length, + ); } argumentList = SassArgumentList( - rest, - evaluated.named, - evaluated.separator == ListSeparator.undecided - ? ListSeparator.comma - : evaluated.separator); + rest, + evaluated.named, + evaluated.separator == ListSeparator.undecided + ? ListSeparator.comma + : evaluated.separator, + ); evaluated.positional.add(argumentList); } Value result; try { result = await _addExceptionSpanAsync( - nodeWithSpan, () => callback(evaluated.positional)); + nodeWithSpan, + () => callback(evaluated.positional), + ); } on SassException { rethrow; } catch (error, stackTrace) { - throwWithTrace(_exception(_getErrorMessage(error), nodeWithSpan.span), - error, stackTrace); + throwWithTrace( + _exception(_getErrorMessage(error), nodeWithSpan.span), + error, + stackTrace, + ); } _callableNode = oldCallableNode; @@ -3137,12 +3624,13 @@ final class _EvaluateVisitor if (argumentList.wereKeywordsAccessed) return result; throw MultiSpanSassRuntimeException( - "No ${pluralize('parameter', evaluated.named.keys.length)} named " - "${toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or')}.", - nodeWithSpan.span, - "invocation", - {overload.spanWithName: "declaration"}, - _stackTrace(nodeWithSpan.span)); + "No ${pluralize('parameter', evaluated.named.keys.length)} named " + "${toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or')}.", + nodeWithSpan.span, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(nodeWithSpan.span), + ); } /// Returns the evaluated values of the given [arguments]. @@ -3176,7 +3664,7 @@ final class _EvaluateVisitor positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: ListSeparator.undecided + separator: ListSeparator.undecided, ); } @@ -3187,11 +3675,12 @@ final class _EvaluateVisitor _addRestMap(named, rest, restArgs, (value) => value); namedNodes.addAll({ for (var key in rest.contents.keys) - (key as SassString).text: restNodeForSpan + (key as SassString).text: restNodeForSpan, }); } else if (rest is SassList) { positional.addAll( - rest.asList.map((value) => _withoutSlash(value, restNodeForSpan))); + rest.asList.map((value) => _withoutSlash(value, restNodeForSpan)), + ); positionalNodes.addAll(List.filled(rest.lengthAsList, restNodeForSpan)); separator = rest.separator; @@ -3213,7 +3702,7 @@ final class _EvaluateVisitor positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: separator + separator: separator, ); } @@ -3223,19 +3712,20 @@ final class _EvaluateVisitor _addRestMap(named, keywordRest, keywordRestArgs, (value) => value); namedNodes.addAll({ for (var key in keywordRest.contents.keys) - (key as SassString).text: keywordRestNodeForSpan + (key as SassString).text: keywordRestNodeForSpan, }); return ( positional: positional, positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: separator + separator: separator, ); } else { throw _exception( - "Variable keyword arguments must be a map (was $keywordRest).", - keywordRestArgs.span); + "Variable keyword arguments must be a map (was $keywordRest).", + keywordRestArgs.span, + ); } } @@ -3257,20 +3747,33 @@ final class _EvaluateVisitor var rest = await restArgs.accept(this); var restNodeForSpan = _expressionNode(restArgs); if (rest is SassMap) { - _addRestMap(named, rest, invocation, - (value) => ValueExpression(value, restArgs.span)); + _addRestMap( + named, + rest, + invocation, + (value) => ValueExpression(value, restArgs.span), + ); } else if (rest is SassList) { - positional.addAll(rest.asList.map((value) => ValueExpression( - _withoutSlash(value, restNodeForSpan), restArgs.span))); + positional.addAll( + rest.asList.map( + (value) => ValueExpression( + _withoutSlash(value, restNodeForSpan), + restArgs.span, + ), + ), + ); if (rest is SassArgumentList) { rest.keywords.forEach((key, value) { named[key] = ValueExpression( - _withoutSlash(value, restNodeForSpan), restArgs.span); + _withoutSlash(value, restNodeForSpan), + restArgs.span, + ); }); } } else { positional.add( - ValueExpression(_withoutSlash(rest, restNodeForSpan), restArgs.span)); + ValueExpression(_withoutSlash(rest, restNodeForSpan), restArgs.span), + ); } var keywordRestArgs_ = invocation.arguments.keywordRest; @@ -3281,17 +3784,20 @@ final class _EvaluateVisitor var keywordRestNodeForSpan = _expressionNode(keywordRestArgs); if (keywordRest is SassMap) { _addRestMap( - named, - keywordRest, - invocation, - (value) => ValueExpression( - _withoutSlash(value, keywordRestNodeForSpan), - keywordRestArgs.span)); + named, + keywordRest, + invocation, + (value) => ValueExpression( + _withoutSlash(value, keywordRestNodeForSpan), + keywordRestArgs.span, + ), + ); return (positional, named); } else { throw _exception( - "Variable keyword arguments must be a map (was $keywordRest).", - keywordRestArgs.span); + "Variable keyword arguments must be a map (was $keywordRest).", + keywordRestArgs.span, + ); } } @@ -3306,27 +3812,38 @@ final class _EvaluateVisitor /// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling /// [AstNode.span] if the span isn't required, since some nodes need to do /// real work to manufacture a source span. - void _addRestMap(Map values, SassMap map, AstNode nodeWithSpan, - T convert(Value value)) { + void _addRestMap( + Map values, + SassMap map, + AstNode nodeWithSpan, + T convert(Value value), + ) { var expressionNode = _expressionNode(nodeWithSpan); map.contents.forEach((key, value) { if (key is SassString) { values[key.text] = convert(_withoutSlash(value, expressionNode)); } else { throw _exception( - "Variable keyword argument map must have string keys.\n" - "$key is not a string in $map.", - nodeWithSpan.span); + "Variable keyword argument map must have string keys.\n" + "$key is not a string in $map.", + nodeWithSpan.span, + ); } }); } /// Throws a [SassRuntimeException] if [positional] and [named] aren't valid /// when applied to [parameters]. - void _verifyArguments(int positional, Map named, - ParameterList parameters, AstNode nodeWithSpan) => + void _verifyArguments( + int positional, + Map named, + ParameterList parameters, + AstNode nodeWithSpan, + ) => _addExceptionSpan( - nodeWithSpan, () => parameters.verify(positional, MapKeySet(named))); + nodeWithSpan, + () => parameters.verify(positional, MapKeySet(named)), + ); Future visitSelectorExpression(SelectorExpression node) async => _styleRuleIgnoringAtRoot?.originalSelector.asSassList ?? sassNull; @@ -3337,26 +3854,30 @@ final class _EvaluateVisitor var oldInSupportsDeclaration = _inSupportsDeclaration; _inSupportsDeclaration = false; var result = SassString( - [ - for (var value in node.text.contents) - switch (value) { - String() => value, - Expression() => switch (await value.accept(this)) { - SassString(:var text) => text, - var result => _serialize(result, value, quote: false) - }, - _ => throw UnsupportedError("Unknown interpolation value $value") - } - ].join(), - quotes: node.hasQuotes); + [ + for (var value in node.text.contents) + switch (value) { + String() => value, + Expression() => switch (await value.accept(this)) { + SassString(:var text) => text, + var result => _serialize(result, value, quote: false), + }, + _ => throw UnsupportedError("Unknown interpolation value $value"), + }, + ].join(), + quotes: node.hasQuotes, + ); _inSupportsDeclaration = oldInSupportsDeclaration; return result; } Future visitSupportsExpression( - SupportsExpression expression) async => - SassString(await _visitSupportsCondition(expression.condition), - quotes: false); + SupportsExpression expression, + ) async => + SassString( + await _visitSupportsCondition(expression.condition), + quotes: false, + ); // ## Plain CSS @@ -3375,12 +3896,20 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "At-rules may not be used within nested declarations.", node.span); + "At-rules may not be used within nested declarations.", + node.span, + ); } if (node.isChildless) { - _parent.addChild(ModifiableCssAtRule(node.name, node.span, - childless: true, value: node.value)); + _parent.addChild( + ModifiableCssAtRule( + node.name, + node.span, + childless: true, + value: node.value, + ), + ); return; } @@ -3393,14 +3922,18 @@ final class _EvaluateVisitor } await _withParent( - ModifiableCssAtRule(node.name, node.span, value: node.value), () async { - // We don't have to check for an unknown at-rule in a style rule here, - // because the previous compilation has already bubbled the at-rule to the - // root. - for (var child in node.children) { - await child.accept(this); - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + ModifiableCssAtRule(node.name, node.span, value: node.value), + () async { + // We don't have to check for an unknown at-rule in a style rule here, + // because the previous compilation has already bubbled the at-rule to the + // root. + for (var child in node.children) { + await child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); _inUnknownAtRule = wasInUnknownAtRule; _inKeyframes = wasInKeyframes; @@ -3419,17 +3952,26 @@ final class _EvaluateVisitor } Future visitCssDeclaration(CssDeclaration node) async { - _parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span, + _parent.addChild( + ModifiableCssDeclaration( + node.name, + node.value, + node.span, parsedAsCustomProperty: node.parsedAsCustomProperty, - valueSpanForMap: node.valueSpanForMap)); + valueSpanForMap: node.valueSpanForMap, + ), + ); } Future visitCssImport(CssImport node) async { // NOTE: this logic is largely duplicated in [_visitStaticImport]. Most // changes here should be mirrored there. - var modifiableNode = - ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); + var modifiableNode = ModifiableCssImport( + node.url, + node.span, + modifiers: node.modifiers, + ); if (_parent != _root) { _parent.addChild(modifiableNode); } else if (_endOfImports == _root.children.length) { @@ -3445,11 +3987,16 @@ final class _EvaluateVisitor // here should be mirrored there. var rule = ModifiableCssKeyframeBlock(node.selector, node.span); - await _withParent(rule, () async { - for (var child in node.children) { - await child.accept(this); - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + await _withParent( + rule, + () async { + for (var child in node.children) { + await child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); } Future visitCssMediaRule(CssMediaRule node) async { @@ -3458,11 +4005,14 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Media rules may not be used within nested declarations.", node.span); + "Media rules may not be used within nested declarations.", + node.span, + ); } var mergedQueries = _mediaQueries.andThen( - (mediaQueries) => _mergeMediaQueries(mediaQueries, node.queries)); + (mediaQueries) => _mergeMediaQueries(mediaQueries, node.queries), + ); if (mergedQueries != null && mergedQueries.isEmpty) return; var mergedSources = mergedQueries == null @@ -3470,34 +4020,38 @@ final class _EvaluateVisitor : {..._mediaQuerySources!, ..._mediaQueries!, ...node.queries}; await _withParent( - ModifiableCssMediaRule(mergedQueries ?? node.queries, node.span), - () async { - await _withMediaQueries(mergedQueries ?? node.queries, mergedSources, + ModifiableCssMediaRule(mergedQueries ?? node.queries, node.span), + () async { + await _withMediaQueries( + mergedQueries ?? node.queries, + mergedSources, () async { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the media query so that - // declarations immediately inside @media have somewhere to go. - // - // For example, "a {@media screen {b: c}}" should produce - // "@media screen {a {b: c}}". - await _withParent(styleRule.copyWithoutChildren(), () async { - for (var child in node.children) { - await child.accept(this); + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the media query so that + // declarations immediately inside @media have somewhere to go. + // + // For example, "a {@media screen {b: c}}" should produce + // "@media screen {a {b: c}}". + await _withParent(styleRule.copyWithoutChildren(), () async { + for (var child in node.children) { + await child.accept(this); + } + }, scopeWhen: false); + } else { + for (var child in node.children) { + await child.accept(this); + } } - }, scopeWhen: false); - } else { - for (var child in node.children) { - await child.accept(this); - } - } - }); - }, - through: (node) => - node is CssStyleRule || - (mergedSources.isNotEmpty && - node is CssMediaRule && - node.queries.every(mergedSources.contains)), - scopeWhen: false); + }, + ); + }, + through: (node) => + node is CssStyleRule || + (mergedSources.isNotEmpty && + node is CssMediaRule && + node.queries.every(mergedSources.contains)), + scopeWhen: false, + ); } Future visitCssStyleRule(CssStyleRule node) async { @@ -3506,31 +4060,46 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Style rules may not be used within nested declarations.", node.span); + "Style rules may not be used within nested declarations.", + node.span, + ); } else if (_inKeyframes && _parent is CssKeyframeBlock) { throw _exception( - "Style rules may not be used within keyframe blocks.", node.span); + "Style rules may not be used within keyframe blocks.", + node.span, + ); } var styleRule = _styleRule; var nest = !(_styleRule?.fromPlainCss ?? false); var originalSelector = nest - ? node.selector.nestWithin(styleRule?.originalSelector, + ? node.selector.nestWithin( + styleRule?.originalSelector, implicitParent: !_atRootExcludingStyleRule, - preserveParentSelectors: node.fromPlainCss) + preserveParentSelectors: node.fromPlainCss, + ) : node.selector; var selector = _extensionStore.addSelector(originalSelector, _mediaQueries); - var rule = ModifiableCssStyleRule(selector, node.span, - originalSelector: originalSelector, fromPlainCss: node.fromPlainCss); + var rule = ModifiableCssStyleRule( + selector, + node.span, + originalSelector: originalSelector, + fromPlainCss: node.fromPlainCss, + ); var oldAtRootExcludingStyleRule = _atRootExcludingStyleRule; _atRootExcludingStyleRule = false; - await _withParent(rule, () async { - await _withStyleRule(rule, () async { - for (var child in node.children) { - await child.accept(this); - } - }); - }, through: nest ? (node) => node is CssStyleRule : null, scopeWhen: false); + await _withParent( + rule, + () async { + await _withStyleRule(rule, () async { + for (var child in node.children) { + await child.accept(this); + } + }); + }, + through: nest ? (node) => node is CssStyleRule : null, + scopeWhen: false, + ); _atRootExcludingStyleRule = oldAtRootExcludingStyleRule; if (_parent.children case [..., var lastChild] when styleRule == null) { @@ -3550,29 +4119,34 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Supports rules may not be used within nested declarations.", - node.span); + "Supports rules may not be used within nested declarations.", + node.span, + ); } - await _withParent(ModifiableCssSupportsRule(node.condition, node.span), - () async { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the supports rule so that - // declarations immediately inside @supports have somewhere to go. - // - // For example, "a {@supports (a: b) {b: c}}" should produce "@supports - // (a: b) {a {b: c}}". - await _withParent(styleRule.copyWithoutChildren(), () async { + await _withParent( + ModifiableCssSupportsRule(node.condition, node.span), + () async { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the supports rule so that + // declarations immediately inside @supports have somewhere to go. + // + // For example, "a {@supports (a: b) {b: c}}" should produce "@supports + // (a: b) {a {b: c}}". + await _withParent(styleRule.copyWithoutChildren(), () async { + for (var child in node.children) { + await child.accept(this); + } + }); + } else { for (var child in node.children) { await child.accept(this); } - }); - } else { - for (var child in node.children) { - await child.accept(this); } - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); } // ## Utilities @@ -3582,7 +4156,9 @@ final class _EvaluateVisitor /// Returns the value returned by [callback], or `null` if it only ever /// returned `null`. Future _handleReturn( - List list, Future callback(T value)) async { + List list, + Future callback(T value), + ) async { for (var value in list) { if (await callback(value) case var result?) return result; } @@ -3591,7 +4167,9 @@ final class _EvaluateVisitor /// Runs [callback] with [environment] as the current environment. Future _withEnvironment( - AsyncEnvironment environment, Future callback()) async { + AsyncEnvironment environment, + Future callback(), + ) async { var oldEnvironment = _environment; _environment = environment; var result = await callback(); @@ -3604,22 +4182,34 @@ final class _EvaluateVisitor /// If [trim] is `true`, removes whitespace around the result. If /// [warnForColor] is `true`, this will emit a warning for any named color /// values passed into the interpolation. - Future> _interpolationToValue(Interpolation interpolation, - {bool trim = false, bool warnForColor = false}) async { - var result = - await _performInterpolation(interpolation, warnForColor: warnForColor); - return CssValue(trim ? trimAscii(result, excludeEscape: true) : result, - interpolation.span); + Future> _interpolationToValue( + Interpolation interpolation, { + bool trim = false, + bool warnForColor = false, + }) async { + var result = await _performInterpolation( + interpolation, + warnForColor: warnForColor, + ); + return CssValue( + trim ? trimAscii(result, excludeEscape: true) : result, + interpolation.span, + ); } /// Evaluates [interpolation]. /// /// If [warnForColor] is `true`, this will emit a warning for any named color /// values passed into the interpolation. - Future _performInterpolation(Interpolation interpolation, - {bool warnForColor = false}) async { - var (result, _) = await _performInterpolationHelper(interpolation, - sourceMap: false, warnForColor: warnForColor); + Future _performInterpolation( + Interpolation interpolation, { + bool warnForColor = false, + }) async { + var (result, _) = await _performInterpolationHelper( + interpolation, + sourceMap: false, + warnForColor: warnForColor, + ); return result; } @@ -3627,19 +4217,24 @@ final class _EvaluateVisitor /// can map spans from the resulting string back to the original /// [interpolation]. Future<(String, InterpolationMap)> _performInterpolationWithMap( - Interpolation interpolation, - {bool warnForColor = false}) async { - var (result, map) = await _performInterpolationHelper(interpolation, - sourceMap: true, warnForColor: warnForColor); + Interpolation interpolation, { + bool warnForColor = false, + }) async { + var (result, map) = await _performInterpolationHelper( + interpolation, + sourceMap: true, + warnForColor: warnForColor, + ); return (result, map!); } /// A helper that implements the core logic of both [_performInterpolation] /// and [_performInterpolationWithMap]. Future<(String, InterpolationMap?)> _performInterpolationHelper( - Interpolation interpolation, - {required bool sourceMap, - bool warnForColor = false}) async { + Interpolation interpolation, { + required bool sourceMap, + bool warnForColor = false, + }) async { var targetLocations = sourceMap ? [] : null; var oldInSupportsDeclaration = _inSupportsDeclaration; _inSupportsDeclaration = false; @@ -3659,19 +4254,23 @@ final class _EvaluateVisitor if (warnForColor && namesByColor.containsKey(result)) { var alternative = BinaryOperationExpression( - BinaryOperator.plus, - StringExpression(Interpolation.plain("", interpolation.span), - quotes: true), - expression); + BinaryOperator.plus, + StringExpression( + Interpolation.plain("", interpolation.span), + quotes: true, + ), + expression, + ); _warn( - "You probably don't mean to use the color value " - "${namesByColor[result]} in interpolation here.\n" - "It may end up represented as $result, which will likely produce " - "invalid CSS.\n" - "Always quote color names when using them as strings or map keys " - '(for example, "${namesByColor[result]}").\n' - "If you really want to use the color value here, use '$alternative'.", - expression.span); + "You probably don't mean to use the color value " + "${namesByColor[result]} in interpolation here.\n" + "It may end up represented as $result, which will likely produce " + "invalid CSS.\n" + "Always quote color names when using them as strings or map keys " + '(for example, "${namesByColor[result]}").\n' + "If you really want to use the color value here, use '$alternative'.", + expression.span, + ); } buffer.write(_serialize(result, expression, quote: false)); @@ -3681,14 +4280,17 @@ final class _EvaluateVisitor return ( buffer.toString(), targetLocations.andThen( - (targetLocations) => InterpolationMap(interpolation, targetLocations)) + (targetLocations) => InterpolationMap(interpolation, targetLocations), + ), ); } /// Evaluates [expression] and calls `toCssString()` and wraps a /// [SassScriptException] to associate it with [span]. - Future _evaluateToCss(Expression expression, - {bool quote = true}) async => + Future _evaluateToCss( + Expression expression, { + bool quote = true, + }) async => _serialize(await expression.accept(this), expression, quote: quote); /// Calls `value.toCssString()` and wraps a [SassScriptException] to associate @@ -3717,9 +4319,12 @@ final class _EvaluateVisitor if (expression is VariableExpression) { return _addExceptionSpan( - expression, - () => _environment.getVariableNode(expression.name, - namespace: expression.namespace)) ?? + expression, + () => _environment.getVariableNode( + expression.name, + namespace: expression.namespace, + ), + ) ?? expression; } else { return expression; @@ -3735,8 +4340,11 @@ final class _EvaluateVisitor /// /// Runs [callback] in a new environment scope unless [scopeWhen] is false. Future _withParent( - S node, Future callback(), - {bool through(CssNode node)?, bool scopeWhen = true}) async { + S node, + Future callback(), { + bool through(CssNode node)?, + bool scopeWhen = true, + }) async { _addChild(node, through: through); var oldParent = _parent; @@ -3761,7 +4369,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw ArgumentError( - "through() must return false for at least one parent of $node."); + "through() must return false for at least one parent of $node.", + ); } } @@ -3787,7 +4396,9 @@ final class _EvaluateVisitor /// Runs [callback] with [rule] as the current style rule. Future _withStyleRule( - ModifiableCssStyleRule rule, Future callback()) async { + ModifiableCssStyleRule rule, + Future callback(), + ) async { var oldRule = _styleRuleIgnoringAtRoot; _styleRuleIgnoringAtRoot = rule; var result = await callback(); @@ -3800,8 +4411,11 @@ final class _EvaluateVisitor /// This also sets [sources] as the current set of media queries that were /// merged together to create [queries]. This is used to determine when it's /// safe to bubble one query through another. - Future _withMediaQueries(List? queries, - Set? sources, Future callback()) async { + Future _withMediaQueries( + List? queries, + Set? sources, + Future callback(), + ) async { var oldMediaQueries = _mediaQueries; var oldSources = _mediaQuerySources; _mediaQueries = queries; @@ -3821,7 +4435,10 @@ final class _EvaluateVisitor /// [AstNode.span] if the span isn't required, since some nodes need to do /// real work to manufacture a source span. Future _withStackFrame( - String member, AstNode nodeWithSpan, Future callback()) async { + String member, + AstNode nodeWithSpan, + Future callback(), + ) async { _stack.add((_member, nodeWithSpan)); var oldMember = _member; _member = member; @@ -3838,19 +4455,20 @@ final class _EvaluateVisitor String recommendation(SassNumber number) => switch (number.asSlash) { (var before, var after) => "math.div(${recommendation(before)}, ${recommendation(after)})", - _ => number.toString() + _ => number.toString(), }; _warn( - "Using / for division is deprecated and will be removed in Dart Sass " - "2.0.0.\n" - "\n" - "Recommendation: ${recommendation(value)}\n" - "\n" - "More info and automated migrator: " - "https://sass-lang.com/d/slash-div", - nodeForSpan.span, - Deprecation.slashDiv); + "Using / for division is deprecated and will be removed in Dart Sass " + "2.0.0.\n" + "\n" + "Recommendation: ${recommendation(value)}\n" + "\n" + "More info and automated migrator: " + "https://sass-lang.com/d/slash-div", + nodeForSpan.span, + Deprecation.slashDiv, + ); } return value.withoutSlash(); @@ -3858,8 +4476,12 @@ final class _EvaluateVisitor /// Creates a new stack frame with location information from [member] and /// [span]. - Frame _stackFrame(String member, FileSpan span) => frameForSpan(span, member, - url: span.sourceUrl.andThen((url) => _importCache?.humanize(url) ?? url)); + Frame _stackFrame(String member, FileSpan span) => frameForSpan( + span, + member, + url: + span.sourceUrl.andThen((url) => _importCache?.humanize(url) ?? url), + ); /// Returns a stack trace at the current point. /// @@ -3868,7 +4490,7 @@ final class _EvaluateVisitor var frames = [ for (var (member, nodeWithSpan) in _stack) _stackFrame(member, nodeWithSpan.span), - if (span != null) _stackFrame(_member, span) + if (span != null) _stackFrame(_member, span), ]; return Trace(frames.reversed); } @@ -3882,8 +4504,12 @@ final class _EvaluateVisitor if (deprecation == null) { _logger.warn(message, span: span, trace: trace); } else { - _logger.warnForDeprecation(deprecation, message, - span: span, trace: trace); + _logger.warnForDeprecation( + deprecation, + message, + span: span, + trace: trace, + ); } } @@ -3892,16 +4518,27 @@ final class _EvaluateVisitor /// If [span] is passed, it's used for the innermost stack frame. SassRuntimeException _exception(String message, [FileSpan? span]) => SassRuntimeException( - message, span ?? _stack.last.$2.span, _stackTrace(span)); + message, + span ?? _stack.last.$2.span, + _stackTrace(span), + ); /// Returns a [MultiSpanSassRuntimeException] with the given [message], /// [primaryLabel], and [secondaryLabels]. /// /// The primary span is taken from the current stack trace span. - SassRuntimeException _multiSpanException(String message, String primaryLabel, - Map secondaryLabels) => - MultiSpanSassRuntimeException(message, _stack.last.$2.span, primaryLabel, - secondaryLabels, _stackTrace()); + SassRuntimeException _multiSpanException( + String message, + String primaryLabel, + Map secondaryLabels, + ) => + MultiSpanSassRuntimeException( + message, + _stack.last.$2.span, + primaryLabel, + secondaryLabels, + _stackTrace(), + ); /// Runs [callback], and converts any [SassScriptException]s it throws to /// [SassRuntimeException]s with [nodeWithSpan]'s source span. @@ -3912,33 +4549,40 @@ final class _EvaluateVisitor /// /// If [addStackFrame] is true (the default), this will add an innermost stack /// frame for [nodeWithSpan]. Otherwise, it will use the existing stack as-is. - T _addExceptionSpan(AstNode nodeWithSpan, T callback(), - {bool addStackFrame = true}) { + T _addExceptionSpan( + AstNode nodeWithSpan, + T callback(), { + bool addStackFrame = true, + }) { try { return callback(); } on SassScriptException catch (error, stackTrace) { throwWithTrace( - error - .withSpan(nodeWithSpan.span) - .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), - error, - stackTrace); + error + .withSpan(nodeWithSpan.span) + .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), + error, + stackTrace, + ); } } /// Like [_addExceptionSpan], but for an asynchronous [callback]. Future _addExceptionSpanAsync( - AstNode nodeWithSpan, FutureOr callback(), - {bool addStackFrame = true}) async { + AstNode nodeWithSpan, + FutureOr callback(), { + bool addStackFrame = true, + }) async { try { return await callback(); } on SassScriptException catch (error, stackTrace) { throwWithTrace( - error - .withSpan(nodeWithSpan.span) - .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), - error, - stackTrace); + error + .withSpan(nodeWithSpan.span) + .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), + error, + stackTrace, + ); } } @@ -3952,7 +4596,10 @@ final class _EvaluateVisitor rethrow; } on SassException catch (error, stackTrace) { throwWithTrace( - error.withTrace(_stackTrace(error.span)), error, stackTrace); + error.withTrace(_stackTrace(error.span)), + error, + stackTrace, + ); } } @@ -3965,9 +4612,10 @@ final class _EvaluateVisitor } on SassRuntimeException catch (error, stackTrace) { if (!error.span.text.startsWith("@error")) rethrow; throwWithTrace( - SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), - error, - stackTrace); + SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), + error, + stackTrace, + ); } } @@ -4002,8 +4650,10 @@ final class _ImportedCssVisitor implements ModifiableCssVisitor { _ImportedCssVisitor(this._visitor); void visitCssAtRule(ModifiableCssAtRule node) { - _visitor._addChild(node, - through: node.isChildless ? null : (node) => node is CssStyleRule); + _visitor._addChild( + node, + through: node.isChildless ? null : (node) => node is CssStyleRule, + ); } void visitCssComment(ModifiableCssComment node) => _visitor._addChild(node); @@ -4035,9 +4685,11 @@ final class _ImportedCssVisitor implements ModifiableCssVisitor { var hasBeenMerged = mediaQueries == null || _visitor._mergeMediaQueries(mediaQueries, node.queries) != null; - _visitor._addChild(node, - through: (node) => - node is CssStyleRule || (hasBeenMerged && node is CssMediaRule)); + _visitor._addChild( + node, + through: (node) => + node is CssStyleRule || (hasBeenMerged && node is CssMediaRule), + ); } void visitCssStyleRule(ModifiableCssStyleRule node) => @@ -4060,7 +4712,7 @@ typedef EvaluateResult = ({ CssStylesheet stylesheet, /// The canonical URLs of all stylesheets loaded during compilation. - Set loadedUrls + Set loadedUrls, }); /// An implementation of [EvaluationContext] using the information available in @@ -4082,11 +4734,12 @@ final class _EvaluationContext implements EvaluationContext { void warn(String message, [Deprecation? deprecation]) { _visitor._warn( - message, - _visitor._importSpan ?? - _visitor._callableNode?.span ?? - _defaultWarnNodeWithSpan.span, - deprecation); + message, + _visitor._importSpan ?? + _visitor._callableNode?.span ?? + _defaultWarnNodeWithSpan.span, + deprecation, + ); } } @@ -4113,7 +4766,7 @@ typedef _ArgumentResults = ({ Map namedNodes, /// The separator used for the rest argument list, if any. - ListSeparator separator + ListSeparator separator, }); /// The result of loading a stylesheet via [AsyncEvaluator._loadStylesheet]. @@ -4129,5 +4782,5 @@ typedef _LoadedStylesheet = ( /// /// That is, whether this was (transitively) loaded through a load path or /// importer rather than relative to the entrypoint. - bool isDependency + bool isDependency, }); diff --git a/lib/src/visitor/clone_css.dart b/lib/src/visitor/clone_css.dart index d011c986a..3bc3a4e13 100644 --- a/lib/src/visitor/clone_css.dart +++ b/lib/src/visitor/clone_css.dart @@ -13,12 +13,14 @@ import 'interface/css.dart'; /// /// The [extender] must be associated with [stylesheet]. (ModifiableCssStylesheet, ExtensionStore) cloneCssStylesheet( - CssStylesheet stylesheet, ExtensionStore extensionStore) { + CssStylesheet stylesheet, + ExtensionStore extensionStore, +) { var (newExtensionStore, oldToNewSelectors) = extensionStore.clone(); return ( _CloneCssVisitor(oldToNewSelectors).visitCssStylesheet(stylesheet), - newExtensionStore + newExtensionStore, ); } @@ -31,8 +33,12 @@ final class _CloneCssVisitor implements CssVisitor { _CloneCssVisitor(this._oldToNewSelectors); ModifiableCssAtRule visitCssAtRule(CssAtRule node) { - var rule = ModifiableCssAtRule(node.name, node.span, - childless: node.isChildless, value: node.value); + var rule = ModifiableCssAtRule( + node.name, + node.span, + childless: node.isChildless, + value: node.value, + ); return node.isChildless ? rule : _visitChildren(rule, node); } @@ -40,16 +46,22 @@ final class _CloneCssVisitor implements CssVisitor { ModifiableCssComment(node.text, node.span); ModifiableCssDeclaration visitCssDeclaration(CssDeclaration node) => - ModifiableCssDeclaration(node.name, node.value, node.span, - parsedAsCustomProperty: node.parsedAsCustomProperty, - valueSpanForMap: node.valueSpanForMap); + ModifiableCssDeclaration( + node.name, + node.value, + node.span, + parsedAsCustomProperty: node.parsedAsCustomProperty, + valueSpanForMap: node.valueSpanForMap, + ); ModifiableCssImport visitCssImport(CssImport node) => ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); ModifiableCssKeyframeBlock visitCssKeyframeBlock(CssKeyframeBlock node) => _visitChildren( - ModifiableCssKeyframeBlock(node.selector, node.span), node); + ModifiableCssKeyframeBlock(node.selector, node.span), + node, + ); ModifiableCssMediaRule visitCssMediaRule(CssMediaRule node) => _visitChildren(ModifiableCssMediaRule(node.queries, node.span), node); @@ -57,13 +69,18 @@ final class _CloneCssVisitor implements CssVisitor { ModifiableCssStyleRule visitCssStyleRule(CssStyleRule node) { if (_oldToNewSelectors[node.selector] case var newSelector?) { return _visitChildren( - ModifiableCssStyleRule(newSelector, node.span, - originalSelector: node.originalSelector), - node); + ModifiableCssStyleRule( + newSelector, + node.span, + originalSelector: node.originalSelector, + ), + node, + ); } else { throw StateError( - "The ExtensionStore and CssStylesheet passed to cloneCssStylesheet() " - "must come from the same compilation."); + "The ExtensionStore and CssStylesheet passed to cloneCssStylesheet() " + "must come from the same compilation.", + ); } } @@ -72,12 +89,16 @@ final class _CloneCssVisitor implements CssVisitor { ModifiableCssSupportsRule visitCssSupportsRule(CssSupportsRule node) => _visitChildren( - ModifiableCssSupportsRule(node.condition, node.span), node); + ModifiableCssSupportsRule(node.condition, node.span), + node, + ); /// Visits [oldParent]'s children and adds their cloned values as children of /// [newParent], then returns [newParent]. T _visitChildren( - T newParent, CssParentNode oldParent) { + T newParent, + CssParentNode oldParent, + ) { for (var child in oldParent.children) { var newChild = child.accept(this); newChild.isGroupEnd = child.isGroupEnd; diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index a826deb7c..be4707f2b 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: 548e54482bead470a6c7931a9d3e098da8b600d6 +// Checksum: 25aa2d050126950ea37dc1c53539f0b041356e8e // // ignore_for_file: unused_import @@ -86,22 +86,24 @@ typedef _ScopeCallback = void Function(void Function() callback); /// declarations. /// /// Throws a [SassRuntimeException] if evaluation fails. -EvaluateResult evaluate(Stylesheet stylesheet, - {ImportCache? importCache, - NodeImporter? nodeImporter, - Importer? importer, - Iterable? functions, - Logger? logger, - bool quietDeps = false, - bool sourceMap = false}) => +EvaluateResult evaluate( + Stylesheet stylesheet, { + ImportCache? importCache, + NodeImporter? nodeImporter, + Importer? importer, + Iterable? functions, + Logger? logger, + bool quietDeps = false, + bool sourceMap = false, +}) => _EvaluateVisitor( - importCache: importCache, - nodeImporter: nodeImporter, - functions: functions, - logger: logger, - quietDeps: quietDeps, - sourceMap: sourceMap) - .run(importer, stylesheet); + importCache: importCache, + nodeImporter: nodeImporter, + functions: functions, + logger: logger, + quietDeps: quietDeps, + sourceMap: sourceMap, + ).run(importer, stylesheet); /// A class that can evaluate multiple independent statements and expressions /// in the context of a single module. @@ -115,13 +117,16 @@ final class Evaluator { /// Creates an evaluator. /// /// Arguments are the same as for [evaluate]. - Evaluator( - {ImportCache? importCache, - Importer? importer, - Iterable? functions, - Logger? logger}) - : _visitor = _EvaluateVisitor( - importCache: importCache, functions: functions, logger: logger), + Evaluator({ + ImportCache? importCache, + Importer? importer, + Iterable? functions, + Logger? logger, + }) : _visitor = _EvaluateVisitor( + importCache: importCache, + functions: functions, + logger: logger, + ), _importer = importer; void use(UseRule use) => _visitor.runStatement(_importer, use); @@ -339,14 +344,14 @@ final class _EvaluateVisitor /// Creates a new visitor. /// /// Most arguments are the same as those to [evaluate]. - _EvaluateVisitor( - {ImportCache? importCache, - NodeImporter? nodeImporter, - Iterable? functions, - Logger? logger, - bool quietDeps = false, - bool sourceMap = false}) - : _importCache = + _EvaluateVisitor({ + ImportCache? importCache, + NodeImporter? nodeImporter, + Iterable? functions, + Logger? logger, + bool quietDeps = false, + bool sourceMap = false, + }) : _importCache = importCache ?? (nodeImporter == null ? ImportCache.none() : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), @@ -359,43 +364,60 @@ final class _EvaluateVisitor // These functions are defined in the context of the evaluator because // they need access to the [_environment] or other local state. BuiltInCallable.function( - "global-variable-exists", r"$name, $module: null", (arguments) { - var variable = arguments[0].assertString("name"); - var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.globalVariableExists( - variable.text.replaceAll("_", "-"), - namespace: module?.text)); - }, url: "sass:meta"), + "global-variable-exists", + r"$name, $module: null", + (arguments) { + var variable = arguments[0].assertString("name"); + var module = arguments[1].realNull?.assertString("module"); + return SassBoolean( + _environment.globalVariableExists( + variable.text.replaceAll("_", "-"), + namespace: module?.text, + ), + ); + }, + url: "sass:meta", + ), BuiltInCallable.function("variable-exists", r"$name", (arguments) { var variable = arguments[0].assertString("name"); return SassBoolean( - _environment.variableExists(variable.text.replaceAll("_", "-"))); + _environment.variableExists(variable.text.replaceAll("_", "-")), + ); }, url: "sass:meta"), - BuiltInCallable.function("function-exists", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("function-exists", r"$name, $module: null", ( + arguments, + ) { var variable = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.functionExists( + return SassBoolean( + _environment.functionExists( variable.text.replaceAll("_", "-"), - namespace: module?.text) || - _builtInFunctions.containsKey(variable.text)); + namespace: module?.text, + ) || + _builtInFunctions.containsKey(variable.text), + ); }, url: "sass:meta"), - BuiltInCallable.function("mixin-exists", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("mixin-exists", r"$name, $module: null", ( + arguments, + ) { var variable = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); - return SassBoolean(_environment.mixinExists( + return SassBoolean( + _environment.mixinExists( variable.text.replaceAll("_", "-"), - namespace: module?.text)); + namespace: module?.text, + ), + ); }, url: "sass:meta"), BuiltInCallable.function("content-exists", "", (arguments) { if (!_environment.inMixin) { throw SassScriptException( - "content-exists() may only be called within a mixin."); + "content-exists() may only be called within a mixin.", + ); } return SassBoolean(_environment.content != null); }, url: "sass:meta"), @@ -409,7 +431,7 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.variables.pairs) - SassString(name): value + SassString(name): value, }); }, url: "sass:meta"), @@ -422,7 +444,7 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.functions.pairs) - SassString(name): SassFunction(value) + SassString(name): SassFunction(value), }); }, url: "sass:meta"), @@ -435,94 +457,122 @@ final class _EvaluateVisitor return SassMap({ for (var (name, value) in module.mixins.pairs) - SassString(name): SassMixin(value) + SassString(name): SassMixin(value), }); }, url: "sass:meta"), BuiltInCallable.function( - "get-function", r"$name, $css: false, $module: null", (arguments) { - var name = arguments[0].assertString("name"); - var css = arguments[1].isTruthy; - var module = arguments[2].realNull?.assertString("module"); - - if (css) { - if (module != null) { - throw r"$css and $module may not both be passed at once."; + "get-function", + r"$name, $css: false, $module: null", + (arguments) { + var name = arguments[0].assertString("name"); + var css = arguments[1].isTruthy; + var module = arguments[2].realNull?.assertString("module"); + + if (css) { + if (module != null) { + throw r"$css and $module may not both be passed at once."; + } + return SassFunction(PlainCssCallable(name.text)); } - return SassFunction(PlainCssCallable(name.text)); - } - var callable = _addExceptionSpan(_callableNode!, () { - var normalizedName = name.text.replaceAll("_", "-"); - var namespace = module?.text; - var local = - _environment.getFunction(normalizedName, namespace: namespace); - if (local != null || namespace != null) return local; - return _builtInFunctions[normalizedName]; - }); - if (callable == null) throw "Function not found: $name"; + var callable = _addExceptionSpan(_callableNode!, () { + var normalizedName = name.text.replaceAll("_", "-"); + var namespace = module?.text; + var local = _environment.getFunction( + normalizedName, + namespace: namespace, + ); + if (local != null || namespace != null) return local; + return _builtInFunctions[normalizedName]; + }); + if (callable == null) throw "Function not found: $name"; - return SassFunction(callable); - }, url: "sass:meta"), + return SassFunction(callable); + }, + url: "sass:meta", + ), - BuiltInCallable.function("get-mixin", r"$name, $module: null", - (arguments) { + BuiltInCallable.function("get-mixin", r"$name, $module: null", ( + arguments, + ) { var name = arguments[0].assertString("name"); var module = arguments[1].realNull?.assertString("module"); var callable = _addExceptionSpan( - _callableNode!, - () => _environment.getMixin(name.text.replaceAll("_", "-"), - namespace: module?.text)); + _callableNode!, + () => _environment.getMixin( + name.text.replaceAll("_", "-"), + namespace: module?.text, + ), + ); if (callable == null) throw "Mixin not found: $name"; return SassMixin(callable); }, url: "sass:meta"), - BuiltInCallable.function("call", r"$function, $args...", (arguments) { + BuiltInCallable.function("call", r"$function, $args...", ( + arguments, + ) { var function = arguments[0]; var args = arguments[1] as SassArgumentList; var callableNode = _callableNode!; - var invocation = ArgumentList([], {}, callableNode.span, - rest: ValueExpression(args, callableNode.span), - keywordRest: args.keywords.isEmpty - ? null - : ValueExpression( - SassMap({ - for (var (name, value) in args.keywords.pairs) - SassString(name, quotes: false): value - }), - callableNode.span)); + var invocation = ArgumentList( + [], + {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + keywordRest: args.keywords.isEmpty + ? null + : ValueExpression( + SassMap({ + for (var (name, value) in args.keywords.pairs) + SassString(name, quotes: false): value, + }), + callableNode.span, + ), + ); if (function is SassString) { warnForDeprecation( - "Passing a string to call() is deprecated and will be illegal in " - "Dart Sass 2.0.0.\n" - "\n" - "Recommendation: call(get-function($function))", - Deprecation.callString); + "Passing a string to call() is deprecated and will be illegal in " + "Dart Sass 2.0.0.\n" + "\n" + "Recommendation: call(get-function($function))", + Deprecation.callString, + ); var callableNode = _callableNode!; - var expression = - FunctionExpression(function.text, invocation, callableNode.span); + var expression = FunctionExpression( + function.text, + invocation, + callableNode.span, + ); return expression.accept(this); } var callable = function.assertFunction("function").callable; // ignore: unnecessary_type_check if (callable is Callable) { - return _runFunctionCallable(invocation, callable, _callableNode!); + return _runFunctionCallable( + invocation, + callable, + _callableNode!, + ); } else { throw SassScriptException( - "The function ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); + "The function ${callable.name} is asynchronous.\n" + "This is probably caused by a bug in a Sass plugin.", + ); } - }, url: "sass:meta") + }, url: "sass:meta"), ]; var metaMixins = [ - BuiltInCallable.mixin("load-css", r"$url, $with: null", (arguments) { + BuiltInCallable.mixin("load-css", r"$url, $with: null", ( + arguments, + ) { var url = Uri.parse(arguments[0].assertString("url").text); var withMap = arguments[1].realNull?.assertMap("with").contents; @@ -543,43 +593,61 @@ final class _EvaluateVisitor configuration = ExplicitConfiguration(values, callableNode); } - _loadModule(url, "load-css()", callableNode, - (module, _) => _combineCss(module, clone: true).accept(this), - baseUrl: callableNode.span.sourceUrl, - configuration: configuration, - namesInErrors: true); + _loadModule( + url, + "load-css()", + callableNode, + (module, _) => _combineCss(module, clone: true).accept(this), + baseUrl: callableNode.span.sourceUrl, + configuration: configuration, + namesInErrors: true, + ); _assertConfigurationIsEmpty(configuration, nameInError: true); }, url: "sass:meta"), - BuiltInCallable.mixin("apply", r"$mixin, $args...", (arguments) { - var mixin = arguments[0]; - var args = arguments[1] as SassArgumentList; - - var callableNode = _callableNode!; - var invocation = ArgumentList( - const [], - const {}, - callableNode.span, - rest: ValueExpression(args, callableNode.span), - ); - - var callable = mixin.assertMixin("mixin").callable; - var content = _environment.content; + BuiltInCallable.mixin( + "apply", + r"$mixin, $args...", + (arguments) { + var mixin = arguments[0]; + var args = arguments[1] as SassArgumentList; - // ignore: unnecessary_type_check - if (callable is Callable) { - _applyMixin( - callable, content, invocation, callableNode, callableNode); - } else { - throw SassScriptException( + var callableNode = _callableNode!; + var invocation = ArgumentList( + const [], + const {}, + callableNode.span, + rest: ValueExpression(args, callableNode.span), + ); + + var callable = mixin.assertMixin("mixin").callable; + var content = _environment.content; + + // ignore: unnecessary_type_check + if (callable is Callable) { + _applyMixin( + callable, + content, + invocation, + callableNode, + callableNode, + ); + } else { + throw SassScriptException( "The mixin ${callable.name} is asynchronous.\n" - "This is probably caused by a bug in a Sass plugin."); - } - }, url: "sass:meta", acceptsContent: true), + "This is probably caused by a bug in a Sass plugin.", + ); + } + }, + url: "sass:meta", + acceptsContent: true, + ), ]; - var metaModule = BuiltInModule("meta", - functions: [...meta.moduleFunctions, ...metaFunctions], - mixins: metaMixins); + var metaModule = BuiltInModule( + "meta", + functions: [...meta.moduleFunctions, ...metaFunctions], + mixins: metaMixins, + ); for (var module in [...coreModules, metaModule]) { _builtInModules[module.url] = module; @@ -590,8 +658,8 @@ final class _EvaluateVisitor ...globalFunctions, ...[ for (var function in metaFunctions) - function.withDeprecationWarning('meta') - ] + function.withDeprecationWarning('meta'), + ], ]; for (var function in functions) { _builtInFunctions[function.name.replaceAll("_", "-")] = function; @@ -600,15 +668,20 @@ final class _EvaluateVisitor EvaluateResult run(Importer? importer, Stylesheet node) { try { - return withEvaluationContext(_EvaluationContext(this, node), () { - if (node.span.sourceUrl case var url?) { - _activeModules[url] = null; - if (!(_asNodeSass && url.toString() == 'stdin')) _loadedUrls.add(url); - } + return withEvaluationContext( + _EvaluationContext(this, node), + () { + if (node.span.sourceUrl case var url?) { + _activeModules[url] = null; + if (!(_asNodeSass && url.toString() == 'stdin')) { + _loadedUrls.add(url); + } + } - var module = _addExceptionTrace(() => _execute(importer, node)); - return (stylesheet: _combineCss(module), loadedUrls: _loadedUrls); - }); + var module = _addExceptionTrace(() => _execute(importer, node)); + return (stylesheet: _combineCss(module), loadedUrls: _loadedUrls); + }, + ); } on SassException catch (error, stackTrace) { throwWithTrace(error.withLoadedUrls(_loadedUrls), error, stackTrace); } @@ -616,15 +689,23 @@ final class _EvaluateVisitor Value runExpression(Importer? importer, Expression expression) => withEvaluationContext( - _EvaluationContext(this, expression), - () => _withFakeStylesheet(importer, expression, - () => _addExceptionTrace(() => expression.accept(this)))); + _EvaluationContext(this, expression), + () => _withFakeStylesheet( + importer, + expression, + () => _addExceptionTrace(() => expression.accept(this)), + ), + ); void runStatement(Importer? importer, Statement statement) => withEvaluationContext( - _EvaluationContext(this, statement), - () => _withFakeStylesheet(importer, statement, - () => _addExceptionTrace(() => statement.accept(this)))); + _EvaluationContext(this, statement), + () => _withFakeStylesheet( + importer, + statement, + () => _addExceptionTrace(() => statement.accept(this)), + ), + ); /// Asserts that [value] is not `null` and returns it. /// @@ -639,7 +720,10 @@ final class _EvaluateVisitor /// Runs [callback] with [importer] as [_importer] and a fake [_stylesheet] /// with [nodeWithSpan]'s source span. T _withFakeStylesheet( - Importer? importer, AstNode nodeWithSpan, T callback()) { + Importer? importer, + AstNode nodeWithSpan, + T callback(), + ) { var oldImporter = _importer; _importer = importer; @@ -672,29 +756,40 @@ final class _EvaluateVisitor /// /// The [stackFrame] and [nodeWithSpan] are used for the name and location of /// the stack frame for the duration of the [callback]. - void _loadModule(Uri url, String stackFrame, AstNode nodeWithSpan, - void callback(Module module, bool firstLoad), - {Uri? baseUrl, - Configuration? configuration, - bool namesInErrors = false}) { + void _loadModule( + Uri url, + String stackFrame, + AstNode nodeWithSpan, + void callback(Module module, bool firstLoad), { + Uri? baseUrl, + Configuration? configuration, + bool namesInErrors = false, + }) { if (_builtInModules[url] case var builtInModule?) { if (configuration is ExplicitConfiguration) { throw _exception( - namesInErrors - ? "Built-in module $url can't be configured." - : "Built-in modules can't be configured.", - configuration.nodeWithSpan.span); + namesInErrors + ? "Built-in module $url can't be configured." + : "Built-in modules can't be configured.", + configuration.nodeWithSpan.span, + ); } // Always consider built-in stylesheets to be "already loaded", since they // never require additional execution to load and never produce CSS. - _addExceptionSpan(nodeWithSpan, () => callback(builtInModule, false)); + _addExceptionSpan( + nodeWithSpan, + () => callback(builtInModule, false), + ); return; } _withStackFrame(stackFrame, nodeWithSpan, () { - var (stylesheet, :importer, :isDependency) = - _loadStylesheet(url.toString(), nodeWithSpan.span, baseUrl: baseUrl); + var (stylesheet, :importer, :isDependency) = _loadStylesheet( + url.toString(), + nodeWithSpan.span, + baseUrl: baseUrl, + ); var canonicalUrl = stylesheet.span.sourceUrl; if (canonicalUrl != null) { @@ -704,9 +799,11 @@ final class _EvaluateVisitor "loaded." : "Module loop: this module is already being loaded."; - throw _activeModules[canonicalUrl].andThen((previousLoad) => - _multiSpanException(message, "new load", - {previousLoad.span: "original load"})) ?? + throw _activeModules[canonicalUrl].andThen( + (previousLoad) => _multiSpanException(message, "new load", { + previousLoad.span: "original load", + }), + ) ?? _exception(message); } else { _activeModules[canonicalUrl] = nodeWithSpan; @@ -718,17 +815,23 @@ final class _EvaluateVisitor _inDependency = isDependency; Module module; try { - module = _execute(importer, stylesheet, - configuration: configuration, - nodeWithSpan: nodeWithSpan, - namesInErrors: namesInErrors); + module = _execute( + importer, + stylesheet, + configuration: configuration, + nodeWithSpan: nodeWithSpan, + namesInErrors: namesInErrors, + ); } finally { _activeModules.remove(canonicalUrl); _inDependency = oldInDependency; } - _addExceptionSpan(nodeWithSpan, () => callback(module, firstLoad), - addStackFrame: false); + _addExceptionSpan( + nodeWithSpan, + () => callback(module, firstLoad), + addStackFrame: false, + ); }); } @@ -740,10 +843,13 @@ final class _EvaluateVisitor /// If [namesInErrors] is `true`, this includes the names of modules in errors /// relating to them. This should only be `true` if the names won't be obvious /// from the source span. - Module _execute(Importer? importer, Stylesheet stylesheet, - {Configuration? configuration, - AstNode? nodeWithSpan, - bool namesInErrors = false}) { + Module _execute( + Importer? importer, + Stylesheet stylesheet, { + Configuration? configuration, + AstNode? nodeWithSpan, + bool namesInErrors = false, + }) { var url = stylesheet.span.sourceUrl; if (_modules[url] case var alreadyLoaded?) { @@ -762,7 +868,7 @@ final class _EvaluateVisitor : null; var secondarySpans = { if (existingSpan != null) existingSpan: "original load", - if (configurationSpan != null) configurationSpan: "configuration" + if (configurationSpan != null) configurationSpan: "configuration", }; throw secondarySpans.isEmpty @@ -832,7 +938,10 @@ final class _EvaluateVisitor }); var module = environment.toModule( - css, preModuleComments ?? const {}, extensionStore); + css, + preModuleComments ?? const {}, + extensionStore, + ); if (url != null) { _modules[url] = module; _moduleConfigurations[url] = _configuration; @@ -850,8 +959,8 @@ final class _EvaluateVisitor var outOfOrderImports => [ ..._root.children.take(_endOfImports), ...outOfOrderImports, - ..._root.children.skip(_endOfImports) - ] + ..._root.children.skip(_endOfImports), + ], }; /// Returns a new stylesheet containing [root]'s CSS as well as the CSS of all @@ -943,11 +1052,15 @@ final class _EvaluateVisitor // Add all as-yet-unsatisfied extensions before adding downstream // [ExtensionStore]s, because those are all in [unsatisfiedExtensions] // already. - unsatisfiedExtensions.addAll(module.extensionStore.extensionsWhereTarget( - (target) => !originalSelectors.contains(target))); + unsatisfiedExtensions.addAll( + module.extensionStore.extensionsWhereTarget( + (target) => !originalSelectors.contains(target), + ), + ); - downstreamExtensionStores[module.url] - .andThen(module.extensionStore.addExtensions); + downstreamExtensionStores[module.url].andThen( + module.extensionStore.addExtensions, + ); if (module.extensionStore.isEmpty) continue; for (var upstream in module.upstream) { @@ -961,8 +1074,9 @@ final class _EvaluateVisitor // Remove all extensions that are now satisfied after adding downstream // [ExtensionStore]s so it counts any downstream extensions that have been // newly satisfied. - unsatisfiedExtensions.removeAll(module.extensionStore - .extensionsWhereTarget(originalSelectors.contains)); + unsatisfiedExtensions.removeAll( + module.extensionStore.extensionsWhereTarget(originalSelectors.contains), + ); } if (unsatisfiedExtensions.isNotEmpty) { @@ -973,9 +1087,10 @@ final class _EvaluateVisitor /// Throws an exception indicating that [extension] is unsatisfied. Never _throwForUnsatisfiedExtension(Extension extension) { throw SassException( - 'The target selector was not found.\n' - 'Use "@extend ${extension.target} !optional" to avoid this error.', - extension.span); + 'The target selector was not found.\n' + 'Use "@extend ${extension.target} !optional" to avoid this error.', + extension.span, + ); } /// Returns the index of the first node in [statements] that comes after all @@ -1010,7 +1125,8 @@ final class _EvaluateVisitor // module's definition, even if their assignments aren't reached. for (var (name, span) in node.globalVariables.pairs) { visitVariableDeclaration( - VariableDeclaration(name, NullExpression(span), span, guarded: true)); + VariableDeclaration(name, NullExpression(span), span, guarded: true), + ); } return null; @@ -1019,8 +1135,10 @@ final class _EvaluateVisitor Value? visitAtRootRule(AtRootRule node) { var query = AtRootQuery.defaultQuery; if (node.query case var unparsedQuery?) { - var (resolved, map) = - _performInterpolationWithMap(unparsedQuery, warnForColor: true); + var (resolved, map) = _performInterpolationWithMap( + unparsedQuery, + warnForColor: true, + ); query = AtRootQuery.parse(resolved, interpolationMap: map); } @@ -1033,7 +1151,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw StateError( - "CssNodes must have a CssStylesheet transitive parent node."); + "CssNodes must have a CssStylesheet transitive parent node.", + ); } } var root = _trimIncluded(included); @@ -1094,7 +1213,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw ArgumentError( - "Expected ${nodes[i]} to be an ancestor of $this."); + "Expected ${nodes[i]} to be an ancestor of $this.", + ); } } innermostContiguous ??= i; @@ -1118,10 +1238,11 @@ final class _EvaluateVisitor /// duration, based on which rules are excluded by [query]. It always assigns /// [_parent] to [newParent]. _ScopeCallback _scopeForAtRoot( - AtRootRule node, - ModifiableCssParentNode newParent, - AtRootQuery query, - List included) { + AtRootRule node, + ModifiableCssParentNode newParent, + AtRootQuery query, + List included, + ) { var scope = (void callback()) { // We can't use [_withParent] here because it'll add the node to the tree // in the wrong place. @@ -1171,7 +1292,8 @@ final class _EvaluateVisitor } Value visitContentBlock(ContentBlock node) => throw UnsupportedError( - "Evaluation handles @include and its content block together."); + "Evaluation handles @include and its content block together.", + ); Value? visitContentRule(ContentRule node) { var content = _environment.content; @@ -1190,20 +1312,24 @@ final class _EvaluateVisitor Value? visitDebugRule(DebugRule node) { var value = node.expression.accept(this); _logger.debug( - value is SassString ? value.text : serializeValue(value, inspect: true), - node.span); + value is SassString ? value.text : serializeValue(value, inspect: true), + node.span, + ); return null; } Value? visitDeclaration(Declaration node) { if (_styleRule == null && !_inUnknownAtRule && !_inKeyframes) { throw _exception( - "Declarations may only be used within style rules.", node.span); + "Declarations may only be used within style rules.", + node.span, + ); } if (_declarationName != null && node.isCustomProperty) { throw _exception( - 'Declarations whose names begin with "--" may not be nested.', - node.span); + 'Declarations whose names begin with "--" may not be nested.', + node.span, + ); } var siblings = _parent.parent!.children; @@ -1226,18 +1352,20 @@ final class _EvaluateVisitor // add no specificity and they're nested in the same parent as this // declaration. _warn( - "Sass's behavior for declarations that appear after nested\n" - "rules will be changing to match the behavior specified by CSS " - "in an upcoming\n" - "version. To keep the existing behavior, move the declaration " - "above the nested\n" - "rule. To opt into the new behavior, wrap the declaration in " - "`& {}`.\n" - "\n" - "More info: https://sass-lang.com/d/mixed-decls", - MultiSpan( - node.span, 'declaration', {sibling.span: 'nested rule'}), - Deprecation.mixedDecls); + "Sass's behavior for declarations that appear after nested\n" + "rules will be changing to match the behavior specified by CSS " + "in an upcoming\n" + "version. To keep the existing behavior, move the declaration " + "above the nested\n" + "rule. To opt into the new behavior, wrap the declaration in " + "`& {}`.\n" + "\n" + "More info: https://sass-lang.com/d/mixed-decls", + MultiSpan(node.span, 'declaration', { + sibling.span: 'nested rule', + }), + Deprecation.mixedDecls, + ); interleavedRules.clear(); break; } @@ -1254,16 +1382,23 @@ final class _EvaluateVisitor // If the value is an empty list, preserve it, because converting it to CSS // will throw an error that we want the user to see. if (!value.isBlank || _isEmptyList(value)) { - _parent.addChild(ModifiableCssDeclaration( - name, CssValue(value, expression.span), node.span, + _parent.addChild( + ModifiableCssDeclaration( + name, + CssValue(value, expression.span), + node.span, parsedAsCustomProperty: node.isCustomProperty, interleavedRules: interleavedRules, trace: interleavedRules.isEmpty ? null : _stackTrace(node.span), valueSpanForMap: - _sourceMap ? node.value.andThen(_expressionNode)?.span : null)); + _sourceMap ? node.value.andThen(_expressionNode)?.span : null, + ), + ); } else if (name.value.startsWith('--')) { throw _exception( - "Custom property values may not be empty.", expression.span); + "Custom property values may not be empty.", + expression.span, + ); } } @@ -1289,15 +1424,20 @@ final class _EvaluateVisitor var nodeWithSpan = _expressionNode(node.list); var setVariables = switch (node.variables) { [var variable] => (Value value) => _environment.setLocalVariable( - variable, _withoutSlash(value, nodeWithSpan), nodeWithSpan), + variable, + _withoutSlash(value, nodeWithSpan), + nodeWithSpan, + ), var variables => (Value value) => - _setMultipleVariables(variables, value, nodeWithSpan) + _setMultipleVariables(variables, value, nodeWithSpan), }; return _environment.scope(() { return _handleReturn(list.asList, (element) { setVariables(element); return _handleReturn( - node.children, (child) => child.accept(this)); + node.children, + (child) => child.accept(this), + ); }); }, semiGlobal: true); } @@ -1305,12 +1445,18 @@ final class _EvaluateVisitor /// Destructures [value] and assigns it to [variables], as in an `@each` /// statement. void _setMultipleVariables( - List variables, Value value, AstNode nodeWithSpan) { + List variables, + Value value, + AstNode nodeWithSpan, + ) { var list = value.asList; var minLength = math.min(variables.length, list.length); for (var i = 0; i < minLength; i++) { _environment.setLocalVariable( - variables[i], _withoutSlash(list[i], nodeWithSpan), nodeWithSpan); + variables[i], + _withoutSlash(list[i], nodeWithSpan), + nodeWithSpan, + ); } for (var i = minLength; i < variables.length; i++) { _environment.setLocalVariable(variables[i], sassNull, nodeWithSpan); @@ -1318,35 +1464,47 @@ final class _EvaluateVisitor } Value visitErrorRule(ErrorRule node) { - throw _exception(node.expression.accept(this).toString(), node.span); + throw _exception( + node.expression.accept(this).toString(), + node.span, + ); } Value? visitExtendRule(ExtendRule node) { var styleRule = _styleRule; if (styleRule == null || _declarationName != null) { throw _exception( - "@extend may only be used within style rules.", node.span); + "@extend may only be used within style rules.", + node.span, + ); } for (var complex in styleRule.originalSelector.components) { if (!complex.isBogus) continue; _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS and ' + - (complex.isUseless ? "can't" : "shouldn't") + - ' be an extender.\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - MultiSpan(complex.span.trimRight(), 'invalid selector', - {node.span: '@extend rule'}), - Deprecation.bogusCombinators); + 'The selector "${complex.toString().trim()}" is invalid CSS and ' + + (complex.isUseless ? "can't" : "shouldn't") + + ' be an extender.\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + MultiSpan(complex.span.trimRight(), 'invalid selector', { + node.span: '@extend rule', + }), + Deprecation.bogusCombinators, + ); } - var (targetText, targetMap) = - _performInterpolationWithMap(node.selector, warnForColor: true); + var (targetText, targetMap) = _performInterpolationWithMap( + node.selector, + warnForColor: true, + ); - var list = SelectorList.parse(trimAscii(targetText, excludeEscape: true), - interpolationMap: targetMap, allowParent: false); + var list = SelectorList.parse( + trimAscii(targetText, excludeEscape: true), + interpolationMap: targetMap, + allowParent: false, + ); for (var complex in list.components) { var compound = complex.singleCompound; @@ -1354,20 +1512,27 @@ final class _EvaluateVisitor // If the selector was a compound selector but not a simple // selector, emit a more explicit error. throw SassFormatException( - "complex selectors may not be extended.", complex.span); + "complex selectors may not be extended.", + complex.span, + ); } var simple = compound.singleSimple; if (simple == null) { throw SassFormatException( - "compound selectors may no longer be extended.\n" - "Consider `@extend ${compound.components.join(', ')}` instead.\n" - "See https://sass-lang.com/d/extend-compound for details.\n", - compound.span); + "compound selectors may no longer be extended.\n" + "Consider `@extend ${compound.components.join(', ')}` instead.\n" + "See https://sass-lang.com/d/extend-compound for details.\n", + compound.span, + ); } _extensionStore.addExtension( - styleRule.selector, simple, node, _mediaQueries); + styleRule.selector, + simple, + node, + _mediaQueries, + ); } return null; @@ -1379,18 +1544,22 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "At-rules may not be used within nested declarations.", node.span); + "At-rules may not be used within nested declarations.", + node.span, + ); } var name = _interpolationToValue(node.name); - var value = node.value.andThen((value) => - _interpolationToValue(value, trim: true, warnForColor: true)); + var value = node.value.andThen( + (value) => _interpolationToValue(value, trim: true, warnForColor: true), + ); var children = node.children; if (children == null) { _parent.addChild( - ModifiableCssAtRule(name, node.span, childless: true, value: value)); + ModifiableCssAtRule(name, node.span, childless: true, value: value), + ); return null; } @@ -1402,28 +1571,31 @@ final class _EvaluateVisitor _inUnknownAtRule = true; } - _withParent(ModifiableCssAtRule(name, node.span, value: value), () { - var styleRule = _styleRule; - if (styleRule == null || _inKeyframes || name.value == 'font-face') { - // Special-cased at-rules within style blocks are pulled out to the - // root. Equivalent to prepending "@at-root" on them. - for (var child in children) { - child.accept(this); - } - } else { - // If we're in a style rule, copy it into the at-rule so that - // declarations immediately inside it have somewhere to go. - // - // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". - _withParent(styleRule.copyWithoutChildren(), () { + _withParent( + ModifiableCssAtRule(name, node.span, value: value), + () { + var styleRule = _styleRule; + if (styleRule == null || _inKeyframes || name.value == 'font-face') { + // Special-cased at-rules within style blocks are pulled out to the + // root. Equivalent to prepending "@at-root" on them. for (var child in children) { child.accept(this); } - }, scopeWhen: false); - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + } else { + // If we're in a style rule, copy it into the at-rule so that + // declarations immediately inside it have somewhere to go. + // + // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". + _withParent(styleRule.copyWithoutChildren(), () { + for (var child in children) { + child.accept(this); + } + }, scopeWhen: false); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); _inUnknownAtRule = wasInUnknownAtRule; _inKeyframes = wasInKeyframes; @@ -1432,16 +1604,21 @@ final class _EvaluateVisitor Value? visitForRule(ForRule node) { var fromNumber = _addExceptionSpan( - node.from, () => node.from.accept(this).assertNumber()); - var toNumber = - _addExceptionSpan(node.to, () => node.to.accept(this).assertNumber()); + node.from, + () => node.from.accept(this).assertNumber(), + ); + var toNumber = _addExceptionSpan( + node.to, + () => node.to.accept(this).assertNumber(), + ); var from = _addExceptionSpan(node.from, () => fromNumber.assertInt()); var to = _addExceptionSpan( - node.to, - () => toNumber - .coerce(fromNumber.numeratorUnits, fromNumber.denominatorUnits) - .assertInt()); + node.to, + () => toNumber + .coerce(fromNumber.numeratorUnits, fromNumber.denominatorUnits) + .assertInt(), + ); var direction = from > to ? -1 : 1; if (!node.isExclusive) to += direction; @@ -1451,13 +1628,18 @@ final class _EvaluateVisitor var nodeWithSpan = _expressionNode(node.from); for (var i = from; i != to; i += direction) { _environment.setLocalVariable( - node.variable, - SassNumber.withUnits(i, - numeratorUnits: fromNumber.numeratorUnits, - denominatorUnits: fromNumber.denominatorUnits), - nodeWithSpan); + node.variable, + SassNumber.withUnits( + i, + numeratorUnits: fromNumber.numeratorUnits, + denominatorUnits: fromNumber.denominatorUnits, + ), + nodeWithSpan, + ); if (_handleReturn( - node.children, (child) => child.accept(this)) + node.children, + (child) => child.accept(this), + ) case var result?) { return result; } @@ -1471,26 +1653,31 @@ final class _EvaluateVisitor var adjustedConfiguration = oldConfiguration.throughForward(node); if (node.configuration.isNotEmpty) { - var newConfiguration = - _addForwardConfiguration(adjustedConfiguration, node); + var newConfiguration = _addForwardConfiguration( + adjustedConfiguration, + node, + ); _loadModule(node.url, "@forward", node, (module, firstLoad) { if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }, configuration: newConfiguration); - _removeUsedConfiguration(adjustedConfiguration, newConfiguration, - except: { - for (var variable in node.configuration) - if (!variable.isGuarded) variable.name - }); + _removeUsedConfiguration( + adjustedConfiguration, + newConfiguration, + except: { + for (var variable in node.configuration) + if (!variable.isGuarded) variable.name, + }, + ); // Remove all the variables that weren't configured by this particular // `@forward` before checking that the configuration is empty. Errors for // outer `with` clauses will be thrown once those clauses finish // executing. var configuredVariables = { - for (var variable in node.configuration) variable.name + for (var variable in node.configuration) variable.name, }; for (var name in newConfiguration.values.keys.toList()) { if (!configuredVariables.contains(name)) newConfiguration.remove(name); @@ -1512,7 +1699,9 @@ final class _EvaluateVisitor /// Updates [configuration] to include [node]'s configuration and returns the /// result. Configuration _addForwardConfiguration( - Configuration configuration, ForwardRule node) { + Configuration configuration, + ForwardRule node, + ) { var newValues = Map.of(configuration.values); for (var variable in node.configuration) { if (variable.isGuarded) { @@ -1525,9 +1714,13 @@ final class _EvaluateVisitor var variableNodeWithSpan = _expressionNode(variable.expression); newValues[variable.name] = ConfiguredValue.explicit( - _withoutSlash(variable.expression.accept(this), variableNodeWithSpan), - variable.span, - variableNodeWithSpan); + _withoutSlash( + variable.expression.accept(this), + variableNodeWithSpan, + ), + variable.span, + variableNodeWithSpan, + ); } if (configuration is ExplicitConfiguration || configuration.isEmpty) { @@ -1554,8 +1747,10 @@ final class _EvaluateVisitor /// Remove configured values from [upstream] that have been removed from /// [downstream], unless they match a name in [except]. void _removeUsedConfiguration( - Configuration upstream, Configuration downstream, - {required Set except}) { + Configuration upstream, + Configuration downstream, { + required Set except, + }) { for (var name in upstream.values.keys.toList()) { if (except.contains(name)) continue; if (!downstream.values.containsKey(name)) upstream.remove(name); @@ -1570,8 +1765,10 @@ final class _EvaluateVisitor /// If [nameInError] is `true`, this includes the name of the configured /// variable in the error message. This should only be `true` if the name /// won't be obvious from the source span. - void _assertConfigurationIsEmpty(Configuration configuration, - {bool nameInError = false}) { + void _assertConfigurationIsEmpty( + Configuration configuration, { + bool nameInError = false, + }) { // By definition, implicit configurations are allowed to only use a subset // of their values. if (configuration is! ExplicitConfiguration) return; @@ -1579,17 +1776,23 @@ final class _EvaluateVisitor var (name, value) = configuration.values.pairs.first; throw _exception( - nameInError - ? "\$$name was not declared with !default in the @used " - "module." - : "This variable was not declared with !default in the @used " - "module.", - value.configurationSpan); + nameInError + ? "\$$name was not declared with !default in the @used " + "module." + : "This variable was not declared with !default in the @used " + "module.", + value.configurationSpan, + ); } Value? visitFunctionRule(FunctionRule node) { - _environment.setFunction(UserDefinedCallable(node, _environment.closure(), - inDependency: _inDependency)); + _environment.setFunction( + UserDefinedCallable( + node, + _environment.closure(), + inDependency: _inDependency, + ), + ); return null; } @@ -1602,11 +1805,16 @@ final class _EvaluateVisitor } } - return clause.andThen((clause) => _environment.scope( + return clause.andThen( + (clause) => _environment.scope( () => _handleReturn( - clause.children, (child) => child.accept(this)), + clause.children, + (child) => child.accept(this), + ), semiGlobal: true, - when: clause.hasDeclarations)); + when: clause.hasDeclarations, + ), + ); } Value? visitImportRule(ImportRule node) { @@ -1623,15 +1831,22 @@ final class _EvaluateVisitor /// Adds the stylesheet imported by [import] to the current document. void _visitDynamicImport(DynamicImport import) { return _withStackFrame("@import", import, () { - var (stylesheet, :importer, :isDependency) = - _loadStylesheet(import.urlString, import.span, forImport: true); + var (stylesheet, :importer, :isDependency) = _loadStylesheet( + import.urlString, + import.span, + forImport: true, + ); var url = stylesheet.span.sourceUrl; if (url != null) { if (_activeModules.containsKey(url)) { - throw _activeModules[url].andThen((previousLoad) => - _multiSpanException("This file is already being loaded.", - "new load", {previousLoad.span: "original load"})) ?? + throw _activeModules[url].andThen( + (previousLoad) => _multiSpanException( + "This file is already being loaded.", + "new load", + {previousLoad.span: "original load"}, + ), + ) ?? _exception("This file is already being loaded."); } _activeModules[url] = import; @@ -1718,8 +1933,10 @@ final class _EvaluateVisitor // clone all modules' CSS. Otherwise, it's possible that they'll be // used or imported from another location that shouldn't have the same // extensions applied. - _combineCss(module, clone: module.transitivelyContainsExtensions) - .accept(this); + _combineCss( + module, + clone: module.transitivelyContainsExtensions, + ).accept(this); } var visitor = _ImportedCssVisitor(this); @@ -1737,23 +1954,32 @@ final class _EvaluateVisitor /// /// This first tries loading [url] relative to [baseUrl], which defaults to /// `_stylesheet.span.sourceUrl`. - _LoadedStylesheet _loadStylesheet(String url, FileSpan span, - {Uri? baseUrl, bool forImport = false}) { + _LoadedStylesheet _loadStylesheet( + String url, + FileSpan span, { + Uri? baseUrl, + bool forImport = false, + }) { try { assert(_importSpan == null); _importSpan = span; if (_importCache case var importCache?) { baseUrl ??= _stylesheet.span.sourceUrl; - if (importCache.canonicalize(Uri.parse(url), - baseImporter: _importer, baseUrl: baseUrl, forImport: forImport) + if (importCache.canonicalize( + Uri.parse(url), + baseImporter: _importer, + baseUrl: baseUrl, + forImport: forImport, + ) case (var importer, var canonicalUrl, :var originalUrl)) { if (canonicalUrl.scheme == '') { _logger.warnForDeprecation( - Deprecation.relativeCanonical, - "Importer $importer canonicalized $url to $canonicalUrl.\n" - "Relative canonical URLs are deprecated and will eventually be " - "disallowed."); + Deprecation.relativeCanonical, + "Importer $importer canonicalized $url to $canonicalUrl.\n" + "Relative canonical URLs are deprecated and will eventually be " + "disallowed.", + ); } // Make sure we record the canonical URL as "loaded" even if the // actual load fails, because watchers should watch it to see if it @@ -1761,8 +1987,11 @@ final class _EvaluateVisitor _loadedUrls.add(canonicalUrl); var isDependency = _inDependency || importer != _importer; - if (importCache.importCanonical(importer, canonicalUrl, - originalUrl: originalUrl) + if (importCache.importCanonical( + importer, + canonicalUrl, + originalUrl: originalUrl, + ) case var stylesheet?) { return (stylesheet, importer: importer, isDependency: isDependency); } @@ -1771,7 +2000,10 @@ final class _EvaluateVisitor if (_nodeImporter != null) { if (_importLikeNode( - url, baseUrl ?? _stylesheet.span.sourceUrl, forImport) + url, + baseUrl ?? _stylesheet.span.sourceUrl, + forImport, + ) case var result?) { result.$1.span.sourceUrl.andThen(_loadedUrls.add); return result; @@ -1800,7 +2032,10 @@ final class _EvaluateVisitor /// /// Returns the [Stylesheet], or `null` if the import failed. _LoadedStylesheet? _importLikeNode( - String originalUrl, Uri? previous, bool forImport) { + String originalUrl, + Uri? previous, + bool forImport, + ) { var result = _nodeImporter!.loadRelative(originalUrl, previous, forImport); bool isDependency; @@ -1815,10 +2050,12 @@ final class _EvaluateVisitor var (contents, url) = result; return ( Stylesheet.parse( - contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, - url: url), + contents, + url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, + url: url, + ), importer: null, - isDependency: isDependency + isDependency: isDependency, ); } @@ -1828,9 +2065,12 @@ final class _EvaluateVisitor // here should be mirrored there. var node = ModifiableCssImport( - _interpolationToValue(import.url), import.span, - modifiers: - import.modifiers.andThen?>(_interpolationToValue)); + _interpolationToValue(import.url), + import.span, + modifiers: import.modifiers.andThen?>( + _interpolationToValue, + ), + ); if (_parent != _root) { _parent.addChild(node); @@ -1844,11 +2084,12 @@ final class _EvaluateVisitor /// Evaluate a given [mixin] with [arguments] and [contentCallable] void _applyMixin( - Callable? mixin, - UserDefinedCallable? contentCallable, - ArgumentList arguments, - AstNode nodeWithSpan, - AstNode nodeWithSpanWithoutContent) { + Callable? mixin, + UserDefinedCallable? contentCallable, + ArgumentList arguments, + AstNode nodeWithSpan, + AstNode nodeWithSpanWithoutContent, + ) { switch (mixin) { case null: throw _exception("Undefined mixin.", nodeWithSpan.span); @@ -1857,44 +2098,58 @@ final class _EvaluateVisitor { var evaluated = _evaluateArguments(arguments); var (overload, _) = mixin.callbackFor( - evaluated.positional.length, MapKeySet(evaluated.named)); + evaluated.positional.length, + MapKeySet(evaluated.named), + ); throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - nodeWithSpanWithoutContent.span, - "invocation", - {overload.spanWithName: "declaration"}, - _stackTrace(nodeWithSpanWithoutContent.span)); + "Mixin doesn't accept a content block.", + nodeWithSpanWithoutContent.span, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(nodeWithSpanWithoutContent.span), + ); } case BuiltInCallable(): _environment.withContent(contentCallable, () { _environment.asMixin(() { - _runBuiltInCallable(arguments, mixin, nodeWithSpanWithoutContent); + _runBuiltInCallable( + arguments, + mixin, + nodeWithSpanWithoutContent, + ); }); }); case UserDefinedCallable( - declaration: MixinRule(hasContent: false) + declaration: MixinRule(hasContent: false), ) when contentCallable != null: throw MultiSpanSassRuntimeException( - "Mixin doesn't accept a content block.", - nodeWithSpanWithoutContent.span, - "invocation", - {mixin.declaration.parameters.spanWithName: "declaration"}, - _stackTrace(nodeWithSpanWithoutContent.span)); + "Mixin doesn't accept a content block.", + nodeWithSpanWithoutContent.span, + "invocation", + {mixin.declaration.parameters.spanWithName: "declaration"}, + _stackTrace(nodeWithSpanWithoutContent.span), + ); case UserDefinedCallable(): - _runUserDefinedCallable(arguments, mixin, nodeWithSpanWithoutContent, - () { - _environment.withContent(contentCallable, () { - _environment.asMixin(() { - for (var statement in mixin.declaration.children) { - _addErrorSpan( - nodeWithSpanWithoutContent, () => statement.accept(this)); - } + _runUserDefinedCallable( + arguments, + mixin, + nodeWithSpanWithoutContent, + () { + _environment.withContent(contentCallable, () { + _environment.asMixin(() { + for (var statement in mixin.declaration.children) { + _addErrorSpan( + nodeWithSpanWithoutContent, + () => statement.accept(this), + ); + } + }); }); - }); - }); + }, + ); case _: throw UnsupportedError("Unknown callable type $mixin."); @@ -1902,36 +2157,54 @@ final class _EvaluateVisitor } Value? visitIncludeRule(IncludeRule node) { - var mixin = _addExceptionSpan(node, - () => _environment.getMixin(node.name, namespace: node.namespace)); + var mixin = _addExceptionSpan( + node, + () => _environment.getMixin(node.name, namespace: node.namespace), + ); if (node.originalName.startsWith('--') && mixin is UserDefinedCallable && !mixin.declaration.originalName.startsWith('--')) { _warn( - 'Sass @mixin names beginning with -- are deprecated for forward-' - 'compatibility with plain CSS mixins.\n' - '\n' - 'For details, see https://sass-lang.com/d/css-function-mixin', - node.nameSpan, - Deprecation.cssFunctionMixin); + 'Sass @mixin names beginning with -- are deprecated for forward-' + 'compatibility with plain CSS mixins.\n' + '\n' + 'For details, see https://sass-lang.com/d/css-function-mixin', + node.nameSpan, + Deprecation.cssFunctionMixin, + ); } - var contentCallable = node.content.andThen((content) => UserDefinedCallable( - content, _environment.closure(), - inDependency: _inDependency)); + var contentCallable = node.content.andThen( + (content) => UserDefinedCallable( + content, + _environment.closure(), + inDependency: _inDependency, + ), + ); - var nodeWithSpanWithoutContent = - AstNode.fake(() => node.spanWithoutContent); + var nodeWithSpanWithoutContent = AstNode.fake( + () => node.spanWithoutContent, + ); - _applyMixin(mixin, contentCallable, node.arguments, node, - nodeWithSpanWithoutContent); + _applyMixin( + mixin, + contentCallable, + node.arguments, + node, + nodeWithSpanWithoutContent, + ); return null; } Value? visitMixinRule(MixinRule node) { - _environment.setMixin(UserDefinedCallable(node, _environment.closure(), - inDependency: _inDependency)); + _environment.setMixin( + UserDefinedCallable( + node, + _environment.closure(), + inDependency: _inDependency, + ), + ); return null; } @@ -1959,54 +2232,67 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Media rules may not be used within nested declarations.", node.span); + "Media rules may not be used within nested declarations.", + node.span, + ); } var queries = _visitMediaQueries(node.query); - var mergedQueries = _mediaQueries - .andThen((mediaQueries) => _mergeMediaQueries(mediaQueries, queries)); + var mergedQueries = _mediaQueries.andThen( + (mediaQueries) => _mergeMediaQueries(mediaQueries, queries), + ); if (mergedQueries != null && mergedQueries.isEmpty) return null; var mergedSources = mergedQueries == null ? const {} : {..._mediaQuerySources!, ..._mediaQueries!, ...queries}; - _withParent(ModifiableCssMediaRule(mergedQueries ?? queries, node.span), - () { - _withMediaQueries(mergedQueries ?? queries, mergedSources, () { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the media query so that - // declarations immediately inside @media have somewhere to go. - // - // For example, "a {@media screen {b: c}}" should produce - // "@media screen {a {b: c}}". - _withParent(styleRule.copyWithoutChildren(), () { - for (var child in node.children) { - child.accept(this); + _withParent( + ModifiableCssMediaRule(mergedQueries ?? queries, node.span), + () { + _withMediaQueries( + mergedQueries ?? queries, + mergedSources, + () { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the media query so that + // declarations immediately inside @media have somewhere to go. + // + // For example, "a {@media screen {b: c}}" should produce + // "@media screen {a {b: c}}". + _withParent(styleRule.copyWithoutChildren(), () { + for (var child in node.children) { + child.accept(this); + } + }, scopeWhen: false); + } else { + for (var child in node.children) { + child.accept(this); + } } - }, scopeWhen: false); - } else { - for (var child in node.children) { - child.accept(this); - } - } - }); - }, - through: (node) => - node is CssStyleRule || - (mergedSources.isNotEmpty && - node is CssMediaRule && - node.queries.every(mergedSources.contains)), - scopeWhen: node.hasDeclarations); + }, + ); + }, + through: (node) => + node is CssStyleRule || + (mergedSources.isNotEmpty && + node is CssMediaRule && + node.queries.every(mergedSources.contains)), + scopeWhen: node.hasDeclarations, + ); return null; } /// Evaluates [interpolation] and parses the result as a list of media /// queries. - List _visitMediaQueries(Interpolation interpolation) { - var (resolved, map) = - _performInterpolationWithMap(interpolation, warnForColor: true); + List _visitMediaQueries( + Interpolation interpolation, + ) { + var (resolved, map) = _performInterpolationWithMap( + interpolation, + warnForColor: true, + ); return CssMediaQuery.parseList(resolved, interpolationMap: map); } @@ -2017,7 +2303,9 @@ final class _EvaluateVisitor /// and [queries2], or `null` if there are contexts that can't be represented /// by media queries. List? _mergeMediaQueries( - Iterable queries1, Iterable queries2) { + Iterable queries1, + Iterable queries2, + ) { var queries = []; for (var query1 in queries1) { inner: @@ -2046,71 +2334,97 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Style rules may not be used within nested declarations.", node.span); + "Style rules may not be used within nested declarations.", + node.span, + ); } else if (_inKeyframes && _parent is CssKeyframeBlock) { throw _exception( - "Style rules may not be used within keyframe blocks.", node.span); + "Style rules may not be used within keyframe blocks.", + node.span, + ); } - var (selectorText, selectorMap) = - _performInterpolationWithMap(node.selector, warnForColor: true); + var (selectorText, selectorMap) = _performInterpolationWithMap( + node.selector, + warnForColor: true, + ); if (_inKeyframes) { // NOTE: this logic is largely duplicated in [visitCssKeyframeBlock]. Most // changes here should be mirrored there. - var parsedSelector = - KeyframeSelectorParser(selectorText, interpolationMap: selectorMap) - .parse(); + var parsedSelector = KeyframeSelectorParser( + selectorText, + interpolationMap: selectorMap, + ).parse(); var rule = ModifiableCssKeyframeBlock( - CssValue(List.unmodifiable(parsedSelector), node.selector.span), - node.span); - _withParent(rule, () { - for (var child in node.children) { - child.accept(this); - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + CssValue(List.unmodifiable(parsedSelector), node.selector.span), + node.span, + ); + _withParent( + rule, + () { + for (var child in node.children) { + child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); return null; } - var parsedSelector = SelectorList.parse(selectorText, - interpolationMap: selectorMap, plainCss: _stylesheet.plainCss); + var parsedSelector = SelectorList.parse( + selectorText, + interpolationMap: selectorMap, + plainCss: _stylesheet.plainCss, + ); var nest = !(_styleRule?.fromPlainCss ?? false); if (nest) { if (_stylesheet.plainCss) { for (var complex in parsedSelector.components) { - if (complex.leadingCombinators case [var first, ...] - when _stylesheet.plainCss) { + if (complex.leadingCombinators + case [ + var first, + ..., + ] when _stylesheet.plainCss) { throw _exception( - "Top-level leading combinators aren't allowed in plain CSS.", - first.span); + "Top-level leading combinators aren't allowed in plain CSS.", + first.span, + ); } } } parsedSelector = parsedSelector.nestWithin( - _styleRuleIgnoringAtRoot?.originalSelector, - implicitParent: !_atRootExcludingStyleRule, - preserveParentSelectors: _stylesheet.plainCss); + _styleRuleIgnoringAtRoot?.originalSelector, + implicitParent: !_atRootExcludingStyleRule, + preserveParentSelectors: _stylesheet.plainCss, + ); } var selector = _extensionStore.addSelector(parsedSelector, _mediaQueries); - var rule = ModifiableCssStyleRule(selector, node.span, - originalSelector: parsedSelector, fromPlainCss: _stylesheet.plainCss); + var rule = ModifiableCssStyleRule( + selector, + node.span, + originalSelector: parsedSelector, + fromPlainCss: _stylesheet.plainCss, + ); var oldAtRootExcludingStyleRule = _atRootExcludingStyleRule; _atRootExcludingStyleRule = false; - _withParent(rule, () { - _withStyleRule(rule, () { - for (var child in node.children) { - child.accept(this); - } - }); - }, - through: nest ? (node) => node is CssStyleRule : null, - scopeWhen: node.hasDeclarations); + _withParent( + rule, + () { + _withStyleRule(rule, () { + for (var child in node.children) { + child.accept(this); + } + }); + }, + through: nest ? (node) => node is CssStyleRule : null, + scopeWhen: node.hasDeclarations, + ); _atRootExcludingStyleRule = oldAtRootExcludingStyleRule; _warnForBogusCombinators(rule); @@ -2131,42 +2445,45 @@ final class _EvaluateVisitor if (complex.isUseless) { _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS. It ' - 'will be omitted from the generated CSS.\n' + 'The selector "${complex.toString().trim()}" is invalid CSS. It ' + 'will be omitted from the generated CSS.\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + complex.span.trimRight(), + Deprecation.bogusCombinators, + ); + } else if (complex.leadingCombinators.isNotEmpty) { + if (!_stylesheet.plainCss) { + _warn( + 'The selector "${complex.toString().trim()}" is invalid CSS.\n' 'This will be an error in Dart Sass 2.0.0.\n' '\n' 'More info: https://sass-lang.com/d/bogus-combinators', complex.span.trimRight(), - Deprecation.bogusCombinators); - } else if (complex.leadingCombinators.isNotEmpty) { - if (!_stylesheet.plainCss) { - _warn( - 'The selector "${complex.toString().trim()}" is invalid CSS.\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - complex.span.trimRight(), - Deprecation.bogusCombinators); + Deprecation.bogusCombinators, + ); } } else { _warn( - 'The selector "${complex.toString().trim()}" is only valid for ' - "nesting and shouldn't\n" - 'have children other than style rules.' + - (complex.isBogusOtherThanLeadingCombinator - ? ' It will be omitted from the generated CSS.' - : '') + - '\n' - 'This will be an error in Dart Sass 2.0.0.\n' - '\n' - 'More info: https://sass-lang.com/d/bogus-combinators', - MultiSpan(complex.span.trimRight(), 'invalid selector', { - rule.children.first.span: "this is not a style rule" + - (rule.children.every((child) => child is CssComment) - ? '\n(try converting to a //-style comment)' - : '') - }), - Deprecation.bogusCombinators); + 'The selector "${complex.toString().trim()}" is only valid for ' + "nesting and shouldn't\n" + 'have children other than style rules.' + + (complex.isBogusOtherThanLeadingCombinator + ? ' It will be omitted from the generated CSS.' + : '') + + '\n' + 'This will be an error in Dart Sass 2.0.0.\n' + '\n' + 'More info: https://sass-lang.com/d/bogus-combinators', + MultiSpan(complex.span.trimRight(), 'invalid selector', { + rule.children.first.span: "this is not a style rule" + + (rule.children.every((child) => child is CssComment) + ? '\n(try converting to a //-style comment)' + : ''), + }), + Deprecation.bogusCombinators, + ); } } } @@ -2178,32 +2495,38 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Supports rules may not be used within nested declarations.", - node.span); - } - - var condition = - CssValue(_visitSupportsCondition(node.condition), node.condition.span); - _withParent(ModifiableCssSupportsRule(condition, node.span), () { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the supports rule so that - // declarations immediately inside @supports have somewhere to go. - // - // For example, "a {@supports (a: b) {b: c}}" should produce "@supports - // (a: b) {a {b: c}}". - _withParent(styleRule.copyWithoutChildren(), () { + "Supports rules may not be used within nested declarations.", + node.span, + ); + } + + var condition = CssValue( + _visitSupportsCondition(node.condition), + node.condition.span, + ); + _withParent( + ModifiableCssSupportsRule(condition, node.span), + () { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the supports rule so that + // declarations immediately inside @supports have somewhere to go. + // + // For example, "a {@supports (a: b) {b: c}}" should produce "@supports + // (a: b) {a {b: c}}". + _withParent(styleRule.copyWithoutChildren(), () { + for (var child in node.children) { + child.accept(this); + } + }); + } else { for (var child in node.children) { child.accept(this); } - }); - } else { - for (var child in node.children) { - child.accept(this); } - } - }, - through: (node) => node is CssStyleRule, - scopeWhen: node.hasDeclarations); + }, + through: (node) => node is CssStyleRule, + scopeWhen: node.hasDeclarations, + ); return null; } @@ -2216,18 +2539,22 @@ final class _EvaluateVisitor "${operation.operator} " "${_parenthesize(operation.right, operation.operator)}", SupportsNegation negation => "not ${_parenthesize(negation.condition)}", - SupportsInterpolation interpolation => - _evaluateToCss(interpolation.expression, quote: false), - SupportsDeclaration declaration => - _withSupportsDeclaration(() => "(${_evaluateToCss(declaration.name)}:" - "${declaration.isCustomProperty ? '' : ' '}" - "${_evaluateToCss(declaration.value)})"), + SupportsInterpolation interpolation => _evaluateToCss( + interpolation.expression, + quote: false, + ), + SupportsDeclaration declaration => _withSupportsDeclaration( + () => "(${_evaluateToCss(declaration.name)}:" + "${declaration.isCustomProperty ? '' : ' '}" + "${_evaluateToCss(declaration.value)})", + ), SupportsFunction function => "${_performInterpolation(function.name)}(" "${_performInterpolation(function.arguments)})", SupportsAnything anything => "(${_performInterpolation(anything.contents)})", var condition => throw ArgumentError( - "Unknown supports condition type ${condition.runtimeType}.") + "Unknown supports condition type ${condition.runtimeType}.", + ), }; /// Runs [callback] in a context where [_inSupportsDeclaration] is true. @@ -2247,7 +2574,10 @@ final class _EvaluateVisitor /// If [operator] is passed, it's the operator for the surrounding /// [SupportsOperation], and is used to determine whether parentheses are /// necessary if [condition] is also a [SupportsOperation]. - String _parenthesize(SupportsCondition condition, [String? operator]) { + String _parenthesize( + SupportsCondition condition, [ + String? operator, + ]) { switch (condition) { case SupportsNegation(): case SupportsOperation() @@ -2266,41 +2596,54 @@ final class _EvaluateVisitor when override.value != sassNull) { _addExceptionSpan(node, () { _environment.setVariable( - node.name, override.value, override.assignmentNode, - global: true); + node.name, + override.value, + override.assignmentNode, + global: true, + ); }); return null; } } - var value = _addExceptionSpan(node, - () => _environment.getVariable(node.name, namespace: node.namespace)); + var value = _addExceptionSpan( + node, + () => _environment.getVariable(node.name, namespace: node.namespace), + ); if (value != null && value != sassNull) return null; } if (node.isGlobal && !_environment.globalVariableExists(node.name)) { _warn( - _environment.atRoot - ? "As of Dart Sass 2.0.0, !global assignments won't be able to " - "declare new variables.\n" - "\n" - "Since this assignment is at the root of the stylesheet, the " - "!global flag is\n" - "unnecessary and can safely be removed." - : "As of Dart Sass 2.0.0, !global assignments won't be able to " - "declare new variables.\n" - "\n" - "Recommendation: add `${node.originalName}: null` at the " - "stylesheet root.", - node.span, - Deprecation.newGlobal); + _environment.atRoot + ? "As of Dart Sass 2.0.0, !global assignments won't be able to " + "declare new variables.\n" + "\n" + "Since this assignment is at the root of the stylesheet, the " + "!global flag is\n" + "unnecessary and can safely be removed." + : "As of Dart Sass 2.0.0, !global assignments won't be able to " + "declare new variables.\n" + "\n" + "Recommendation: add `${node.originalName}: null` at the " + "stylesheet root.", + node.span, + Deprecation.newGlobal, + ); } - var value = _withoutSlash(node.expression.accept(this), node.expression); + var value = _withoutSlash( + node.expression.accept(this), + node.expression, + ); _addExceptionSpan(node, () { _environment.setVariable( - node.name, value, _expressionNode(node.expression), - namespace: node.namespace, global: node.isGlobal); + node.name, + value, + _expressionNode(node.expression), + namespace: node.namespace, + global: node.isGlobal, + ); }); return null; } @@ -2312,10 +2655,13 @@ final class _EvaluateVisitor for (var variable in node.configuration) { var variableNodeWithSpan = _expressionNode(variable.expression); values[variable.name] = ConfiguredValue.explicit( - _withoutSlash( - variable.expression.accept(this), variableNodeWithSpan), - variable.span, - variableNodeWithSpan); + _withoutSlash( + variable.expression.accept(this), + variableNodeWithSpan, + ), + variable.span, + variableNodeWithSpan, + ); } configuration = ExplicitConfiguration(values, node); } @@ -2330,24 +2676,34 @@ final class _EvaluateVisitor } Value? visitWarnRule(WarnRule node) { - var value = _addExceptionSpan(node, () => node.expression.accept(this)); + var value = _addExceptionSpan( + node, + () => node.expression.accept(this), + ); _logger.warn( - value is SassString ? value.text : _serialize(value, node.expression), - trace: _stackTrace(node.span)); + value is SassString ? value.text : _serialize(value, node.expression), + trace: _stackTrace(node.span), + ); return null; } Value? visitWhileRule(WhileRule node) { - return _environment.scope(() { - while (node.condition.accept(this).isTruthy) { - if (_handleReturn( - node.children, (child) => child.accept(this)) - case var result?) { - return result; + return _environment.scope( + () { + while (node.condition.accept(this).isTruthy) { + if (_handleReturn( + node.children, + (child) => child.accept(this), + ) + case var result?) { + return result; + } } - } - return null; - }, semiGlobal: true, when: node.hasDeclarations); + return null; + }, + semiGlobal: true, + when: node.hasDeclarations, + ); } // ## Expressions @@ -2357,30 +2713,44 @@ final class _EvaluateVisitor node.operator != BinaryOperator.singleEquals && node.operator != BinaryOperator.dividedBy) { throw _exception( - "Operators aren't allowed in plain CSS.", node.operatorSpan); + "Operators aren't allowed in plain CSS.", + node.operatorSpan, + ); } return _addExceptionSpan(node, () { var left = node.left.accept(this); return switch (node.operator) { - BinaryOperator.singleEquals => - left.singleEquals(node.right.accept(this)), + BinaryOperator.singleEquals => left.singleEquals( + node.right.accept(this), + ), BinaryOperator.or => left.isTruthy ? left : node.right.accept(this), BinaryOperator.and => left.isTruthy ? node.right.accept(this) : left, - BinaryOperator.equals => SassBoolean(left == node.right.accept(this)), - BinaryOperator.notEquals => - SassBoolean(left != node.right.accept(this)), - BinaryOperator.greaterThan => left.greaterThan(node.right.accept(this)), - BinaryOperator.greaterThanOrEquals => - left.greaterThanOrEquals(node.right.accept(this)), + BinaryOperator.equals => SassBoolean( + left == node.right.accept(this), + ), + BinaryOperator.notEquals => SassBoolean( + left != node.right.accept(this), + ), + BinaryOperator.greaterThan => left.greaterThan( + node.right.accept(this), + ), + BinaryOperator.greaterThanOrEquals => left.greaterThanOrEquals( + node.right.accept(this), + ), BinaryOperator.lessThan => left.lessThan(node.right.accept(this)), - BinaryOperator.lessThanOrEquals => - left.lessThanOrEquals(node.right.accept(this)), + BinaryOperator.lessThanOrEquals => left.lessThanOrEquals( + node.right.accept(this), + ), BinaryOperator.plus => left.plus(node.right.accept(this)), BinaryOperator.minus => left.minus(node.right.accept(this)), BinaryOperator.times => left.times(node.right.accept(this)), - BinaryOperator.dividedBy => _slash(left, node.right.accept(this), node), - BinaryOperator.modulo => left.modulo(node.right.accept(this)) + BinaryOperator.dividedBy => _slash( + left, + node.right.accept(this), + node, + ), + BinaryOperator.modulo => left.modulo(node.right.accept(this)), }; }); } @@ -2401,24 +2771,25 @@ final class _EvaluateVisitor BinaryOperationExpression( operator: BinaryOperator.dividedBy, :var left, - :var right + :var right, ) => "math.div(${recommendation(left)}, ${recommendation(right)})", ParenthesizedExpression() => expression.expression.toString(), - _ => expression.toString() + _ => expression.toString(), }; _warn( - "Using / for division outside of calc() is deprecated " - "and will be removed in Dart Sass 2.0.0.\n" - "\n" - "Recommendation: ${recommendation(node)} or " - "${expressionToCalc(node)}\n" - "\n" - "More info and automated migrator: " - "https://sass-lang.com/d/slash-div", - node.span, - Deprecation.slashDiv); + "Using / for division outside of calc() is deprecated " + "and will be removed in Dart Sass 2.0.0.\n" + "\n" + "Recommendation: ${recommendation(node)} or " + "${expressionToCalc(node)}\n" + "\n" + "More info and automated migrator: " + "https://sass-lang.com/d/slash-div", + node.span, + Deprecation.slashDiv, + ); return result; case _: @@ -2437,27 +2808,31 @@ final class _EvaluateVisitor const { "calc", "clamp", "hypot", "sin", "cos", "tan", "asin", "acos", // "atan", "sqrt", "exp", "sign", "mod", "rem", "atan2", "pow", // - "log", "calc-size" + "log", "calc-size", }.contains(node.name.toLowerCase()) && _environment.getFunction(node.name) == null); Value visitValueExpression(ValueExpression node) => node.value; Value visitVariableExpression(VariableExpression node) { - var result = _addExceptionSpan(node, - () => _environment.getVariable(node.name, namespace: node.namespace)); + var result = _addExceptionSpan( + node, + () => _environment.getVariable(node.name, namespace: node.namespace), + ); if (result != null) return result; throw _exception("Undefined variable.", node.span); } - Value visitUnaryOperationExpression(UnaryOperationExpression node) { + Value visitUnaryOperationExpression( + UnaryOperationExpression node, + ) { var operand = node.operand.accept(this); return _addExceptionSpan(node, () { return switch (node.operator) { UnaryOperator.plus => operand.unaryPlus(), UnaryOperator.minus => operand.unaryMinus(), UnaryOperator.divide => operand.unaryDivide(), - UnaryOperator.not => operand.unaryNot() + UnaryOperator.not => operand.unaryNot(), }; }); } @@ -2486,15 +2861,20 @@ final class _EvaluateVisitor Value visitParenthesizedExpression(ParenthesizedExpression node) => _stylesheet.plainCss ? throw _exception( - "Parentheses aren't allowed in plain CSS.", node.span) + "Parentheses aren't allowed in plain CSS.", + node.span, + ) : node.expression.accept(this); SassColor visitColorExpression(ColorExpression node) => node.value; SassList visitListExpression(ListExpression node) => SassList( - node.contents.map((Expression expression) => expression.accept(this)), - node.separator, - brackets: node.hasBrackets); + node.contents.map( + (Expression expression) => expression.accept(this), + ), + node.separator, + brackets: node.hasBrackets, + ); SassMap visitMapExpression(MapExpression node) { var map = {}; @@ -2506,11 +2886,12 @@ final class _EvaluateVisitor if (map.containsKey(keyValue)) { var oldValueSpan = keyNodes[keyValue]?.span; throw MultiSpanSassRuntimeException( - 'Duplicate key.', - key.span, - 'second key', - {if (oldValueSpan != null) oldValueSpan: 'first key'}, - _stackTrace(key.span)); + 'Duplicate key.', + key.span, + 'second key', + {if (oldValueSpan != null) oldValueSpan: 'first key'}, + _stackTrace(key.span), + ); } map[keyValue] = valueValue; keyNodes[keyValue] = key; @@ -2523,8 +2904,11 @@ final class _EvaluateVisitor ? null : _addExceptionSpan( node, - () => - _environment.getFunction(node.name, namespace: node.namespace)); + () => _environment.getFunction( + node.name, + namespace: node.namespace, + ), + ); if (function == null) { if (node.namespace != null) { throw _exception("Undefined function.", node.span); @@ -2536,8 +2920,9 @@ final class _EvaluateVisitor case ("min" || "max" || "round" || "abs") && var name when node.arguments.named.isEmpty && node.arguments.rest == null && - node.arguments.positional - .every((argument) => argument.isCalculationSafe): + node.arguments.positional.every( + (argument) => argument.isCalculationSafe, + ): return _visitCalculation(node, inLegacySassFunction: name); case "calc" || @@ -2581,7 +2966,9 @@ final class _EvaluateVisitor var oldInFunction = _inFunction; _inFunction = true; var result = _addErrorSpan( - node, () => _runFunctionCallable(node.arguments, function, node)); + node, + () => _runFunctionCallable(node.arguments, function, node), + ); _inFunction = oldInFunction; return result; } @@ -2593,21 +2980,29 @@ final class _EvaluateVisitor /// with the old global `min()`, `max()`, `round()`, and `abs()` functions. /// The parameter is the name of the function, which is used for reporting /// deprecation warnings. - Value _visitCalculation(FunctionExpression node, - {String? inLegacySassFunction}) { + Value _visitCalculation( + FunctionExpression node, { + String? inLegacySassFunction, + }) { if (node.arguments.named.isNotEmpty) { throw _exception( - "Keyword arguments can't be used with calculations.", node.span); + "Keyword arguments can't be used with calculations.", + node.span, + ); } else if (node.arguments.rest != null) { throw _exception( - "Rest arguments can't be used with calculations.", node.span); + "Rest arguments can't be used with calculations.", + node.span, + ); } _checkCalculationArguments(node); var arguments = [ for (var argument in node.arguments.positional) - _visitCalculationExpression(argument, - inLegacySassFunction: inLegacySassFunction) + _visitCalculationExpression( + argument, + inLegacySassFunction: inLegacySassFunction, + ), ]; if (_inSupportsDeclaration) { return SassCalculation.unsimplified(node.name, arguments); @@ -2632,27 +3027,45 @@ final class _EvaluateVisitor "min" => SassCalculation.min(arguments), "max" => SassCalculation.max(arguments), "hypot" => SassCalculation.hypot(arguments), - "pow" => - SassCalculation.pow(arguments[0], arguments.elementAtOrNull(1)), - "atan2" => - SassCalculation.atan2(arguments[0], arguments.elementAtOrNull(1)), - "log" => - SassCalculation.log(arguments[0], arguments.elementAtOrNull(1)), - "mod" => - SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)), - "rem" => - SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)), - "round" => SassCalculation.roundInternal(arguments[0], - arguments.elementAtOrNull(1), arguments.elementAtOrNull(2), + "pow" => SassCalculation.pow( + arguments[0], + arguments.elementAtOrNull(1), + ), + "atan2" => SassCalculation.atan2( + arguments[0], + arguments.elementAtOrNull(1), + ), + "log" => SassCalculation.log( + arguments[0], + arguments.elementAtOrNull(1), + ), + "mod" => SassCalculation.mod( + arguments[0], + arguments.elementAtOrNull(1), + ), + "rem" => SassCalculation.rem( + arguments[0], + arguments.elementAtOrNull(1), + ), + "round" => SassCalculation.roundInternal( + arguments[0], + arguments.elementAtOrNull(1), + arguments.elementAtOrNull(2), span: node.span, inLegacySassFunction: inLegacySassFunction, warn: (message, [deprecation]) => - _warn(message, node.span, deprecation)), - "clamp" => SassCalculation.clamp(arguments[0], - arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)), - "calc-size" => - SassCalculation.calcSize(arguments[0], arguments.elementAtOrNull(1)), - _ => throw UnsupportedError('Unknown calculation name "${node.name}".') + _warn(message, node.span, deprecation), + ), + "clamp" => SassCalculation.clamp( + arguments[0], + arguments.elementAtOrNull(1), + arguments.elementAtOrNull(2), + ), + "calc-size" => SassCalculation.calcSize( + arguments[0], + arguments.elementAtOrNull(1), + ), + _ => throw UnsupportedError('Unknown calculation name "${node.name}".'), }; } on SassScriptException catch (error, stackTrace) { // The simplification logic in the [SassCalculation] static methods will @@ -2675,12 +3088,16 @@ final class _EvaluateVisitor } else if (maxArgs != null && node.arguments.positional.length > maxArgs) { throw _exception( - "Only $maxArgs ${pluralize('argument', maxArgs)} allowed, but " - "${node.arguments.positional.length} " + - pluralize('was', node.arguments.positional.length, - plural: 'were') + - " passed.", - node.span); + "Only $maxArgs ${pluralize('argument', maxArgs)} allowed, but " + "${node.arguments.positional.length} " + + pluralize( + 'was', + node.arguments.positional.length, + plural: 'were', + ) + + " passed.", + node.span, + ); } } @@ -2713,14 +3130,18 @@ final class _EvaluateVisitor /// /// The [nodesWithSpans] should correspond to the spans for [args]. void _verifyCompatibleNumbers( - List args, List nodesWithSpans) { + List args, + List nodesWithSpans, + ) { // Note: this logic is largely duplicated in // SassCalculation._verifyCompatibleNumbers and most changes here should // also be reflected there. for (var i = 0; i < args.length; i++) { if (args[i] case SassNumber arg when arg.hasComplexUnits) { - throw _exception("Number $arg isn't compatible with CSS calculations.", - nodesWithSpans[i].span); + throw _exception( + "Number $arg isn't compatible with CSS calculations.", + nodesWithSpans[i].span, + ); } } @@ -2734,11 +3155,12 @@ final class _EvaluateVisitor if (number1.hasPossiblyCompatibleUnits(number2)) continue; throw MultiSpanSassRuntimeException( - "$number1 and $number2 are incompatible.", - nodesWithSpans[i].span, - number1.toString(), - {nodesWithSpans[j].span: number2.toString()}, - _stackTrace(nodesWithSpans[i].span)); + "$number1 and $number2 are incompatible.", + nodesWithSpans[i].span, + number1.toString(), + {nodesWithSpans[j].span: number2.toString()}, + _stackTrace(nodesWithSpans[i].span), + ); } } } @@ -2750,12 +3172,16 @@ final class _EvaluateVisitor /// with the old global `min()`, `max()`, `round()`, and `abs()` functions. /// The parameter is the name of the function, which is used for reporting /// deprecation warnings. - Object _visitCalculationExpression(Expression node, - {required String? inLegacySassFunction}) { + Object _visitCalculationExpression( + Expression node, { + required String? inLegacySassFunction, + }) { switch (node) { case ParenthesizedExpression(expression: var inner): - var result = _visitCalculationExpression(inner, - inLegacySassFunction: inLegacySassFunction); + var result = _visitCalculationExpression( + inner, + inLegacySassFunction: inLegacySassFunction, + ); return result is SassString ? SassString('(${result.text})', quotes: false) : result; @@ -2768,23 +3194,32 @@ final class _EvaluateVisitor 'infinity' => SassNumber(double.infinity), '-infinity' => SassNumber(double.negativeInfinity), 'nan' => SassNumber(double.nan), - _ => SassString(_performInterpolation(node.text), quotes: false) + _ => SassString( + _performInterpolation(node.text), + quotes: false, + ), }; case BinaryOperationExpression(:var operator, :var left, :var right): _checkWhitespaceAroundCalculationOperator(node); return _addExceptionSpan( - node, - () => SassCalculation.operateInternal( - _binaryOperatorToCalculationOperator(operator, node), - _visitCalculationExpression(left, - inLegacySassFunction: inLegacySassFunction), - _visitCalculationExpression(right, - inLegacySassFunction: inLegacySassFunction), - inLegacySassFunction: inLegacySassFunction, - simplify: !_inSupportsDeclaration, - warn: (message, [deprecation]) => - _warn(message, node.span, deprecation))); + node, + () => SassCalculation.operateInternal( + _binaryOperatorToCalculationOperator(operator, node), + _visitCalculationExpression( + left, + inLegacySassFunction: inLegacySassFunction, + ), + _visitCalculationExpression( + right, + inLegacySassFunction: inLegacySassFunction, + ), + inLegacySassFunction: inLegacySassFunction, + simplify: !_inSupportsDeclaration, + warn: (message, [deprecation]) => + _warn(message, node.span, deprecation), + ), + ); case NumberExpression() || VariableExpression() || @@ -2795,18 +3230,22 @@ final class _EvaluateVisitor SassCalculation result => result, SassString result when !result.hasQuotes => result, var result => throw _exception( - "Value $result can't be used in a calculation.", node.span) + "Value $result can't be used in a calculation.", + node.span, + ), }; case ListExpression( hasBrackets: false, separator: ListSeparator.space, - contents: [_, _, ...] + contents: [_, _, ...], ): var elements = [ for (var element in node.contents) - _visitCalculationExpression(element, - inLegacySassFunction: inLegacySassFunction) + _visitCalculationExpression( + element, + inLegacySassFunction: inLegacySassFunction, + ), ]; _checkAdjacentCalculationValues(elements, node); @@ -2823,14 +3262,17 @@ final class _EvaluateVisitor case _: assert(!node.isCalculationSafe); throw _exception( - "This expression can't be used in a calculation.", node.span); + "This expression can't be used in a calculation.", + node.span, + ); } } /// Throws an error if [node] requires whitespace around its operator in a /// calculation but doesn't have it. void _checkWhitespaceAroundCalculationOperator( - BinaryOperationExpression node) { + BinaryOperationExpression node, + ) { if (node.operator != BinaryOperator.plus && node.operator != BinaryOperator.minus) { return; @@ -2842,33 +3284,42 @@ final class _EvaluateVisitor if (node.left.span.file != node.right.span.file) return; if (node.left.span.end.offset >= node.right.span.start.offset) return; - var textBetweenOperands = node.left.span.file - .getText(node.left.span.end.offset, node.right.span.start.offset); + var textBetweenOperands = node.left.span.file.getText( + node.left.span.end.offset, + node.right.span.start.offset, + ); var first = textBetweenOperands.codeUnitAt(0); var last = textBetweenOperands.codeUnitAt(textBetweenOperands.length - 1); if (!(first.isWhitespace || first == $slash) || !(last.isWhitespace || last == $slash)) { throw _exception( - '"+" and "-" must be surrounded by whitespace in calculations.', - node.operatorSpan); + '"+" and "-" must be surrounded by whitespace in calculations.', + node.operatorSpan, + ); } } /// Returns the [CalculationOperator] that corresponds to [operator]. CalculationOperator _binaryOperatorToCalculationOperator( - BinaryOperator operator, BinaryOperationExpression node) => + BinaryOperator operator, + BinaryOperationExpression node, + ) => switch (operator) { BinaryOperator.plus => CalculationOperator.plus, BinaryOperator.minus => CalculationOperator.minus, BinaryOperator.times => CalculationOperator.times, BinaryOperator.dividedBy => CalculationOperator.dividedBy, _ => throw _exception( - "This operation can't be used in a calculation.", node.operatorSpan) + "This operation can't be used in a calculation.", + node.operatorSpan, + ), }; /// Throws an error if [elements] contains two adjacent non-string values. void _checkAdjacentCalculationValues( - List elements, ListExpression node) { + List elements, + ListExpression node, + ) { assert(elements.length > 1); for (var i = 1; i < elements.length; i++) { @@ -2880,7 +3331,7 @@ final class _EvaluateVisitor var currentNode = node.contents[i]; if (currentNode case UnaryOperationExpression( - operator: UnaryOperator.minus || UnaryOperator.plus + operator: UnaryOperator.minus || UnaryOperator.plus, ) || NumberExpression(value: < 0)) { // `calc(1 -2)` parses as a space-separated list whose second value is a @@ -2888,22 +3339,28 @@ final class _EvaluateVisitor // expression doesn't help the user understand what's going wrong. We // add special case error handling to help clarify the issue. throw _exception( - '"+" and "-" must be surrounded by whitespace in calculations.', - currentNode.span.subspan(0, 1)); + '"+" and "-" must be surrounded by whitespace in calculations.', + currentNode.span.subspan(0, 1), + ); } else { - throw _exception('Missing math operator.', - previousNode.span.expand(currentNode.span)); + throw _exception( + 'Missing math operator.', + previousNode.span.expand(currentNode.span), + ); } } } Value visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) { + InterpolatedFunctionExpression node, + ) { var function = PlainCssCallable(_performInterpolation(node.name)); var oldInFunction = _inFunction; _inFunction = true; var result = _addErrorSpan( - node, () => _runFunctionCallable(node.arguments, function, node)); + node, + () => _runFunctionCallable(node.arguments, function, node), + ); _inFunction = oldInFunction; return result; } @@ -2911,10 +3368,11 @@ final class _EvaluateVisitor /// Evaluates the arguments in [arguments] as applied to [callable], and /// invokes [run] in a scope with those arguments defined. V _runUserDefinedCallable( - ArgumentList arguments, - UserDefinedCallable callable, - AstNode nodeWithSpan, - V run()) { + ArgumentList arguments, + UserDefinedCallable callable, + AstNode nodeWithSpan, + V run(), + ) { // TODO(nweiz): Set [trackSpans] to `null` once we're no longer emitting // deprecation warnings for /-as-division. var evaluated = _evaluateArguments(arguments); @@ -2931,15 +3389,24 @@ final class _EvaluateVisitor // don't affect the underlying environment closure. return _withEnvironment(callable.environment.closure(), () { return _environment.scope(() { - _verifyArguments(evaluated.positional.length, evaluated.named, - callable.declaration.parameters, nodeWithSpan); + _verifyArguments( + evaluated.positional.length, + evaluated.named, + callable.declaration.parameters, + nodeWithSpan, + ); var parameters = callable.declaration.parameters.parameters; - var minLength = - math.min(evaluated.positional.length, parameters.length); + var minLength = math.min( + evaluated.positional.length, + parameters.length, + ); for (var i = 0; i < minLength; i++) { - _environment.setLocalVariable(parameters[i].name, - evaluated.positional[i], evaluated.positionalNodes[i]); + _environment.setLocalVariable( + parameters[i].name, + evaluated.positional[i], + evaluated.positionalNodes[i], + ); } for (var i = evaluated.positional.length; @@ -2947,13 +3414,16 @@ final class _EvaluateVisitor i++) { var parameter = parameters[i]; var value = evaluated.named.remove(parameter.name) ?? - _withoutSlash(parameter.defaultValue!.accept(this), - _expressionNode(parameter.defaultValue!)); + _withoutSlash( + parameter.defaultValue!.accept(this), + _expressionNode(parameter.defaultValue!), + ); _environment.setLocalVariable( - parameter.name, - value, - evaluated.namedNodes[parameter.name] ?? - _expressionNode(parameter.defaultValue!)); + parameter.name, + value, + evaluated.namedNodes[parameter.name] ?? + _expressionNode(parameter.defaultValue!), + ); } SassArgumentList? argumentList; @@ -2963,13 +3433,17 @@ final class _EvaluateVisitor ? evaluated.positional.sublist(parameters.length) : const []; argumentList = SassArgumentList( - rest, - evaluated.named, - evaluated.separator == ListSeparator.undecided - ? ListSeparator.comma - : evaluated.separator); + rest, + evaluated.named, + evaluated.separator == ListSeparator.undecided + ? ListSeparator.comma + : evaluated.separator, + ); _environment.setLocalVariable( - restParameter, argumentList, nodeWithSpan); + restParameter, + argumentList, + nodeWithSpan, + ); } var result = run(); @@ -2978,16 +3452,21 @@ final class _EvaluateVisitor if (evaluated.named.isEmpty) return result; if (argumentList.wereKeywordsAccessed) return result; - var parameterWord = - pluralize('parameter', evaluated.named.keys.length); - var parameterNames = - toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or'); + var parameterWord = pluralize( + 'parameter', + evaluated.named.keys.length, + ); + var parameterNames = toSentence( + evaluated.named.keys.map((name) => "\$$name"), + 'or', + ); throw MultiSpanSassRuntimeException( - "No $parameterWord named $parameterNames.", - nodeWithSpan.span, - "invocation", - {callable.declaration.parameters.spanWithName: "declaration"}, - _stackTrace(nodeWithSpan.span)); + "No $parameterWord named $parameterNames.", + nodeWithSpan.span, + "invocation", + {callable.declaration.parameters.spanWithName: "declaration"}, + _stackTrace(nodeWithSpan.span), + ); }); }); }); @@ -2998,24 +3477,38 @@ final class _EvaluateVisitor /// Evaluates [arguments] as applied to [callable]. Value _runFunctionCallable( - ArgumentList arguments, Callable? callable, AstNode nodeWithSpan) { + ArgumentList arguments, + Callable? callable, + AstNode nodeWithSpan, + ) { if (callable is BuiltInCallable) { return _withoutSlash( - _runBuiltInCallable(arguments, callable, nodeWithSpan), nodeWithSpan); + _runBuiltInCallable(arguments, callable, nodeWithSpan), + nodeWithSpan, + ); } else if (callable is UserDefinedCallable) { - return _runUserDefinedCallable(arguments, callable, nodeWithSpan, () { - for (var statement in callable.declaration.children) { - var returnValue = statement.accept(this); - if (returnValue is Value) return returnValue; - } + return _runUserDefinedCallable( + arguments, + callable, + nodeWithSpan, + () { + for (var statement in callable.declaration.children) { + var returnValue = statement.accept(this); + if (returnValue is Value) return returnValue; + } - throw _exception( - "Function finished without @return.", callable.declaration.span); - }); + throw _exception( + "Function finished without @return.", + callable.declaration.span, + ); + }, + ); } else if (callable is PlainCssCallable) { if (arguments.named.isNotEmpty || arguments.keywordRest != null) { - throw _exception("Plain CSS functions don't support keyword arguments.", - nodeWithSpan.span); + throw _exception( + "Plain CSS functions don't support keyword arguments.", + nodeWithSpan.span, + ); } var buffer = StringBuffer("${callable.name}("); @@ -3040,11 +3533,12 @@ final class _EvaluateVisitor } on SassRuntimeException catch (error) { if (!error.message.endsWith("isn't a valid CSS value.")) rethrow; throw MultiSpanSassRuntimeException( - error.message, - error.span, - "value", - {nodeWithSpan.span: "unknown function treated as plain CSS"}, - error.trace); + error.message, + error.span, + "value", + {nodeWithSpan.span: "unknown function treated as plain CSS"}, + error.trace, + ); } buffer.writeCharCode($rparen); @@ -3057,24 +3551,35 @@ final class _EvaluateVisitor /// Evaluates [invocation] as applied to [callable], and invokes [callable]'s /// body. Value _runBuiltInCallable( - ArgumentList arguments, BuiltInCallable callable, AstNode nodeWithSpan) { + ArgumentList arguments, + BuiltInCallable callable, + AstNode nodeWithSpan, + ) { var evaluated = _evaluateArguments(arguments); var oldCallableNode = _callableNode; _callableNode = nodeWithSpan; var namedSet = MapKeySet(evaluated.named); - var (overload, callback) = - callable.callbackFor(evaluated.positional.length, namedSet); - _addExceptionSpan(nodeWithSpan, - () => overload.verify(evaluated.positional.length, namedSet)); + var (overload, callback) = callable.callbackFor( + evaluated.positional.length, + namedSet, + ); + _addExceptionSpan( + nodeWithSpan, + () => overload.verify(evaluated.positional.length, namedSet), + ); var parameters = overload.parameters; for (var i = evaluated.positional.length; i < parameters.length; i++) { var parameter = parameters[i]; - evaluated.positional.add(evaluated.named.remove(parameter.name) ?? - _withoutSlash( - parameter.defaultValue!.accept(this), parameter.defaultValue!)); + evaluated.positional.add( + evaluated.named.remove(parameter.name) ?? + _withoutSlash( + parameter.defaultValue!.accept(this), + parameter.defaultValue!, + ), + ); } SassArgumentList? argumentList; @@ -3082,28 +3587,36 @@ final class _EvaluateVisitor var rest = const []; if (evaluated.positional.length > parameters.length) { rest = evaluated.positional.sublist(parameters.length); - evaluated.positional - .removeRange(parameters.length, evaluated.positional.length); + evaluated.positional.removeRange( + parameters.length, + evaluated.positional.length, + ); } argumentList = SassArgumentList( - rest, - evaluated.named, - evaluated.separator == ListSeparator.undecided - ? ListSeparator.comma - : evaluated.separator); + rest, + evaluated.named, + evaluated.separator == ListSeparator.undecided + ? ListSeparator.comma + : evaluated.separator, + ); evaluated.positional.add(argumentList); } Value result; try { - result = - _addExceptionSpan(nodeWithSpan, () => callback(evaluated.positional)); + result = _addExceptionSpan( + nodeWithSpan, + () => callback(evaluated.positional), + ); } on SassException { rethrow; } catch (error, stackTrace) { - throwWithTrace(_exception(_getErrorMessage(error), nodeWithSpan.span), - error, stackTrace); + throwWithTrace( + _exception(_getErrorMessage(error), nodeWithSpan.span), + error, + stackTrace, + ); } _callableNode = oldCallableNode; @@ -3112,12 +3625,13 @@ final class _EvaluateVisitor if (argumentList.wereKeywordsAccessed) return result; throw MultiSpanSassRuntimeException( - "No ${pluralize('parameter', evaluated.named.keys.length)} named " - "${toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or')}.", - nodeWithSpan.span, - "invocation", - {overload.spanWithName: "declaration"}, - _stackTrace(nodeWithSpan.span)); + "No ${pluralize('parameter', evaluated.named.keys.length)} named " + "${toSentence(evaluated.named.keys.map((name) => "\$$name"), 'or')}.", + nodeWithSpan.span, + "invocation", + {overload.spanWithName: "declaration"}, + _stackTrace(nodeWithSpan.span), + ); } /// Returns the evaluated values of the given [arguments]. @@ -3151,7 +3665,7 @@ final class _EvaluateVisitor positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: ListSeparator.undecided + separator: ListSeparator.undecided, ); } @@ -3162,11 +3676,12 @@ final class _EvaluateVisitor _addRestMap(named, rest, restArgs, (value) => value); namedNodes.addAll({ for (var key in rest.contents.keys) - (key as SassString).text: restNodeForSpan + (key as SassString).text: restNodeForSpan, }); } else if (rest is SassList) { positional.addAll( - rest.asList.map((value) => _withoutSlash(value, restNodeForSpan))); + rest.asList.map((value) => _withoutSlash(value, restNodeForSpan)), + ); positionalNodes.addAll(List.filled(rest.lengthAsList, restNodeForSpan)); separator = rest.separator; @@ -3188,7 +3703,7 @@ final class _EvaluateVisitor positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: separator + separator: separator, ); } @@ -3198,19 +3713,20 @@ final class _EvaluateVisitor _addRestMap(named, keywordRest, keywordRestArgs, (value) => value); namedNodes.addAll({ for (var key in keywordRest.contents.keys) - (key as SassString).text: keywordRestNodeForSpan + (key as SassString).text: keywordRestNodeForSpan, }); return ( positional: positional, positionalNodes: positionalNodes, named: named, namedNodes: namedNodes, - separator: separator + separator: separator, ); } else { throw _exception( - "Variable keyword arguments must be a map (was $keywordRest).", - keywordRestArgs.span); + "Variable keyword arguments must be a map (was $keywordRest).", + keywordRestArgs.span, + ); } } @@ -3232,20 +3748,33 @@ final class _EvaluateVisitor var rest = restArgs.accept(this); var restNodeForSpan = _expressionNode(restArgs); if (rest is SassMap) { - _addRestMap(named, rest, invocation, - (value) => ValueExpression(value, restArgs.span)); + _addRestMap( + named, + rest, + invocation, + (value) => ValueExpression(value, restArgs.span), + ); } else if (rest is SassList) { - positional.addAll(rest.asList.map((value) => ValueExpression( - _withoutSlash(value, restNodeForSpan), restArgs.span))); + positional.addAll( + rest.asList.map( + (value) => ValueExpression( + _withoutSlash(value, restNodeForSpan), + restArgs.span, + ), + ), + ); if (rest is SassArgumentList) { rest.keywords.forEach((key, value) { named[key] = ValueExpression( - _withoutSlash(value, restNodeForSpan), restArgs.span); + _withoutSlash(value, restNodeForSpan), + restArgs.span, + ); }); } } else { positional.add( - ValueExpression(_withoutSlash(rest, restNodeForSpan), restArgs.span)); + ValueExpression(_withoutSlash(rest, restNodeForSpan), restArgs.span), + ); } var keywordRestArgs_ = invocation.arguments.keywordRest; @@ -3256,17 +3785,20 @@ final class _EvaluateVisitor var keywordRestNodeForSpan = _expressionNode(keywordRestArgs); if (keywordRest is SassMap) { _addRestMap( - named, - keywordRest, - invocation, - (value) => ValueExpression( - _withoutSlash(value, keywordRestNodeForSpan), - keywordRestArgs.span)); + named, + keywordRest, + invocation, + (value) => ValueExpression( + _withoutSlash(value, keywordRestNodeForSpan), + keywordRestArgs.span, + ), + ); return (positional, named); } else { throw _exception( - "Variable keyword arguments must be a map (was $keywordRest).", - keywordRestArgs.span); + "Variable keyword arguments must be a map (was $keywordRest).", + keywordRestArgs.span, + ); } } @@ -3281,27 +3813,38 @@ final class _EvaluateVisitor /// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling /// [AstNode.span] if the span isn't required, since some nodes need to do /// real work to manufacture a source span. - void _addRestMap(Map values, SassMap map, AstNode nodeWithSpan, - T convert(Value value)) { + void _addRestMap( + Map values, + SassMap map, + AstNode nodeWithSpan, + T convert(Value value), + ) { var expressionNode = _expressionNode(nodeWithSpan); map.contents.forEach((key, value) { if (key is SassString) { values[key.text] = convert(_withoutSlash(value, expressionNode)); } else { throw _exception( - "Variable keyword argument map must have string keys.\n" - "$key is not a string in $map.", - nodeWithSpan.span); + "Variable keyword argument map must have string keys.\n" + "$key is not a string in $map.", + nodeWithSpan.span, + ); } }); } /// Throws a [SassRuntimeException] if [positional] and [named] aren't valid /// when applied to [parameters]. - void _verifyArguments(int positional, Map named, - ParameterList parameters, AstNode nodeWithSpan) => + void _verifyArguments( + int positional, + Map named, + ParameterList parameters, + AstNode nodeWithSpan, + ) => _addExceptionSpan( - nodeWithSpan, () => parameters.verify(positional, MapKeySet(named))); + nodeWithSpan, + () => parameters.verify(positional, MapKeySet(named)), + ); Value visitSelectorExpression(SelectorExpression node) => _styleRuleIgnoringAtRoot?.originalSelector.asSassList ?? sassNull; @@ -3312,24 +3855,30 @@ final class _EvaluateVisitor var oldInSupportsDeclaration = _inSupportsDeclaration; _inSupportsDeclaration = false; var result = SassString( - [ - for (var value in node.text.contents) - switch (value) { - String() => value, - Expression() => switch (value.accept(this)) { - SassString(:var text) => text, - var result => _serialize(result, value, quote: false) - }, - _ => throw UnsupportedError("Unknown interpolation value $value") - } - ].join(), - quotes: node.hasQuotes); + [ + for (var value in node.text.contents) + switch (value) { + String() => value, + Expression() => switch (value.accept(this)) { + SassString(:var text) => text, + var result => _serialize(result, value, quote: false), + }, + _ => throw UnsupportedError("Unknown interpolation value $value"), + }, + ].join(), + quotes: node.hasQuotes, + ); _inSupportsDeclaration = oldInSupportsDeclaration; return result; } - SassString visitSupportsExpression(SupportsExpression expression) => - SassString(_visitSupportsCondition(expression.condition), quotes: false); + SassString visitSupportsExpression( + SupportsExpression expression, + ) => + SassString( + _visitSupportsCondition(expression.condition), + quotes: false, + ); // ## Plain CSS @@ -3348,12 +3897,20 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "At-rules may not be used within nested declarations.", node.span); + "At-rules may not be used within nested declarations.", + node.span, + ); } if (node.isChildless) { - _parent.addChild(ModifiableCssAtRule(node.name, node.span, - childless: true, value: node.value)); + _parent.addChild( + ModifiableCssAtRule( + node.name, + node.span, + childless: true, + value: node.value, + ), + ); return; } @@ -3365,15 +3922,19 @@ final class _EvaluateVisitor _inUnknownAtRule = true; } - _withParent(ModifiableCssAtRule(node.name, node.span, value: node.value), - () { - // We don't have to check for an unknown at-rule in a style rule here, - // because the previous compilation has already bubbled the at-rule to the - // root. - for (var child in node.children) { - child.accept(this); - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + _withParent( + ModifiableCssAtRule(node.name, node.span, value: node.value), + () { + // We don't have to check for an unknown at-rule in a style rule here, + // because the previous compilation has already bubbled the at-rule to the + // root. + for (var child in node.children) { + child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); _inUnknownAtRule = wasInUnknownAtRule; _inKeyframes = wasInKeyframes; @@ -3392,17 +3953,26 @@ final class _EvaluateVisitor } void visitCssDeclaration(CssDeclaration node) { - _parent.addChild(ModifiableCssDeclaration(node.name, node.value, node.span, + _parent.addChild( + ModifiableCssDeclaration( + node.name, + node.value, + node.span, parsedAsCustomProperty: node.parsedAsCustomProperty, - valueSpanForMap: node.valueSpanForMap)); + valueSpanForMap: node.valueSpanForMap, + ), + ); } void visitCssImport(CssImport node) { // NOTE: this logic is largely duplicated in [_visitStaticImport]. Most // changes here should be mirrored there. - var modifiableNode = - ModifiableCssImport(node.url, node.span, modifiers: node.modifiers); + var modifiableNode = ModifiableCssImport( + node.url, + node.span, + modifiers: node.modifiers, + ); if (_parent != _root) { _parent.addChild(modifiableNode); } else if (_endOfImports == _root.children.length) { @@ -3418,11 +3988,16 @@ final class _EvaluateVisitor // here should be mirrored there. var rule = ModifiableCssKeyframeBlock(node.selector, node.span); - _withParent(rule, () { - for (var child in node.children) { - child.accept(this); - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + _withParent( + rule, + () { + for (var child in node.children) { + child.accept(this); + } + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); } void visitCssMediaRule(CssMediaRule node) { @@ -3431,11 +4006,14 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Media rules may not be used within nested declarations.", node.span); + "Media rules may not be used within nested declarations.", + node.span, + ); } var mergedQueries = _mediaQueries.andThen( - (mediaQueries) => _mergeMediaQueries(mediaQueries, node.queries)); + (mediaQueries) => _mergeMediaQueries(mediaQueries, node.queries), + ); if (mergedQueries != null && mergedQueries.isEmpty) return; var mergedSources = mergedQueries == null @@ -3443,32 +4021,38 @@ final class _EvaluateVisitor : {..._mediaQuerySources!, ..._mediaQueries!, ...node.queries}; _withParent( - ModifiableCssMediaRule(mergedQueries ?? node.queries, node.span), () { - _withMediaQueries(mergedQueries ?? node.queries, mergedSources, () { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the media query so that - // declarations immediately inside @media have somewhere to go. - // - // For example, "a {@media screen {b: c}}" should produce - // "@media screen {a {b: c}}". - _withParent(styleRule.copyWithoutChildren(), () { - for (var child in node.children) { - child.accept(this); + ModifiableCssMediaRule(mergedQueries ?? node.queries, node.span), + () { + _withMediaQueries( + mergedQueries ?? node.queries, + mergedSources, + () { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the media query so that + // declarations immediately inside @media have somewhere to go. + // + // For example, "a {@media screen {b: c}}" should produce + // "@media screen {a {b: c}}". + _withParent(styleRule.copyWithoutChildren(), () { + for (var child in node.children) { + child.accept(this); + } + }, scopeWhen: false); + } else { + for (var child in node.children) { + child.accept(this); + } } - }, scopeWhen: false); - } else { - for (var child in node.children) { - child.accept(this); - } - } - }); - }, - through: (node) => - node is CssStyleRule || - (mergedSources.isNotEmpty && - node is CssMediaRule && - node.queries.every(mergedSources.contains)), - scopeWhen: false); + }, + ); + }, + through: (node) => + node is CssStyleRule || + (mergedSources.isNotEmpty && + node is CssMediaRule && + node.queries.every(mergedSources.contains)), + scopeWhen: false, + ); } void visitCssStyleRule(CssStyleRule node) { @@ -3477,31 +4061,46 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Style rules may not be used within nested declarations.", node.span); + "Style rules may not be used within nested declarations.", + node.span, + ); } else if (_inKeyframes && _parent is CssKeyframeBlock) { throw _exception( - "Style rules may not be used within keyframe blocks.", node.span); + "Style rules may not be used within keyframe blocks.", + node.span, + ); } var styleRule = _styleRule; var nest = !(_styleRule?.fromPlainCss ?? false); var originalSelector = nest - ? node.selector.nestWithin(styleRule?.originalSelector, + ? node.selector.nestWithin( + styleRule?.originalSelector, implicitParent: !_atRootExcludingStyleRule, - preserveParentSelectors: node.fromPlainCss) + preserveParentSelectors: node.fromPlainCss, + ) : node.selector; var selector = _extensionStore.addSelector(originalSelector, _mediaQueries); - var rule = ModifiableCssStyleRule(selector, node.span, - originalSelector: originalSelector, fromPlainCss: node.fromPlainCss); + var rule = ModifiableCssStyleRule( + selector, + node.span, + originalSelector: originalSelector, + fromPlainCss: node.fromPlainCss, + ); var oldAtRootExcludingStyleRule = _atRootExcludingStyleRule; _atRootExcludingStyleRule = false; - _withParent(rule, () { - _withStyleRule(rule, () { - for (var child in node.children) { - child.accept(this); - } - }); - }, through: nest ? (node) => node is CssStyleRule : null, scopeWhen: false); + _withParent( + rule, + () { + _withStyleRule(rule, () { + for (var child in node.children) { + child.accept(this); + } + }); + }, + through: nest ? (node) => node is CssStyleRule : null, + scopeWhen: false, + ); _atRootExcludingStyleRule = oldAtRootExcludingStyleRule; if (_parent.children case [..., var lastChild] when styleRule == null) { @@ -3521,28 +4120,34 @@ final class _EvaluateVisitor if (_declarationName != null) { throw _exception( - "Supports rules may not be used within nested declarations.", - node.span); - } - - _withParent(ModifiableCssSupportsRule(node.condition, node.span), () { - if (_styleRule case var styleRule?) { - // If we're in a style rule, copy it into the supports rule so that - // declarations immediately inside @supports have somewhere to go. - // - // For example, "a {@supports (a: b) {b: c}}" should produce "@supports - // (a: b) {a {b: c}}". - _withParent(styleRule.copyWithoutChildren(), () { + "Supports rules may not be used within nested declarations.", + node.span, + ); + } + + _withParent( + ModifiableCssSupportsRule(node.condition, node.span), + () { + if (_styleRule case var styleRule?) { + // If we're in a style rule, copy it into the supports rule so that + // declarations immediately inside @supports have somewhere to go. + // + // For example, "a {@supports (a: b) {b: c}}" should produce "@supports + // (a: b) {a {b: c}}". + _withParent(styleRule.copyWithoutChildren(), () { + for (var child in node.children) { + child.accept(this); + } + }); + } else { for (var child in node.children) { child.accept(this); } - }); - } else { - for (var child in node.children) { - child.accept(this); } - } - }, through: (node) => node is CssStyleRule, scopeWhen: false); + }, + through: (node) => node is CssStyleRule, + scopeWhen: false, + ); } // ## Utilities @@ -3551,7 +4156,10 @@ final class _EvaluateVisitor /// /// Returns the value returned by [callback], or `null` if it only ever /// returned `null`. - Value? _handleReturn(List list, Value? callback(T value)) { + Value? _handleReturn( + List list, + Value? callback(T value), + ) { for (var value in list) { if (callback(value) case var result?) return result; } @@ -3559,7 +4167,10 @@ final class _EvaluateVisitor } /// Runs [callback] with [environment] as the current environment. - T _withEnvironment(Environment environment, T callback()) { + T _withEnvironment( + Environment environment, + T callback(), + ) { var oldEnvironment = _environment; _environment = environment; var result = callback(); @@ -3572,22 +4183,34 @@ final class _EvaluateVisitor /// If [trim] is `true`, removes whitespace around the result. If /// [warnForColor] is `true`, this will emit a warning for any named color /// values passed into the interpolation. - CssValue _interpolationToValue(Interpolation interpolation, - {bool trim = false, bool warnForColor = false}) { - var result = - _performInterpolation(interpolation, warnForColor: warnForColor); - return CssValue(trim ? trimAscii(result, excludeEscape: true) : result, - interpolation.span); + CssValue _interpolationToValue( + Interpolation interpolation, { + bool trim = false, + bool warnForColor = false, + }) { + var result = _performInterpolation( + interpolation, + warnForColor: warnForColor, + ); + return CssValue( + trim ? trimAscii(result, excludeEscape: true) : result, + interpolation.span, + ); } /// Evaluates [interpolation]. /// /// If [warnForColor] is `true`, this will emit a warning for any named color /// values passed into the interpolation. - String _performInterpolation(Interpolation interpolation, - {bool warnForColor = false}) { - var (result, _) = _performInterpolationHelper(interpolation, - sourceMap: false, warnForColor: warnForColor); + String _performInterpolation( + Interpolation interpolation, { + bool warnForColor = false, + }) { + var (result, _) = _performInterpolationHelper( + interpolation, + sourceMap: false, + warnForColor: warnForColor, + ); return result; } @@ -3595,19 +4218,24 @@ final class _EvaluateVisitor /// can map spans from the resulting string back to the original /// [interpolation]. (String, InterpolationMap) _performInterpolationWithMap( - Interpolation interpolation, - {bool warnForColor = false}) { - var (result, map) = _performInterpolationHelper(interpolation, - sourceMap: true, warnForColor: warnForColor); + Interpolation interpolation, { + bool warnForColor = false, + }) { + var (result, map) = _performInterpolationHelper( + interpolation, + sourceMap: true, + warnForColor: warnForColor, + ); return (result, map!); } /// A helper that implements the core logic of both [_performInterpolation] /// and [_performInterpolationWithMap]. (String, InterpolationMap?) _performInterpolationHelper( - Interpolation interpolation, - {required bool sourceMap, - bool warnForColor = false}) { + Interpolation interpolation, { + required bool sourceMap, + bool warnForColor = false, + }) { var targetLocations = sourceMap ? [] : null; var oldInSupportsDeclaration = _inSupportsDeclaration; _inSupportsDeclaration = false; @@ -3627,19 +4255,23 @@ final class _EvaluateVisitor if (warnForColor && namesByColor.containsKey(result)) { var alternative = BinaryOperationExpression( - BinaryOperator.plus, - StringExpression(Interpolation.plain("", interpolation.span), - quotes: true), - expression); + BinaryOperator.plus, + StringExpression( + Interpolation.plain("", interpolation.span), + quotes: true, + ), + expression, + ); _warn( - "You probably don't mean to use the color value " - "${namesByColor[result]} in interpolation here.\n" - "It may end up represented as $result, which will likely produce " - "invalid CSS.\n" - "Always quote color names when using them as strings or map keys " - '(for example, "${namesByColor[result]}").\n' - "If you really want to use the color value here, use '$alternative'.", - expression.span); + "You probably don't mean to use the color value " + "${namesByColor[result]} in interpolation here.\n" + "It may end up represented as $result, which will likely produce " + "invalid CSS.\n" + "Always quote color names when using them as strings or map keys " + '(for example, "${namesByColor[result]}").\n' + "If you really want to use the color value here, use '$alternative'.", + expression.span, + ); } buffer.write(_serialize(result, expression, quote: false)); @@ -3649,13 +4281,17 @@ final class _EvaluateVisitor return ( buffer.toString(), targetLocations.andThen( - (targetLocations) => InterpolationMap(interpolation, targetLocations)) + (targetLocations) => InterpolationMap(interpolation, targetLocations), + ), ); } /// Evaluates [expression] and calls `toCssString()` and wraps a /// [SassScriptException] to associate it with [span]. - String _evaluateToCss(Expression expression, {bool quote = true}) => + String _evaluateToCss( + Expression expression, { + bool quote = true, + }) => _serialize(expression.accept(this), expression, quote: quote); /// Calls `value.toCssString()` and wraps a [SassScriptException] to associate @@ -3684,9 +4320,12 @@ final class _EvaluateVisitor if (expression is VariableExpression) { return _addExceptionSpan( - expression, - () => _environment.getVariableNode(expression.name, - namespace: expression.namespace)) ?? + expression, + () => _environment.getVariableNode( + expression.name, + namespace: expression.namespace, + ), + ) ?? expression; } else { return expression; @@ -3701,8 +4340,12 @@ final class _EvaluateVisitor /// lattermost child of its parent. /// /// Runs [callback] in a new environment scope unless [scopeWhen] is false. - T _withParent(S node, T callback(), - {bool through(CssNode node)?, bool scopeWhen = true}) { + T _withParent( + S node, + T callback(), { + bool through(CssNode node)?, + bool scopeWhen = true, + }) { _addChild(node, through: through); var oldParent = _parent; @@ -3727,7 +4370,8 @@ final class _EvaluateVisitor parent = grandparent; } else { throw ArgumentError( - "through() must return false for at least one parent of $node."); + "through() must return false for at least one parent of $node.", + ); } } @@ -3752,7 +4396,10 @@ final class _EvaluateVisitor } /// Runs [callback] with [rule] as the current style rule. - T _withStyleRule(ModifiableCssStyleRule rule, T callback()) { + T _withStyleRule( + ModifiableCssStyleRule rule, + T callback(), + ) { var oldRule = _styleRuleIgnoringAtRoot; _styleRuleIgnoringAtRoot = rule; var result = callback(); @@ -3766,7 +4413,10 @@ final class _EvaluateVisitor /// merged together to create [queries]. This is used to determine when it's /// safe to bubble one query through another. T _withMediaQueries( - List? queries, Set? sources, T callback()) { + List? queries, + Set? sources, + T callback(), + ) { var oldMediaQueries = _mediaQueries; var oldSources = _mediaQuerySources; _mediaQueries = queries; @@ -3785,7 +4435,11 @@ final class _EvaluateVisitor /// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling /// [AstNode.span] if the span isn't required, since some nodes need to do /// real work to manufacture a source span. - T _withStackFrame(String member, AstNode nodeWithSpan, T callback()) { + T _withStackFrame( + String member, + AstNode nodeWithSpan, + T callback(), + ) { _stack.add((_member, nodeWithSpan)); var oldMember = _member; _member = member; @@ -3802,19 +4456,20 @@ final class _EvaluateVisitor String recommendation(SassNumber number) => switch (number.asSlash) { (var before, var after) => "math.div(${recommendation(before)}, ${recommendation(after)})", - _ => number.toString() + _ => number.toString(), }; _warn( - "Using / for division is deprecated and will be removed in Dart Sass " - "2.0.0.\n" - "\n" - "Recommendation: ${recommendation(value)}\n" - "\n" - "More info and automated migrator: " - "https://sass-lang.com/d/slash-div", - nodeForSpan.span, - Deprecation.slashDiv); + "Using / for division is deprecated and will be removed in Dart Sass " + "2.0.0.\n" + "\n" + "Recommendation: ${recommendation(value)}\n" + "\n" + "More info and automated migrator: " + "https://sass-lang.com/d/slash-div", + nodeForSpan.span, + Deprecation.slashDiv, + ); } return value.withoutSlash(); @@ -3822,8 +4477,12 @@ final class _EvaluateVisitor /// Creates a new stack frame with location information from [member] and /// [span]. - Frame _stackFrame(String member, FileSpan span) => frameForSpan(span, member, - url: span.sourceUrl.andThen((url) => _importCache?.humanize(url) ?? url)); + Frame _stackFrame(String member, FileSpan span) => frameForSpan( + span, + member, + url: + span.sourceUrl.andThen((url) => _importCache?.humanize(url) ?? url), + ); /// Returns a stack trace at the current point. /// @@ -3832,7 +4491,7 @@ final class _EvaluateVisitor var frames = [ for (var (member, nodeWithSpan) in _stack) _stackFrame(member, nodeWithSpan.span), - if (span != null) _stackFrame(_member, span) + if (span != null) _stackFrame(_member, span), ]; return Trace(frames.reversed); } @@ -3846,8 +4505,12 @@ final class _EvaluateVisitor if (deprecation == null) { _logger.warn(message, span: span, trace: trace); } else { - _logger.warnForDeprecation(deprecation, message, - span: span, trace: trace); + _logger.warnForDeprecation( + deprecation, + message, + span: span, + trace: trace, + ); } } @@ -3856,16 +4519,27 @@ final class _EvaluateVisitor /// If [span] is passed, it's used for the innermost stack frame. SassRuntimeException _exception(String message, [FileSpan? span]) => SassRuntimeException( - message, span ?? _stack.last.$2.span, _stackTrace(span)); + message, + span ?? _stack.last.$2.span, + _stackTrace(span), + ); /// Returns a [MultiSpanSassRuntimeException] with the given [message], /// [primaryLabel], and [secondaryLabels]. /// /// The primary span is taken from the current stack trace span. - SassRuntimeException _multiSpanException(String message, String primaryLabel, - Map secondaryLabels) => - MultiSpanSassRuntimeException(message, _stack.last.$2.span, primaryLabel, - secondaryLabels, _stackTrace()); + SassRuntimeException _multiSpanException( + String message, + String primaryLabel, + Map secondaryLabels, + ) => + MultiSpanSassRuntimeException( + message, + _stack.last.$2.span, + primaryLabel, + secondaryLabels, + _stackTrace(), + ); /// Runs [callback], and converts any [SassScriptException]s it throws to /// [SassRuntimeException]s with [nodeWithSpan]'s source span. @@ -3876,17 +4550,21 @@ final class _EvaluateVisitor /// /// If [addStackFrame] is true (the default), this will add an innermost stack /// frame for [nodeWithSpan]. Otherwise, it will use the existing stack as-is. - T _addExceptionSpan(AstNode nodeWithSpan, T callback(), - {bool addStackFrame = true}) { + T _addExceptionSpan( + AstNode nodeWithSpan, + T callback(), { + bool addStackFrame = true, + }) { try { return callback(); } on SassScriptException catch (error, stackTrace) { throwWithTrace( - error - .withSpan(nodeWithSpan.span) - .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), - error, - stackTrace); + error + .withSpan(nodeWithSpan.span) + .withTrace(_stackTrace(addStackFrame ? nodeWithSpan.span : null)), + error, + stackTrace, + ); } } @@ -3900,7 +4578,10 @@ final class _EvaluateVisitor rethrow; } on SassException catch (error, stackTrace) { throwWithTrace( - error.withTrace(_stackTrace(error.span)), error, stackTrace); + error.withTrace(_stackTrace(error.span)), + error, + stackTrace, + ); } } @@ -3913,9 +4594,10 @@ final class _EvaluateVisitor } on SassRuntimeException catch (error, stackTrace) { if (!error.span.text.startsWith("@error")) rethrow; throwWithTrace( - SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), - error, - stackTrace); + SassRuntimeException(error.message, nodeWithSpan.span, _stackTrace()), + error, + stackTrace, + ); } } @@ -3950,8 +4632,10 @@ final class _ImportedCssVisitor implements ModifiableCssVisitor { _ImportedCssVisitor(this._visitor); void visitCssAtRule(ModifiableCssAtRule node) { - _visitor._addChild(node, - through: node.isChildless ? null : (node) => node is CssStyleRule); + _visitor._addChild( + node, + through: node.isChildless ? null : (node) => node is CssStyleRule, + ); } void visitCssComment(ModifiableCssComment node) => _visitor._addChild(node); @@ -3983,9 +4667,11 @@ final class _ImportedCssVisitor implements ModifiableCssVisitor { var hasBeenMerged = mediaQueries == null || _visitor._mergeMediaQueries(mediaQueries, node.queries) != null; - _visitor._addChild(node, - through: (node) => - node is CssStyleRule || (hasBeenMerged && node is CssMediaRule)); + _visitor._addChild( + node, + through: (node) => + node is CssStyleRule || (hasBeenMerged && node is CssMediaRule), + ); } void visitCssStyleRule(ModifiableCssStyleRule node) => @@ -4020,11 +4706,12 @@ final class _EvaluationContext implements EvaluationContext { void warn(String message, [Deprecation? deprecation]) { _visitor._warn( - message, - _visitor._importSpan ?? - _visitor._callableNode?.span ?? - _defaultWarnNodeWithSpan.span, - deprecation); + message, + _visitor._importSpan ?? + _visitor._callableNode?.span ?? + _defaultWarnNodeWithSpan.span, + deprecation, + ); } } @@ -4051,7 +4738,7 @@ typedef _ArgumentResults = ({ Map namedNodes, /// The separator used for the rest argument list, if any. - ListSeparator separator + ListSeparator separator, }); /// The result of loading a stylesheet via [Evaluator._loadStylesheet]. @@ -4067,5 +4754,5 @@ typedef _LoadedStylesheet = ( /// /// That is, whether this was (transitively) loaded through a load path or /// importer rather than relative to the entrypoint. - bool isDependency + bool isDependency, }); diff --git a/lib/src/visitor/expression_to_calc.dart b/lib/src/visitor/expression_to_calc.dart index 497aafd6c..3c102bc5c 100644 --- a/lib/src/visitor/expression_to_calc.dart +++ b/lib/src/visitor/expression_to_calc.dart @@ -12,12 +12,14 @@ import 'replace_expression.dart'; /// expressions. FunctionExpression expressionToCalc(Expression expression) => FunctionExpression( - "calc", - ArgumentList( - [expression.accept(const _MakeExpressionCalculationSafe())], - const {}, - expression.span), - expression.span); + "calc", + ArgumentList( + [expression.accept(const _MakeExpressionCalculationSafe())], + const {}, + expression.span, + ), + expression.span, + ); /// A visitor that replaces constructs that can't be used in a calculation with /// those that can. @@ -31,23 +33,30 @@ class _MakeExpressionCalculationSafe with ReplaceExpressionVisitor { // `mod()` calculation function because there's no browser support, so we have // to work around it by wrapping the call in a Sass function. ? FunctionExpression( - 'max', ArgumentList([node], const {}, node.span), node.span, - namespace: 'math') + 'max', + ArgumentList([node], const {}, node.span), + node.span, + namespace: 'math', + ) : super.visitBinaryOperationExpression(node); Expression visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) => + InterpolatedFunctionExpression node, + ) => node; Expression visitUnaryOperationExpression(UnaryOperationExpression node) => switch (node.operator) { // `calc()` doesn't support unary operations. UnaryOperator.plus => node.operand, - UnaryOperator.minus => BinaryOperationExpression(BinaryOperator.times, - NumberExpression(-1, node.span), node.operand), + UnaryOperator.minus => BinaryOperationExpression( + BinaryOperator.times, + NumberExpression(-1, node.span), + node.operand, + ), _ => // Other unary operations don't produce numbers, so keep them as-is to // give the user a more useful syntax error after serialization. - super.visitUnaryOperationExpression(node) + super.visitUnaryOperationExpression(node), }; } diff --git a/lib/src/visitor/find_dependencies.dart b/lib/src/visitor/find_dependencies.dart index 94607e952..8aece29e1 100644 --- a/lib/src/visitor/find_dependencies.dart +++ b/lib/src/visitor/find_dependencies.dart @@ -30,10 +30,11 @@ class _FindDependenciesVisitor with RecursiveStatementVisitor { DependencyReport run(Stylesheet stylesheet) { visitStylesheet(stylesheet); return DependencyReport._( - uses: UnmodifiableSetView(_uses), - forwards: UnmodifiableSetView(_forwards), - metaLoadCss: UnmodifiableSetView(_metaLoadCss), - imports: UnmodifiableSetView(_imports)); + uses: UnmodifiableSetView(_uses), + forwards: UnmodifiableSetView(_forwards), + metaLoadCss: UnmodifiableSetView(_metaLoadCss), + imports: UnmodifiableSetView(_imports), + ); } // These can never contain imports. @@ -68,7 +69,9 @@ class _FindDependenciesVisitor with RecursiveStatementVisitor { if (!_metaNamespaces.contains(node.namespace)) return; if (node.arguments.positional - case [StringExpression(text: Interpolation(asPlain: var url?))]) { + case [ + StringExpression(text: Interpolation(asPlain: var url?)), + ]) { try { _metaLoadCss.add(Uri.parse(url)); } on FormatException { @@ -103,9 +106,10 @@ final class DependencyReport { /// [imports]. Set get all => UnionSet({uses, forwards, metaLoadCss, imports}); - DependencyReport._( - {required this.uses, - required this.forwards, - required this.metaLoadCss, - required this.imports}); + DependencyReport._({ + required this.uses, + required this.forwards, + required this.metaLoadCss, + required this.imports, + }); } diff --git a/lib/src/visitor/is_calculation_safe.dart b/lib/src/visitor/is_calculation_safe.dart index 9d2e406fc..275dbcb59 100644 --- a/lib/src/visitor/is_calculation_safe.dart +++ b/lib/src/visitor/is_calculation_safe.dart @@ -25,7 +25,7 @@ class IsCalculationSafeVisitor implements ExpressionVisitor { BinaryOperator.times, BinaryOperator.dividedBy, BinaryOperator.plus, - BinaryOperator.minus + BinaryOperator.minus, }).contains(node.operator) && (node.left.accept(this) || node.right.accept(this)); @@ -36,7 +36,8 @@ class IsCalculationSafeVisitor implements ExpressionVisitor { bool visitFunctionExpression(FunctionExpression node) => true; bool visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) => + InterpolatedFunctionExpression node, + ) => true; bool visitIfExpression(IfExpression node) => true; diff --git a/lib/src/visitor/recursive_ast.dart b/lib/src/visitor/recursive_ast.dart index f2ac25fec..b8d960887 100644 --- a/lib/src/visitor/recursive_ast.dart +++ b/lib/src/visitor/recursive_ast.dart @@ -161,7 +161,8 @@ mixin RecursiveAstVisitor on RecursiveStatementVisitor } void visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) { + InterpolatedFunctionExpression node, + ) { visitInterpolation(node.name); visitArgumentList(node.arguments); } diff --git a/lib/src/visitor/replace_expression.dart b/lib/src/visitor/replace_expression.dart index f32184b8b..ed10c3271 100644 --- a/lib/src/visitor/replace_expression.dart +++ b/lib/src/visitor/replace_expression.dart @@ -24,33 +24,45 @@ import 'interface/expression.dart'; mixin ReplaceExpressionVisitor implements ExpressionVisitor { Expression visitBinaryOperationExpression(BinaryOperationExpression node) => BinaryOperationExpression( - node.operator, node.left.accept(this), node.right.accept(this)); + node.operator, + node.left.accept(this), + node.right.accept(this), + ); Expression visitBooleanExpression(BooleanExpression node) => node; Expression visitColorExpression(ColorExpression node) => node; - Expression visitFunctionExpression( - FunctionExpression node) => + Expression visitFunctionExpression(FunctionExpression node) => FunctionExpression( - node.originalName, visitArgumentList(node.arguments), node.span, - namespace: node.namespace); + node.originalName, + visitArgumentList(node.arguments), + node.span, + namespace: node.namespace, + ); Expression visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) => - InterpolatedFunctionExpression(visitInterpolation(node.name), - visitArgumentList(node.arguments), node.span); + InterpolatedFunctionExpression node, + ) => + InterpolatedFunctionExpression( + visitInterpolation(node.name), + visitArgumentList(node.arguments), + node.span, + ); Expression visitIfExpression(IfExpression node) => IfExpression(visitArgumentList(node.arguments), node.span); Expression visitListExpression(ListExpression node) => ListExpression( - node.contents.map((item) => item.accept(this)), node.separator, node.span, - brackets: node.hasBrackets); + node.contents.map((item) => item.accept(this)), + node.separator, + node.span, + brackets: node.hasBrackets, + ); Expression visitMapExpression(MapExpression node) => MapExpression([ for (var (key, value) in node.pairs) - (key.accept(this), value.accept(this)) + (key.accept(this), value.accept(this)), ], node.span); Expression visitNullExpression(NullExpression node) => node; @@ -70,7 +82,10 @@ mixin ReplaceExpressionVisitor implements ExpressionVisitor { Expression visitUnaryOperationExpression(UnaryOperationExpression node) => UnaryOperationExpression( - node.operator, node.operand.accept(this), node.span); + node.operator, + node.operand.accept(this), + node.span, + ); Expression visitValueExpression(ValueExpression node) => node; @@ -82,14 +97,15 @@ mixin ReplaceExpressionVisitor implements ExpressionVisitor { /// argument invocation in an expression. @protected ArgumentList visitArgumentList(ArgumentList invocation) => ArgumentList( - invocation.positional.map((expression) => expression.accept(this)), - { - for (var (name, value) in invocation.named.pairs) - name: value.accept(this) - }, - invocation.span, - rest: invocation.rest?.accept(this), - keywordRest: invocation.keywordRest?.accept(this)); + invocation.positional.map((expression) => expression.accept(this)), + { + for (var (name, value) in invocation.named.pairs) + name: value.accept(this), + }, + invocation.span, + rest: invocation.rest?.accept(this), + keywordRest: invocation.keywordRest?.accept(this), + ); /// Replaces each expression in [condition]. /// @@ -99,22 +115,32 @@ mixin ReplaceExpressionVisitor implements ExpressionVisitor { SupportsCondition visitSupportsCondition(SupportsCondition condition) { if (condition is SupportsOperation) { return SupportsOperation( - visitSupportsCondition(condition.left), - visitSupportsCondition(condition.right), - condition.operator, - condition.span); + visitSupportsCondition(condition.left), + visitSupportsCondition(condition.right), + condition.operator, + condition.span, + ); } else if (condition is SupportsNegation) { return SupportsNegation( - visitSupportsCondition(condition.condition), condition.span); + visitSupportsCondition(condition.condition), + condition.span, + ); } else if (condition is SupportsInterpolation) { return SupportsInterpolation( - condition.expression.accept(this), condition.span); + condition.expression.accept(this), + condition.span, + ); } else if (condition is SupportsDeclaration) { - return SupportsDeclaration(condition.name.accept(this), - condition.value.accept(this), condition.span); + return SupportsDeclaration( + condition.name.accept(this), + condition.value.accept(this), + condition.span, + ); } else { throw SassException( - "BUG: Unknown SupportsCondition $condition.", condition.span); + "BUG: Unknown SupportsCondition $condition.", + condition.span, + ); } } @@ -125,8 +151,10 @@ mixin ReplaceExpressionVisitor implements ExpressionVisitor { @protected Interpolation visitInterpolation(Interpolation interpolation) => Interpolation( - interpolation.contents - .map((node) => node is Expression ? node.accept(this) : node), - interpolation.spans, - interpolation.span); + interpolation.contents.map( + (node) => node is Expression ? node.accept(this) : node, + ), + interpolation.spans, + interpolation.span, + ); } diff --git a/lib/src/visitor/selector_search.dart b/lib/src/visitor/selector_search.dart index 8029ad8c7..ac6d2766f 100644 --- a/lib/src/visitor/selector_search.dart +++ b/lib/src/visitor/selector_search.dart @@ -23,8 +23,9 @@ mixin SelectorSearchVisitor implements SelectorVisitor { T? visitTypeSelector(TypeSelector type) => null; T? visitUniversalSelector(UniversalSelector universal) => null; - T? visitComplexSelector(ComplexSelector complex) => complex.components - .search((component) => visitCompoundSelector(component.selector)); + T? visitComplexSelector(ComplexSelector complex) => complex.components.search( + (component) => visitCompoundSelector(component.selector), + ); T? visitCompoundSelector(CompoundSelector compound) => compound.components.search((simple) => simple.accept(this)); diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index a93c2fb8f..2856a24fa 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -46,24 +46,27 @@ import 'interface/value.dart'; /// /// If [charset] is `true`, this will include a `@charset` declaration or a BOM /// if the stylesheet contains any non-ASCII characters. -SerializeResult serialize(CssNode node, - {OutputStyle? style, - bool inspect = false, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - Logger? logger, - bool sourceMap = false, - bool charset = true}) { +SerializeResult serialize( + CssNode node, { + OutputStyle? style, + bool inspect = false, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + Logger? logger, + bool sourceMap = false, + bool charset = true, +}) { indentWidth ??= 2; var visitor = _SerializeVisitor( - style: style, - inspect: inspect, - useSpaces: useSpaces, - indentWidth: indentWidth, - lineFeed: lineFeed, - logger: logger, - sourceMap: sourceMap); + style: style, + inspect: inspect, + useSpaces: useSpaces, + indentWidth: indentWidth, + lineFeed: lineFeed, + logger: logger, + sourceMap: sourceMap, + ); node.accept(visitor); var css = visitor._buffer.toString(); String prefix; @@ -75,7 +78,8 @@ SerializeResult serialize(CssNode node, return ( prefix + css, - sourceMap: sourceMap ? visitor._buffer.buildSourceMap(prefix: prefix) : null + sourceMap: + sourceMap ? visitor._buffer.buildSourceMap(prefix: prefix) : null, ); } @@ -88,8 +92,11 @@ SerializeResult serialize(CssNode node, /// /// If [quote] is `false`, quoted strings are emitted without quotes. String serializeValue(Value value, {bool inspect = false, bool quote = true}) { - var visitor = - _SerializeVisitor(inspect: inspect, quote: quote, sourceMap: false); + var visitor = _SerializeVisitor( + inspect: inspect, + quote: quote, + sourceMap: false, + ); value.accept(visitor); return visitor._buffer.toString(); } @@ -143,16 +150,16 @@ final class _SerializeVisitor /// Whether we're emitting compressed output. bool get _isCompressed => _style == OutputStyle.compressed; - _SerializeVisitor( - {OutputStyle? style, - bool inspect = false, - bool quote = true, - bool useSpaces = true, - int? indentWidth, - LineFeed? lineFeed, - Logger? logger, - bool sourceMap = true}) - : _buffer = sourceMap ? SourceMapBuffer() : NoSourceMapBuffer(), + _SerializeVisitor({ + OutputStyle? style, + bool inspect = false, + bool quote = true, + bool useSpaces = true, + int? indentWidth, + LineFeed? lineFeed, + Logger? logger, + bool sourceMap = true, + }) : _buffer = sourceMap ? SourceMapBuffer() : NoSourceMapBuffer(), _style = style ?? OutputStyle.expanded, _inspect = inspect, _quote = quote, @@ -196,8 +203,10 @@ final class _SerializeVisitor if (_minimumIndentation(node.text) case var minimumIndentation?) { assert(minimumIndentation != -1); - minimumIndentation = - math.min(minimumIndentation, node.span.start.column); + minimumIndentation = math.min( + minimumIndentation, + node.span.start.column, + ); _writeIndentation(); _writeWithIndent(node.text, minimumIndentation); @@ -288,9 +297,9 @@ final class _SerializeVisitor _writeIndentation(); _for( - node.selector, - () => - _writeBetween(node.selector.value, _commaSeparator, _buffer.write)); + node.selector, + () => _writeBetween(node.selector.value, _commaSeparator, _buffer.write), + ); _writeOptionalSpace(); _visitChildren(node); } @@ -312,8 +321,11 @@ final class _SerializeVisitor _buffer.write(condition.substring("(not ".length, condition.length - 1)); } else { var operator = query.conjunction ? "and" : "or"; - _writeBetween(query.conditions, - _isCompressed ? "$operator " : " $operator ", _buffer.write); + _writeBetween( + query.conditions, + _isCompressed ? "$operator " : " $operator ", + _buffer.write, + ); } } @@ -354,19 +366,19 @@ final class _SerializeVisitor if (!declSpecificities.any(ruleSpecificities.contains)) continue; _logger.warnForDeprecation( - Deprecation.mixedDecls, - "Sass's behavior for declarations that appear after nested\n" - "rules will be changing to match the behavior specified by CSS in an " - "upcoming\n" - "version. To keep the existing behavior, move the declaration above " - "the nested\n" - "rule. To opt into the new behavior, wrap the declaration in `& " - "{}`.\n" - "\n" - "More info: https://sass-lang.com/d/mixed-decls", - span: - MultiSpan(node.span, 'declaration', {rule.span: 'nested rule'}), - trace: node.trace); + Deprecation.mixedDecls, + "Sass's behavior for declarations that appear after nested\n" + "rules will be changing to match the behavior specified by CSS in an " + "upcoming\n" + "version. To keep the existing behavior, move the declaration above " + "the nested\n" + "rule. To opt into the new behavior, wrap the declaration in `& " + "{}`.\n" + "\n" + "More info: https://sass-lang.com/d/mixed-decls", + span: MultiSpan(node.span, 'declaration', {rule.span: 'nested rule'}), + trace: node.trace, + ); } } @@ -390,16 +402,26 @@ final class _SerializeVisitor _writeOptionalSpace(); try { _buffer.forSpan( - node.valueSpanForMap, () => node.value.value.accept(this)); + node.valueSpanForMap, + () => node.value.value.accept(this), + ); } on MultiSpanSassScriptException catch (error, stackTrace) { throwWithTrace( - MultiSpanSassException(error.message, node.value.span, - error.primaryLabel, error.secondarySpans), - error, - stackTrace); + MultiSpanSassException( + error.message, + node.value.span, + error.primaryLabel, + error.secondarySpans, + ), + error, + stackTrace, + ); } on SassScriptException catch (error, stackTrace) { throwWithTrace( - SassException(error.message, node.value.span), error, stackTrace); + SassException(error.message, node.value.span), + error, + stackTrace, + ); } } } @@ -413,7 +435,7 @@ final class _SerializeVisitor var parent = node.parent.andThen(_specificities)?.max ?? 0; return { for (var selector in rule.selector.components) - parent + selector.specificity + parent + selector.specificity, }; } else { return node.parent.andThen(_specificities) ?? const {0}; @@ -449,7 +471,9 @@ final class _SerializeVisitor _buffer.writeCharCode($space); case var minimumIndentation: _writeWithIndent( - value, math.min(minimumIndentation, node.name.span.start.column)); + value, + math.min(minimumIndentation, node.name.span.start.column), + ); } } @@ -600,7 +624,9 @@ final class _SerializeVisitor /// Writes the complex numerator and denominator units beyond the first /// numerator unit for a number as they appear in a calculation. void _writeCalculationUnits( - List numeratorUnits, List denominatorUnits) { + List numeratorUnits, + List denominatorUnits, + ) { for (var unit in numeratorUnits) { _writeOptionalSpace(); _buffer.writeCharCode($asterisk); @@ -623,12 +649,14 @@ final class _SerializeVisitor /// /// In `a ? (b # c)`, `outer` is `?` and `right` is `#`. bool _parenthesizeCalculationRhs( - CalculationOperator outer, CalculationOperator right) => + CalculationOperator outer, + CalculationOperator right, + ) => switch (outer) { CalculationOperator.dividedBy => true, CalculationOperator.plus => false, _ => right == CalculationOperator.plus || - right == CalculationOperator.minus + right == CalculationOperator.minus, }; void visitColor(SassColor value) { @@ -727,7 +755,9 @@ final class _SerializeVisitor _writeChannel(value.channel1OrNull); _buffer.writeCharCode($space); _writeChannel( - value.channel2OrNull, polar && !_isCompressed ? 'deg' : null); + value.channel2OrNull, + polar && !_isCompressed ? 'deg' : null, + ); _maybeWriteSlashAlpha(value); _buffer.writeCharCode($rparen); @@ -959,15 +989,17 @@ final class _SerializeVisitor /// Writes [color] using the `color()` function syntax. void _writeColorFunction(SassColor color) { - assert(!{ - ColorSpace.rgb, - ColorSpace.hsl, - ColorSpace.hwb, - ColorSpace.lab, - ColorSpace.oklab, - ColorSpace.lch, - ColorSpace.oklch - }.contains(color.space)); + assert( + !{ + ColorSpace.rgb, + ColorSpace.hsl, + ColorSpace.hwb, + ColorSpace.lab, + ColorSpace.oklab, + ColorSpace.lch, + ColorSpace.oklch, + }.contains(color.space), + ); _buffer ..write('color(') ..write(color.space) @@ -1042,20 +1074,21 @@ final class _SerializeVisitor if (singleton && !value.hasBrackets) _buffer.writeCharCode($lparen); _writeBetween( - _inspect - ? value.asList - : value.asList.where((element) => !element.isBlank), - _separatorString(value.separator), - _inspect - ? (element) { - var needsParens = _elementNeedsParens(value.separator, element); - if (needsParens) _buffer.writeCharCode($lparen); - element.accept(this); - if (needsParens) _buffer.writeCharCode($rparen); - } - : (element) { - element.accept(this); - }); + _inspect + ? value.asList + : value.asList.where((element) => !element.isBlank), + _separatorString(value.separator), + _inspect + ? (element) { + var needsParens = _elementNeedsParens(value.separator, element); + if (needsParens) _buffer.writeCharCode($lparen); + element.accept(this); + if (needsParens) _buffer.writeCharCode($rparen); + } + : (element) { + element.accept(this); + }, + ); if (singleton) { _buffer.write(value.separator.separator); @@ -1073,7 +1106,7 @@ final class _SerializeVisitor // This should never be used, but it may still be returned since // [_separatorString] is invoked eagerly by [writeList] even for lists // with only one elements. - _ => "" + _ => "", }; /// Returns whether [value] needs parentheses as an element in a list with the @@ -1087,7 +1120,7 @@ final class _SerializeVisitor value.separator == ListSeparator.slash, _ => value.separator != ListSeparator.undecided, }, - _ => false + _ => false, }; void visitMap(SassMap map) { @@ -1243,8 +1276,10 @@ final class _SerializeVisitor /// to [SassNumber.precision] digits after the decimal and writes the result /// to [_buffer]. void _writeRounded(String text, SourceMapBuffer buffer) { - assert(RegExp(r"^-?\d+(\.\d+)?$").hasMatch(text), - '"$text" should be a number written without exponent notation.'); + assert( + RegExp(r"^-?\d+(\.\d+)?$").hasMatch(text), + '"$text" should be a number written without exponent notation.', + ); // Dart serializes all doubles with a trailing `.0`, even if they have // integer values. In that case we definitely don't need to adjust for @@ -1491,7 +1526,11 @@ final class _SerializeVisitor /// characters are often used for glyph fonts, where it's useful for readers /// to be able to distinguish between them in the rendered stylesheet. int? _tryPrivateUseCharacter( - StringBuffer buffer, int codeUnit, String string, int i) { + StringBuffer buffer, + int codeUnit, + String string, + int i, + ) { if (_isCompressed) return null; if (codeUnit.isPrivateUseBMP) { @@ -1500,8 +1539,12 @@ final class _SerializeVisitor } if (codeUnit.isPrivateUseHighSurrogate && string.length > i + 1) { - _writeEscape(buffer, - combineSurrogates(codeUnit, string.codeUnitAt(i + 1)), string, i + 1); + _writeEscape( + buffer, + combineSurrogates(codeUnit, string.codeUnitAt(i + 1)), + string, + i + 1, + ); return i + 1; } @@ -1559,7 +1602,7 @@ final class _SerializeVisitor if (complex case ComplexSelector( leadingCombinators: [_, ...], - components: [_, ...] + components: [_, ...], )) { _writeOptionalSpace(); } @@ -1635,7 +1678,7 @@ final class _SerializeVisitor if (pseudo case PseudoSelector( name: 'not', - selector: SelectorList(isInvisible: true) + selector: SelectorList(isInvisible: true), )) { return; } @@ -1752,7 +1795,9 @@ final class _SerializeVisitor var endOffset = previous.span.text.lastIndexOf("{", searchFrom); endOffset = math.max(0, endOffset); var span = previous.span.file.span( - previous.span.start.offset, previous.span.start.offset + endOffset); + previous.span.start.offset, + previous.span.start.offset + endOffset, + ); return node.span.start.line == span.end.line; } @@ -1782,7 +1827,10 @@ final class _SerializeVisitor /// Calls [callback] to write each value in [iterable], and writes [text] in /// between each one. void _writeBetween( - Iterable iterable, String text, void callback(T value)) { + Iterable iterable, + String text, + void callback(T value), + ) { var first = true; for (var value in iterable) { if (first) { @@ -1836,7 +1884,7 @@ enum OutputStyle { /// ```css /// .sidebar{width:100px} /// ``` - compressed; + compressed, } /// An enum of line feed sequences. @@ -1867,11 +1915,9 @@ enum LineFeed { /// The result of converting a CSS AST to CSS text. typedef SerializeResult = ( /// The serialized CSS. - String css, - + String css, { /// The source map indicating how the source files map to [css]. /// /// This is `null` if source mapping was disabled for this compilation. - { - SingleMapping? sourceMap + SingleMapping? sourceMap, }); diff --git a/lib/src/visitor/source_interpolation.dart b/lib/src/visitor/source_interpolation.dart index 9a8e70634..7251e13c0 100644 --- a/lib/src/visitor/source_interpolation.dart +++ b/lib/src/visitor/source_interpolation.dart @@ -30,15 +30,18 @@ class SourceInterpolationVisitor implements ExpressionVisitor { void visitFunctionExpression(FunctionExpression node) => buffer = null; void visitInterpolatedFunctionExpression( - InterpolatedFunctionExpression node) { + InterpolatedFunctionExpression node, + ) { buffer?.addInterpolation(node.name); _visitArguments(node.arguments); } /// Visits the positional arguments in [arguments] with [visitor], if it's /// valid interpolated plain CSS. - void _visitArguments(ArgumentList arguments, - [ExpressionVisitor? visitor]) { + void _visitArguments( + ArgumentList arguments, [ + ExpressionVisitor? visitor, + ]) { if (arguments.named.isNotEmpty || arguments.rest != null) return; if (arguments.positional.isEmpty) { @@ -119,8 +122,10 @@ class SourceInterpolationVisitor implements ExpressionVisitor { /// Visits each expression in [expression] with [visitor], and writes whatever /// text is between them to [buffer]. - void _writeListAndBetween(List expressions, - [ExpressionVisitor? visitor]) { + void _writeListAndBetween( + List expressions, [ + ExpressionVisitor? visitor, + ]) { visitor ??= this; Expression? lastExpression; diff --git a/lib/src/visitor/statement_search.dart b/lib/src/visitor/statement_search.dart index 6abd7fe9f..c32166746 100644 --- a/lib/src/visitor/statement_search.dart +++ b/lib/src/visitor/statement_search.dart @@ -46,9 +46,12 @@ mixin StatementSearchVisitor implements StatementVisitor { T? visitIfRule(IfRule node) => node.clauses.search( - (clause) => clause.children.search((child) => child.accept(this))) ?? - node.lastClause.andThen((lastClause) => - lastClause.children.search((child) => child.accept(this))); + (clause) => clause.children.search((child) => child.accept(this)), + ) ?? + node.lastClause.andThen( + (lastClause) => + lastClause.children.search((child) => child.accept(this)), + ); T? visitImportRule(ImportRule node) => null; diff --git a/pkg/sass_api/lib/sass_api.dart b/pkg/sass_api/lib/sass_api.dart index 62c8f862e..acc67b548 100644 --- a/pkg/sass_api/lib/sass_api.dart +++ b/pkg/sass_api/lib/sass_api.dart @@ -2,9 +2,6 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -/// We strongly recommend importing this library with the prefix `sass`. -library sass; - // ignore_for_file: implementation_imports import 'package:sass/src/parse/parser.dart'; diff --git a/test/browser_test.dart b/test/browser_test.dart index a6efa9785..80a366c65 100644 --- a/test/browser_test.dart +++ b/test/browser_test.dart @@ -16,14 +16,20 @@ external Sass get sass; @JS() class Sass { - external NodeCompileResult compileString(String text, - [CompileStringOptions? options]); - external Promise compileStringAsync(String text, - [CompileStringOptions? options]); + external NodeCompileResult compileString( + String text, [ + CompileStringOptions? options, + ]); + external Promise compileStringAsync( + String text, [ + CompileStringOptions? options, + ]); external NodeCompileResult compile(String path, [CompileOptions? options]); external Promise compileAsync(String path, [CompileOptions? options]); external void render( - RenderOptions options, void callback(Error error, RenderResult result)); + RenderOptions options, + void callback(Error error, RenderResult result), + ); external RenderResult renderSync(RenderOptions options); external String get info; } @@ -32,48 +38,75 @@ void main() { setUpAll(ensureNpmPackage); test('compileAsync() is not available', () { - expect(() => sass.compileAsync('index.scss'), throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), - startsWith( - "Error: The compileAsync() method is only available in Node.js.")); - return true; - }))); + expect( + () => sass.compileAsync('index.scss'), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Error: The compileAsync() method is only available in Node.js.", + ), + ); + return true; + }), + ), + ); }); test('compile() is not available', () { - expect(() => sass.compile('index.scss'), throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), - startsWith( - "Error: The compile() method is only available in Node.js.")); - return true; - }))); + expect( + () => sass.compile('index.scss'), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Error: The compile() method is only available in Node.js.", + ), + ); + return true; + }), + ), + ); }); test('render() is not available', () { - expect(() => sass.render(RenderOptions(), allowInterop((error, result) {})), - throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), - startsWith( - "Error: The render() method is only available in Node.js.")); - return true; - }))); + expect( + () => sass.render(RenderOptions(), allowInterop((error, result) {})), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Error: The render() method is only available in Node.js.", + ), + ); + return true; + }), + ), + ); }); test('renderSync() is not available', () { - expect(() => sass.renderSync(RenderOptions()), throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), - startsWith( - "Error: The renderSync() method is only available in Node.js.")); - return true; - }))); + expect( + () => sass.renderSync(RenderOptions()), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Error: The renderSync() method is only available in Node.js.", + ), + ); + return true; + }), + ), + ); }); test('info produces output', () { @@ -132,45 +165,64 @@ void main() { expect(getProperty(sourceMap, 'mappings'), isA()); }); - test('compileStringAsync() produces a sourceMap with source content', - () async { - var opts = jsify({'sourceMap': true, 'sourceMapIncludeSources': true}) - as CompileStringOptions; - var result = sass.compileStringAsync('foo {bar: baz}', opts); - result = await promiseToFuture(result); - var sourceMap = (result as NodeCompileResult).sourceMap; + test( + 'compileStringAsync() produces a sourceMap with source content', + () async { + var opts = jsify({'sourceMap': true, 'sourceMapIncludeSources': true}) + as CompileStringOptions; + var result = sass.compileStringAsync('foo {bar: baz}', opts); + result = await promiseToFuture(result); + var sourceMap = (result as NodeCompileResult).sourceMap; - expect(sourceMap, isA()); + expect(sourceMap, isA()); - sourceMap = sourceMap!; + sourceMap = sourceMap!; - expect(getProperty>(sourceMap, 'sourcesContent'), isList); - expect(getProperty>(sourceMap, 'sourcesContent'), isNotEmpty); - }); + expect(getProperty>(sourceMap, 'sourcesContent'), isList); + expect( + getProperty>(sourceMap, 'sourcesContent'), + isNotEmpty, + ); + }, + ); test('compileString() throws error if importing without custom importer', () { - expect(() => sass.compileString("@use 'other';"), - throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), - startsWith( - "Custom importers are required to load stylesheets when compiling in the browser.")); - return true; - }))); + expect( + () => sass.compileString("@use 'other';"), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Custom importers are required to load stylesheets when compiling in the browser.", + ), + ); + return true; + }), + ), + ); }); - test('compileStringAsync() throws error if importing without custom importer', - () async { - var result = sass.compileStringAsync("@use 'other';"); - expect(() async => await promiseToFuture(result), - throwsA(predicate((error) { - expect(error, const TypeMatcher()); + test( + 'compileStringAsync() throws error if importing without custom importer', + () async { + var result = sass.compileStringAsync("@use 'other';"); expect( - error.toString(), - startsWith( - "Custom importers are required to load stylesheets when compiling in the browser.")); - return true; - }))); - }); + () async => await promiseToFuture(result), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith( + "Custom importers are required to load stylesheets when compiling in the browser.", + ), + ); + return true; + }), + ), + ); + }, + ); } diff --git a/test/cli/dart/errors_test.dart b/test/cli/dart/errors_test.dart index 85c269865..1fc953e11 100644 --- a/test/cli/dart/errors_test.dart +++ b/test/cli/dart/errors_test.dart @@ -20,15 +20,16 @@ void main() { var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: Can't find stylesheet to import.", - " ,", - "1 | @use 'package:nope/test';", - " | ^^^^^^^^^^^^^^^^^^^^^^^^", - " '", - " test.scss 1:1 root stylesheet" - ])); + sass.stderr, + emitsInOrder([ + "Error: Can't find stylesheet to import.", + " ,", + "1 | @use 'package:nope/test';", + " | ^^^^^^^^^^^^^^^^^^^^^^^^", + " '", + " test.scss 1:1 root stylesheet", + ]), + ); await sass.shouldExit(65); }); } diff --git a/test/cli/dart/watch_test.dart b/test/cli/dart/watch_test.dart index 7288dc468..303e6beb5 100644 --- a/test/cli/dart/watch_test.dart +++ b/test/cli/dart/watch_test.dart @@ -4,7 +4,6 @@ // OS X's modification time reporting is flaky, so we skip these tests on it. @TestOn('vm && !mac-os') - // File watching is inherently flaky at the OS level. To mitigate this, we do a // few retries when the tests fail. @Retry(3) diff --git a/test/cli/dart_test.dart b/test/cli/dart_test.dart index 4c8ee35c6..2a91f0a48 100644 --- a/test/cli/dart_test.dart +++ b/test/cli/dart_test.dart @@ -30,7 +30,14 @@ void main() { /// up-to-date, if one has been generated. void ensureSnapshotUpToDate() => pkg.ensureExecutableUpToDate("sass"); -Future runSass(Iterable arguments, - {Map? environment}) => - pkg.start("sass", arguments, - environment: environment, workingDirectory: d.sandbox, encoding: utf8); +Future runSass( + Iterable arguments, { + Map? environment, +}) => + pkg.start( + "sass", + arguments, + environment: environment, + workingDirectory: d.sandbox, + encoding: utf8, + ); diff --git a/test/cli/node/errors_test.dart b/test/cli/node/errors_test.dart index a20e4905c..861c07603 100644 --- a/test/cli/node/errors_test.dart +++ b/test/cli/node/errors_test.dart @@ -22,15 +22,16 @@ void main() { var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: \"package:\" URLs aren't supported on this platform.", - " ,", - "1 | @use 'package:nope/test';", - " | ^^^^^^^^^^^^^^^^^^^^^^^^", - " '", - " test.scss 1:1 root stylesheet" - ])); + sass.stderr, + emitsInOrder([ + "Error: \"package:\" URLs aren't supported on this platform.", + " ,", + "1 | @use 'package:nope/test';", + " | ^^^^^^^^^^^^^^^^^^^^^^^^", + " '", + " test.scss 1:1 root stylesheet", + ]), + ); await sass.shouldExit(65); }); } diff --git a/test/cli/node/watch_test.dart b/test/cli/node/watch_test.dart index 2de6bf452..ff280749f 100644 --- a/test/cli/node/watch_test.dart +++ b/test/cli/node/watch_test.dart @@ -5,7 +5,6 @@ // OS X's modification time reporting is flaky, so we skip these tests on it. @TestOn('vm && !mac-os') @Tags(['node']) - // File watching is inherently flaky at the OS level. To mitigate this, we do a // few retries when the tests fail. @Retry(3) diff --git a/test/cli/node_test.dart b/test/cli/node_test.dart index b3224d0fd..0598ed624 100644 --- a/test/cli/node_test.dart +++ b/test/cli/node_test.dart @@ -24,17 +24,26 @@ void main() { test("--version prints the Sass and dart2js versions", () async { var sass = await runSass(["--version"]); expect( - sass.stdout, - emits(matches( - RegExp(r"^\d+\.\d+\.\d+.* compiled with dart2js \d+\.\d+\.\d+")))); + sass.stdout, + emits( + matches( + RegExp(r"^\d+\.\d+\.\d+.* compiled with dart2js \d+\.\d+\.\d+"), + ), + ), + ); await sass.shouldExit(0); }); } -Future runSass(Iterable arguments, - {Map? environment}) => - pkg.start("sass", arguments, - environment: environment, - workingDirectory: d.sandbox, - encoding: utf8, - node: true); +Future runSass( + Iterable arguments, { + Map? environment, +}) => + pkg.start( + "sass", + arguments, + environment: environment, + workingDirectory: d.sandbox, + encoding: utf8, + node: true, + ); diff --git a/test/cli/shared.dart b/test/cli/shared.dart index f66f40599..46ae07844 100644 --- a/test/cli/shared.dart +++ b/test/cli/shared.dart @@ -12,14 +12,23 @@ import 'package:test_process/test_process.dart'; /// Defines test that are shared between the Dart and Node.js CLI test suites. void sharedTests( - Future runSass(Iterable arguments, - {Map? environment})) { + Future runSass( + Iterable arguments, { + Map? environment, + }), +) { /// Runs the executable on [arguments] plus an output file, then verifies that /// the contents of the output file match [expected]. - Future expectCompiles(List arguments, Object expected, - {Map? environment}) async { - var sass = await runSass([...arguments, "out.css", "--no-source-map"], - environment: environment); + Future expectCompiles( + List arguments, + Object expected, { + Map? environment, + }) async { + var sass = await runSass([ + ...arguments, + "out.css", + "--no-source-map", + ], environment: environment); await sass.shouldExit(0); await d.file("out.css", expected).validate(); } @@ -30,7 +39,9 @@ void sharedTests( var sass = await runSass(["--help"]); expect(sass.stdout, emits("Compile Sass to CSS.")); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -38,13 +49,7 @@ void sharedTests( await d.file("test.scss", "a {b: 1 + 2}").create(); var sass = await runSass(["test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 3;", "}"])); await sass.shouldExit(0); }); @@ -63,13 +68,7 @@ void sharedTests( await d.file("test.scss", "a {b: 1 + 2}").create(); var sass = await runSass([p.absolute(d.path("test.scss"))]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 3;", "}"])); await sass.shouldExit(0); }); @@ -85,8 +84,11 @@ void sharedTests( test("creates directories if necessary", () async { await d.file("test.scss", "a {b: 1 + 2}").create(); - var sass = - await runSass(["--no-source-map", "test.scss", "some/new/dir/out.css"]); + var sass = await runSass([ + "--no-source-map", + "test.scss", + "some/new/dir/out.css", + ]); expect(sass.stdout, emitsDone); await sass.shouldExit(0); await d @@ -98,13 +100,7 @@ void sharedTests( var sass = await runSass(["-"]); sass.stdin.writeln("a {b: 1 + 2}"); sass.stdin.close(); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 3;", "}"])); await sass.shouldExit(0); }); @@ -114,8 +110,9 @@ void sharedTests( await d.dir("dir", [d.file("test.scss", "a {b: 1 + 2}")]).create(); - await expectCompiles( - ["test.scss"], equalsIgnoringWhitespace("a { b: 3; }")); + await expectCompiles([ + "test.scss", + ], equalsIgnoringWhitespace("a { b: 3; }")); }); test("from the load path", () async { @@ -123,8 +120,11 @@ void sharedTests( await d.dir("dir", [d.file("test2.scss", "a {b: c}")]).create(); - await expectCompiles(["--load-path", "dir", "test.scss"], - equalsIgnoringWhitespace("a { b: c; }")); + await expectCompiles([ + "--load-path", + "dir", + "test.scss", + ], equalsIgnoringWhitespace("a { b: c; }")); }); test("from SASS_PATH", () async { @@ -138,26 +138,34 @@ void sharedTests( var separator = Platform.isWindows ? ';' : ':'; await expectCompiles( - ["test.scss"], equalsIgnoringWhitespace("a { b: c; } x { y: z; }"), - environment: {"SASS_PATH": "dir2${separator}dir3"}); + ["test.scss"], + equalsIgnoringWhitespace("a { b: c; } x { y: z; }"), + environment: {"SASS_PATH": "dir2${separator}dir3"}, + ); }); // Regression test for #369 - test("from within a directory, relative to a file on the load path", - () async { - await d - .dir("dir1", [d.file("test.scss", "@use 'subdir/test2'")]).create(); + test( + "from within a directory, relative to a file on the load path", + () async { + await d.dir("dir1", [ + d.file("test.scss", "@use 'subdir/test2'"), + ]).create(); - await d.dir("dir2", [ - d.dir("subdir", [ - d.file("test2.scss", "@use 'test3'"), - d.file("test3.scss", "a {b: c}") - ]) - ]).create(); + await d.dir("dir2", [ + d.dir("subdir", [ + d.file("test2.scss", "@use 'test3'"), + d.file("test3.scss", "a {b: c}"), + ]), + ]).create(); - await expectCompiles(["--load-path", "dir2", "dir1/test.scss"], - equalsIgnoringWhitespace("a { b: c; }")); - }); + await expectCompiles([ + "--load-path", + "dir2", + "dir1/test.scss", + ], equalsIgnoringWhitespace("a { b: c; }")); + }, + ); test("relative in preference to from the load path", () async { await d.file("test.scss", "@use 'test2'").create(); @@ -165,8 +173,11 @@ void sharedTests( await d.dir("dir", [d.file("test2.scss", "a {b: c}")]).create(); - await expectCompiles(["--load-path", "dir", "test.scss"], - equalsIgnoringWhitespace("x { y: z; }")); + await expectCompiles([ + "--load-path", + "dir", + "test.scss", + ], equalsIgnoringWhitespace("x { y: z; }")); }); test("in load path order", () async { @@ -175,9 +186,13 @@ void sharedTests( await d.dir("dir1", [d.file("test2.scss", "a {b: c}")]).create(); await d.dir("dir2", [d.file("test2.scss", "x {y: z}")]).create(); - await expectCompiles( - ["--load-path", "dir2", "--load-path", "dir1", "test.scss"], - equalsIgnoringWhitespace("x { y: z; }")); + await expectCompiles([ + "--load-path", + "dir2", + "--load-path", + "dir1", + "test.scss", + ], equalsIgnoringWhitespace("x { y: z; }")); }); test("from the load path in preference to from SASS_PATH", () async { @@ -186,9 +201,11 @@ void sharedTests( await d.dir("dir1", [d.file("test2.scss", "a {b: c}")]).create(); await d.dir("dir2", [d.file("test2.scss", "x {y: z}")]).create(); - await expectCompiles(["--load-path", "dir2", "test.scss"], - equalsIgnoringWhitespace("x { y: z; }"), - environment: {"SASS_PATH": "dir1"}); + await expectCompiles( + ["--load-path", "dir2", "test.scss"], + equalsIgnoringWhitespace("x { y: z; }"), + environment: {"SASS_PATH": "dir1"}, + ); }); test("in SASS_PATH order", () async { @@ -199,8 +216,10 @@ void sharedTests( var separator = Platform.isWindows ? ';' : ':'; await expectCompiles( - ["test.scss"], equalsIgnoringWhitespace("x { y: z; }"), - environment: {"SASS_PATH": "dir2${separator}dir3"}); + ["test.scss"], + equalsIgnoringWhitespace("x { y: z; }"), + environment: {"SASS_PATH": "dir2${separator}dir3"}, + ); }); // Regression test for an internal Google issue. @@ -214,9 +233,9 @@ void sharedTests( d.dir("parent", [ d.dir("child", [ d.file("test2.scss", "@import 'test3';"), - d.file("test3.scss", "a {b: c};") - ]) - ]) + d.file("test3.scss", "a {b: c};"), + ]), + ]), ]).create(); await expectCompiles([ @@ -226,7 +245,7 @@ void sharedTests( "grandparent/parent", "--silence-deprecation", "import", - "test.scss" + "test.scss", ], equalsIgnoringWhitespace("a { b: c; } a { b: c; }")); }); @@ -239,7 +258,7 @@ void sharedTests( await d.dir("load-path", [ d.file("_library.scss", "a { b: regular }"), - d.file("_library.import.scss", "a { b: import-only }") + d.file("_library.import.scss", "a { b: import-only }"), ]).create(); await expectCompiles([ @@ -247,7 +266,7 @@ void sharedTests( "load-path", "--silence-deprecation", "import", - "test.scss" + "test.scss", ], equalsIgnoringWhitespace("a { b: regular; } a { b: import-only; }")); }); }); @@ -257,13 +276,7 @@ void sharedTests( var sass = await runSass(["--stdin"]); sass.stdin.writeln("a {b: 1 + 2}"); sass.stdin.close(); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 3;", "}"])); await sass.shouldExit(0); }); @@ -283,13 +296,7 @@ void sharedTests( var sass = await runSass(["--no-source-map", "--stdin", "--indented"]); sass.stdin.writeln("a\n b: 1 + 2"); sass.stdin.close(); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 3;", "}"])); await sass.shouldExit(0); }); @@ -308,15 +315,16 @@ void sharedTests( sass.stdin.writeln("a {b: 1 + }"); sass.stdin.close(); expect( - sass.stderr, - emitsInOrder([ - "Error: Expected expression.", - " ,", - "1 | a {b: 1 + }", - " | ^", - " '", - " - 1:11 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: Expected expression.", + " ,", + "1 | a {b: 1 + }", + " | ^", + " '", + " - 1:11 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -328,15 +336,16 @@ void sharedTests( sass.stdin.writeln("a {b: (#123) + (#456)}"); sass.stdin.close(); expect( - sass.stderr, - emitsInOrder([ - 'Error: Undefined operation "#123 + #456".', - " ,", - "1 | a {b: (#123) + (#456)}", - " | ^^^^^^^^^^^^^^^", - " '", - " - 1:7 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + 'Error: Undefined operation "#123 + #456".', + " ,", + "1 | a {b: (#123) + (#456)}", + " | ^^^^^^^^^^^^^^^", + " '", + " - 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -345,13 +354,7 @@ void sharedTests( await d.file("_test.scss", "x {y: z}").create(); var sass = await runSass(["test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: c;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: c;", "}"])); await sass.shouldExit(0); }); @@ -361,11 +364,9 @@ void sharedTests( var sass = await runSass(["test.scss"]); expect(sass.stdout, emitsDone); expect( - sass.stderr, - emitsInOrder([ - "WARNING: aw beans", - " test.scss 1:1 root stylesheet", - ])); + sass.stderr, + emitsInOrder(["WARNING: aw beans", " test.scss 1:1 root stylesheet"]), + ); await sass.shouldExit(0); }); @@ -489,27 +490,29 @@ void sharedTests( }); // Regression test for sass/dart-sass#2418 - test("doesn't emit runner warnings in content blocks from local @include", - () async { - await d.file("test.scss", """ - @use 'other'; - @include other.foo; - """).create(); - await d.dir("dir", [ - d.file("_other.scss", """ + test( + "doesn't emit runner warnings in content blocks from local @include", + () async { + await d.file("test.scss", """ + @use 'other'; + @include other.foo; + """).create(); + await d.dir("dir", [ + d.file("_other.scss", """ @mixin bar {@content} @mixin foo { @include bar { #{blue} {x: y} } } - """) - ]).create(); + """), + ]).create(); - var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); - expect(sass.stderr, emitsDone); - await sass.shouldExit(0); - }); + var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); + expect(sass.stderr, emitsDone); + await sass.shouldExit(0); + }, + ); }); group("silences warnings through @import", () { @@ -523,7 +526,7 @@ void sharedTests( "dir", "--silence-deprecation", "import", - "test.scss" + "test.scss", ]); expect(sass.stderr, emitsDone); await sass.shouldExit(0); @@ -535,7 +538,7 @@ void sharedTests( d.file("_other.scss", """ @use 'sass:color'; #{blue} {x: y} - """) + """), ]).create(); var sass = await runSass([ @@ -544,7 +547,7 @@ void sharedTests( "dir", "--silence-deprecation", "import", - "test.scss" + "test.scss", ]); expect(sass.stderr, emitsDone); await sass.shouldExit(0); @@ -561,10 +564,10 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @mixin foo { - @warn heck; - } - """) + @mixin foo { + @warn heck; + } + """), ]).create(); var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); @@ -580,10 +583,10 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @mixin foo { - @debug heck; - } - """) + @mixin foo { + @debug heck; + } + """), ]).create(); var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); @@ -599,10 +602,10 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @mixin foo { - #{blue} {x: y} - } - """) + @mixin foo { + #{blue} {x: y} + } + """), ]).create(); await d.file("test.scss", "@use 'other'").create(); await d.dir("dir", [d.file("_other.scss", "")]).create(); @@ -622,11 +625,11 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @function foo() { - @warn heck; - @return null; - } - """) + @function foo() { + @warn heck; + @return null; + } + """), ]).create(); var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); @@ -642,11 +645,11 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @function foo() { - @debug heck; - @return null; - } - """) + @function foo() { + @debug heck; + @return null; + } + """), ]).create(); var sass = await runSass(["--quiet-deps", "-I", "dir", "test.scss"]); @@ -662,10 +665,10 @@ void sharedTests( await d.dir("dir", [ d.file("_other.scss", """ - @function foo() { - @return #{blue}; - } - """) + @function foo() { + @return #{blue}; + } + """), ]).create(); await d.file("test.scss", "@use 'other'").create(); await d.dir("dir", [d.file("_other.scss", "")]).create(); @@ -681,49 +684,61 @@ void sharedTests( group("with a bunch of deprecation warnings", () { setUp(() async { await d.file("test.scss", r""" - @use "sass:list"; - @use "sass:meta"; - - $_: meta.call("inspect", null); - $_: meta.call("rgb", 0, 0, 0); - $_: meta.call("nth", null, 1); - $_: meta.call("join", null, null); - $_: meta.call("if", true, 1, 2); - $_: meta.call("hsl", 0, 100%, 100%); - - $_: 1/2; - $_: 1/3; - $_: 1/4; - $_: 1/5; - $_: 1/6; - $_: 1/7; - """).create(); + @use "sass:list"; + @use "sass:meta"; + + $_: meta.call("inspect", null); + $_: meta.call("rgb", 0, 0, 0); + $_: meta.call("nth", null, 1); + $_: meta.call("join", null, null); + $_: meta.call("if", true, 1, 2); + $_: meta.call("hsl", 0, 100%, 100%); + + $_: 1/2; + $_: 1/3; + $_: 1/4; + $_: 1/5; + $_: 1/6; + $_: 1/7; + """).create(); }); test("without --verbose, only prints five", () async { var sass = await runSass(["test.scss"]); - expect(sass.stderr, - emitsInOrder(List.filled(5, emitsThrough(contains("call()"))))); + expect( + sass.stderr, + emitsInOrder(List.filled(5, emitsThrough(contains("call()")))), + ); expect(sass.stderr, neverEmits(contains("call()"))); - expect(sass.stderr, - emitsInOrder(List.filled(5, emitsThrough(contains("math.div"))))); + expect( + sass.stderr, + emitsInOrder(List.filled(5, emitsThrough(contains("math.div")))), + ); expect(sass.stderr, neverEmits(contains("math.div()"))); - expect(sass.stderr, - emitsThrough(contains("2 repetitive deprecation warnings omitted."))); + expect( + sass.stderr, + emitsThrough(contains("2 repetitive deprecation warnings omitted.")), + ); }); test("with --verbose, prints all", () async { var sass = await runSass(["--verbose", "test.scss"]); - expect(sass.stderr, - neverEmits(contains("2 repetitive deprecation warnings omitted."))); + expect( + sass.stderr, + neverEmits(contains("2 repetitive deprecation warnings omitted.")), + ); - expect(sass.stderr, - emitsInOrder(List.filled(6, emitsThrough(contains("call()"))))); + expect( + sass.stderr, + emitsInOrder(List.filled(6, emitsThrough(contains("call()")))), + ); - expect(sass.stderr, - emitsInOrder(List.filled(6, emitsThrough(contains("math.div"))))); + expect( + sass.stderr, + emitsInOrder(List.filled(6, emitsThrough(contains("math.div")))), + ); }); }); @@ -732,13 +747,7 @@ void sharedTests( await d.file("test.scss", "a {b: c}").create(); var sass = await runSass(["test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: c;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: c;", "}"])); await sass.shouldExit(0); }); @@ -747,31 +756,32 @@ void sharedTests( var sass = await runSass(["test.scss"]); expect( - sass.stdout, - emitsInOrder([ - "@charset \"UTF-8\";", - "a {", - " b: 👭;", - "}", - ])); + sass.stdout, + emitsInOrder(["@charset \"UTF-8\";", "a {", " b: 👭;", "}"]), + ); await sass.shouldExit(0); }); test("emits a BOM with compressed output", () async { await d.file("test.scss", "a {b: 👭}").create(); - var sass = await runSass( - ["--no-source-map", "--style=compressed", "test.scss", "test.css"]); + var sass = await runSass([ + "--no-source-map", + "--style=compressed", + "test.scss", + "test.css", + ]); await sass.shouldExit(0); // We can't verify this as a string because `dart:io` automatically trims // the BOM. var bomBytes = utf8.encode("\uFEFF"); expect( - File(p.join(d.sandbox, "test.css")) - .readAsBytesSync() - .sublist(0, bomBytes.length), - equals(bomBytes)); + File( + p.join(d.sandbox, "test.css"), + ).readAsBytesSync().sublist(0, bomBytes.length), + equals(bomBytes), + ); }); }); @@ -780,13 +790,7 @@ void sharedTests( await d.file("test.scss", "a {b: 👭}").create(); var sass = await runSass(["--no-charset", "test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 👭;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 👭;", "}"])); await sass.shouldExit(0); }); @@ -798,7 +802,7 @@ void sharedTests( "--no-source-map", "--style=compressed", "test.scss", - "test.css" + "test.css", ]); await sass.shouldExit(0); @@ -806,10 +810,11 @@ void sharedTests( // the BOM. var bomBytes = utf8.encode("\uFEFF"); expect( - File(p.join(d.sandbox, "test.css")) - .readAsBytesSync() - .sublist(0, bomBytes.length), - isNot(equals(bomBytes))); + File( + p.join(d.sandbox, "test.css"), + ).readAsBytesSync().sublist(0, bomBytes.length), + isNot(equals(bomBytes)), + ); }); }); @@ -878,13 +883,7 @@ void sharedTests( test("set to lower version, only warns", () async { await d.file("test.scss", "a {b: (4/2)}").create(); var sass = await runSass(["--fatal-deprecation=1.32.0", "test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 2;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: 2;", "}"])); expect(sass.stderr, emitsThrough(contains("DEPRECATION WARNING"))); await sass.shouldExit(0); }); @@ -902,13 +901,7 @@ void sharedTests( await d.file("_lib.scss", "a{b:c}").create(); await d.file("test.scss", "@import 'lib'").create(); var sass = await runSass(["--future-deprecation=import", "test.scss"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: c;", - "}", - ])); + expect(sass.stdout, emitsInOrder(["a {", " b: c;", "}"])); expect(sass.stderr, emitsThrough(contains("DEPRECATION WARNING"))); await sass.shouldExit(0); }); @@ -919,7 +912,7 @@ void sharedTests( var sass = await runSass([ "--future-deprecation=import", "--fatal-deprecation=import", - "test.scss" + "test.scss", ]); expect(sass.stdout, emitsDone); await sass.shouldExit(65); @@ -944,16 +937,25 @@ void sharedTests( await d.file("_midstream.scss", "@forward 'upstream'").create(); await d.file("_upstream.scss", r"$c: g").create(); - var sass = await runSass( - ["--silence-deprecation", "import", "input.scss", "output.css"]); + var sass = await runSass([ + "--silence-deprecation", + "import", + "input.scss", + "output.css", + ]); await sass.shouldExit(0); - await d.file("output.css", equalsIgnoringWhitespace(""" - a e { - f: g; - } + await d + .file( + "output.css", + equalsIgnoringWhitespace(""" + a e { + f: g; + } - /*# sourceMappingURL=output.css.map */ - """)).validate(); + /*# sourceMappingURL=output.css.map */ + """), + ) + .validate(); }); } diff --git a/test/cli/shared/colon_args.dart b/test/cli/shared/colon_args.dart index a5ccf314f..f0f985fc7 100644 --- a/test/cli/shared/colon_args.dart +++ b/test/cli/shared/colon_args.dart @@ -13,8 +13,11 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test1.scss", "a {b: c}").create(); await d.file("test2.scss", "x {y: z}").create(); - var sass = await runSass( - ["--no-source-map", "test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await runSass([ + "--no-source-map", + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); expect(sass.stdout, emitsDone); await sass.shouldExit(0); @@ -48,7 +51,7 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(0); await d.dir("dir", [ - d.file("out.css", equalsIgnoringWhitespace("a { b: c; }")) + d.file("out.css", equalsIgnoringWhitespace("a { b: c; }")), ]).validate(); }); @@ -71,8 +74,11 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test2.scss", "x {y: z}").create(); var message = 'Error: Expected expression.'; - var sass = await runSass( - ["--no-source-map", "test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await runSass([ + "--no-source-map", + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); await expectLater(sass.stderr, emits(message)); await expectLater(sass.stderr, emitsThrough(contains('test1.scss 1:7'))); await sass.shouldExit(65); @@ -88,12 +94,19 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test2.scss", "x {y: z}").create(); var message = 'Error: Expected expression.'; - var sass = await runSass( - ["--stop-on-error", "test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await runSass([ + "--stop-on-error", + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); await expectLater( - sass.stderr, - emitsInOrder( - [message, emitsThrough(contains('test1.scss 1:7')), emitsDone])); + sass.stderr, + emitsInOrder([ + message, + emitsThrough(contains('test1.scss 1:7')), + emitsDone, + ]), + ); await sass.shouldExit(65); await d.file("out1.css", contains(message)).validate(); @@ -104,7 +117,7 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("in", [ d.file("test1.scss", "a {b: c}"), d.file("test2.sass", "x\n y: z"), - d.file("test3.css", "q {r: s}") + d.file("test3.css", "q {r: s}"), ]).create(); var sass = await runSass(["--no-source-map", "in:out"]); @@ -114,13 +127,13 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("out", [ d.file("test1.css", equalsIgnoringWhitespace("a { b: c; }")), d.file("test2.css", equalsIgnoringWhitespace("x { y: z; }")), - d.file("test3.css", equalsIgnoringWhitespace("q { r: s; }")) + d.file("test3.css", equalsIgnoringWhitespace("q { r: s; }")), ]).validate(); }); test("creates subdirectories in the destination", () async { await d.dir("in", [ - d.dir("sub", [d.file("test.scss", "a {b: c}")]) + d.dir("sub", [d.file("test.scss", "a {b: c}")]), ]).create(); var sass = await runSass(["--no-source-map", "in:out"]); @@ -128,32 +141,35 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(0); await d.dir("out", [ - d.dir("sub", - [d.file("test.css", equalsIgnoringWhitespace("a { b: c; }"))]) + d.dir("sub", [ + d.file("test.css", equalsIgnoringWhitespace("a { b: c; }")), + ]), ]).validate(); }); - test("compiles files to the same directory if no output is given", - () async { - await d.dir("in", [ - d.file("test1.scss", "a {b: c}"), - d.file("test2.sass", "x\n y: z") - ]).create(); - - var sass = await runSass(["--no-source-map", "in"]); - expect(sass.stdout, emitsDone); - await sass.shouldExit(0); - - await d.dir("in", [ - d.file("test1.css", equalsIgnoringWhitespace("a { b: c; }")), - d.file("test2.css", equalsIgnoringWhitespace("x { y: z; }")) - ]).validate(); - }); + test( + "compiles files to the same directory if no output is given", + () async { + await d.dir("in", [ + d.file("test1.scss", "a {b: c}"), + d.file("test2.sass", "x\n y: z"), + ]).create(); + + var sass = await runSass(["--no-source-map", "in"]); + expect(sass.stdout, emitsDone); + await sass.shouldExit(0); + + await d.dir("in", [ + d.file("test1.css", equalsIgnoringWhitespace("a { b: c; }")), + d.file("test2.css", equalsIgnoringWhitespace("x { y: z; }")), + ]).validate(); + }, + ); test("ignores partials", () async { await d.dir("in", [ d.file("_fake.scss", "a {b:"), - d.file("real.scss", "x {y: z}") + d.file("real.scss", "x {y: z}"), ]).create(); var sass = await runSass(["--no-source-map", "in:out"]); @@ -163,14 +179,14 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("out", [ d.file("real.css", equalsIgnoringWhitespace("x { y: z; }")), d.nothing("fake.css"), - d.nothing("_fake.css") + d.nothing("_fake.css"), ]).validate(); }); test("ignores files without a Sass extension", () async { await d.dir("in", [ d.file("fake.szss", "a {b:"), - d.file("real.scss", "x {y: z}") + d.file("real.scss", "x {y: z}"), ]).create(); var sass = await runSass(["--no-source-map", "in:out"]); @@ -179,7 +195,7 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("out", [ d.file("real.css", equalsIgnoringWhitespace("x { y: z; }")), - d.nothing("fake.css") + d.nothing("fake.css"), ]).validate(); }); @@ -198,12 +214,13 @@ void sharedTests(Future runSass(Iterable arguments)) { test("file-not-found errors", () async { var sass = await runSass(["test1.scss:out1.css", "test2.scss:out2.css"]); expect( - sass.stderr, - emitsInOrder([ - startsWith("Error reading test1.scss: "), - "", - startsWith("Error reading test2.scss: ") - ])); + sass.stderr, + emitsInOrder([ + startsWith("Error reading test1.scss: "), + "", + startsWith("Error reading test2.scss: "), + ]), + ); await sass.shouldExit(66); }); @@ -211,25 +228,29 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test1.scss", "a {b: }").create(); await d.file("test2.scss", "x {y: }").create(); - var sass = await runSass( - ["--no-unicode", "test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await runSass([ + "--no-unicode", + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); expect( - sass.stderr, - emitsInOrder([ - "Error: Expected expression.", - " ,", - "1 | a {b: }", - " | ^", - " '", - " test1.scss 1:7 root stylesheet", - "", - "Error: Expected expression.", - " ,", - "1 | x {y: }", - " | ^", - " '", - " test2.scss 1:7 root stylesheet" - ])); + sass.stderr, + emitsInOrder([ + "Error: Expected expression.", + " ,", + "1 | a {b: }", + " | ^", + " '", + " test1.scss 1:7 root stylesheet", + "", + "Error: Expected expression.", + " ,", + "1 | x {y: }", + " | ^", + " '", + " test2.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -237,25 +258,29 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test1.scss", "a {b: 1 + #abc}").create(); await d.file("test2.scss", "x {y: 1 + #abc}").create(); - var sass = await runSass( - ["--no-unicode", "test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await runSass([ + "--no-unicode", + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); expect( - sass.stderr, - emitsInOrder([ - 'Error: Undefined operation "1 + #abc".', - " ,", - "1 | a {b: 1 + #abc}", - " | ^^^^^^^^", - " '", - " test1.scss 1:7 root stylesheet", - "", - 'Error: Undefined operation "1 + #abc".', - " ,", - "1 | x {y: 1 + #abc}", - " | ^^^^^^^^", - " '", - " test2.scss 1:7 root stylesheet" - ])); + sass.stderr, + emitsInOrder([ + 'Error: Undefined operation "1 + #abc".', + " ,", + "1 | a {b: 1 + #abc}", + " | ^^^^^^^^", + " '", + " test1.scss 1:7 root stylesheet", + "", + 'Error: Undefined operation "1 + #abc".', + " ,", + "1 | x {y: 1 + #abc}", + " | ^^^^^^^^", + " '", + " test2.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); }); @@ -264,15 +289,19 @@ void sharedTests(Future runSass(Iterable arguments)) { group("positional arguments", () { test("before", () async { var sass = await runSass(["positional", "test.scss:out.css"]); - expect(sass.stdout, - emits('Positional and ":" arguments may not both be used.')); + expect( + sass.stdout, + emits('Positional and ":" arguments may not both be used.'), + ); await sass.shouldExit(64); }); test("after", () async { var sass = await runSass(["test.scss:out.css", "positional"]); - expect(sass.stdout, - emits('Positional and ":" arguments may not both be used.')); + expect( + sass.stdout, + emits('Positional and ":" arguments may not both be used.'), + ); await sass.shouldExit(64); }); @@ -281,7 +310,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["positional", "in"]); expect( - sass.stdout, emits('Directory "in" may not be a positional arg.')); + sass.stdout, + emits('Directory "in" may not be a positional arg.'), + ); await sass.shouldExit(64); }); @@ -290,12 +321,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["in", "positional"]); expect( - sass.stdout, - emitsInOrder([ - 'Directory "in" may not be a positional arg.', - 'To compile all CSS in "in" to "positional", use `sass ' - 'in:positional`.' - ])); + sass.stdout, + emitsInOrder([ + 'Directory "in" may not be a positional arg.', + 'To compile all CSS in "in" to "positional", use `sass ' + 'in:positional`.', + ]), + ); await sass.shouldExit(64); }); }); @@ -308,8 +340,10 @@ void sharedTests(Future runSass(Iterable arguments)) { test("multiple colons", () async { var sass = await runSass(["test.scss:out.css:wut"]); - expect(sass.stdout, - emits('"test.scss:out.css:wut" may only contain one ":".')); + expect( + sass.stdout, + emits('"test.scss:out.css:wut" may only contain one ":".'), + ); await sass.shouldExit(64); }); diff --git a/test/cli/shared/deprecations.dart b/test/cli/shared/deprecations.dart index a956a9572..ff31318d6 100644 --- a/test/cli/shared/deprecations.dart +++ b/test/cli/shared/deprecations.dart @@ -15,8 +15,10 @@ void sharedTests(Future runSass(Iterable arguments)) { setUp(() => d.file("test.scss", "").create()); test("for user-authored", () async { - var sass = - await runSass(["--silence-deprecation=user-authored", "test.scss"]); + var sass = await runSass([ + "--silence-deprecation=user-authored", + "test.scss", + ]); expect(sass.stderr, emits(contains("User-authored deprecations"))); await sass.shouldExit(0); }); @@ -35,7 +37,7 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass([ "--future-deprecation=import", "--silence-deprecation=import", - "test.scss" + "test.scss", ]); expect(sass.stderr, emits(contains("Conflicting options for future"))); await sass.shouldExit(0); @@ -46,20 +48,26 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--silence-deprecation=user-authored", - "test.scss:out.css" + "test.scss:out.css", ]); expect(sass.stderr, emits(contains("User-authored deprecations"))); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); }); test("in repl mode", () async { - var sass = await runSass( - ["--interactive", "--silence-deprecation=user-authored"]); + var sass = await runSass([ + "--interactive", + "--silence-deprecation=user-authored", + ]); await expectLater( - sass.stderr, emits(contains("User-authored deprecations"))); + sass.stderr, + emits(contains("User-authored deprecations")), + ); await sass.kill(); }); }); @@ -68,8 +76,10 @@ void sharedTests(Future runSass(Iterable arguments)) { setUp(() => d.file("test.scss", "").create()); test("in immediate mode", () async { - var sass = - await runSass(["--silence-deprecation=unknown", "test.scss"]); + var sass = await runSass([ + "--silence-deprecation=unknown", + "test.scss", + ]); expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); await sass.shouldExit(64); }); @@ -79,15 +89,17 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--silence-deprecation=unknown", - "test.scss:out.css" + "test.scss:out.css", ]); expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); await sass.shouldExit(64); }); test("in repl mode", () async { - var sass = - await runSass(["--interactive", "--silence-deprecation=unknown"]); + var sass = await runSass([ + "--interactive", + "--silence-deprecation=unknown", + ]); expect(sass.stdout, emits(contains('Invalid deprecation "unknown".'))); await sass.shouldExit(64); }); @@ -96,11 +108,14 @@ void sharedTests(Future runSass(Iterable arguments)) { group("silences", () { group("a parse-time deprecation", () { setUp( - () => d.file("test.scss", "@if true {} @elseif false {}").create()); + () => d.file("test.scss", "@if true {} @elseif false {}").create(), + ); test("in immediate mode", () async { - var sass = - await runSass(["--silence-deprecation=elseif", "test.scss"]); + var sass = await runSass([ + "--silence-deprecation=elseif", + "test.scss", + ]); expect(sass.stderr, emitsDone); await sass.shouldExit(0); }); @@ -110,18 +125,22 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--silence-deprecation=elseif", - "test.scss:out.css" + "test.scss:out.css", ]); expect(sass.stderr, emitsDone); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); }); test("in repl mode", () async { - var sass = await runSass( - ["--interactive", "--silence-deprecation=strict-unary"]); + var sass = await runSass([ + "--interactive", + "--silence-deprecation=strict-unary", + ]); expect(sass.stderr, emitsDone); sass.stdin.writeln("4 -(5)"); await expectLater(sass.stdout, emitsInOrder([">> 4 -(5)", "-1"])); @@ -130,14 +149,18 @@ void sharedTests(Future runSass(Iterable arguments)) { }); group("an evaluation-time deprecation", () { - setUp(() => d.file("test.scss", """ - @use 'sass:math'; - a {b: math.random(1px)} - """).create()); + setUp( + () => d.file("test.scss", """ + @use 'sass:math'; + a {b: math.random(1px)} + """).create(), + ); test("in immediate mode", () async { - var sass = await runSass( - ["--silence-deprecation=function-units", "test.scss"]); + var sass = await runSass([ + "--silence-deprecation=function-units", + "test.scss", + ]); expect(sass.stderr, emitsDone); await sass.shouldExit(0); }); @@ -147,24 +170,30 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--silence-deprecation=function-units", - "test.scss:out.css" + "test.scss:out.css", ]); expect(sass.stderr, emitsDone); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); }); test("in repl mode", () async { - var sass = await runSass( - ["--interactive", "--silence-deprecation=function-units"]); + var sass = await runSass([ + "--interactive", + "--silence-deprecation=function-units", + ]); expect(sass.stderr, emitsDone); sass.stdin.writeln("@use 'sass:math'"); await expectLater(sass.stdout, emits(">> @use 'sass:math'")); sass.stdin.writeln("math.random(1px)"); await expectLater( - sass.stdout, emitsInOrder([">> math.random(1px)", "1"])); + sass.stdout, + emitsInOrder([">> math.random(1px)", "1"]), + ); await sass.kill(); }); }); @@ -189,7 +218,7 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass([ "--fatal-deprecation=elseif", "--silence-deprecation=elseif", - "test.scss" + "test.scss", ]); expect(sass.stderr, emits(contains("Ignoring setting to silence"))); await sass.shouldExit(0); @@ -201,12 +230,14 @@ void sharedTests(Future runSass(Iterable arguments)) { "--poll", "--fatal-deprecation=elseif", "--silence-deprecation=elseif", - "test.scss:out.css" + "test.scss:out.css", ]); expect(sass.stderr, emits(contains("Ignoring setting to silence"))); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); }); @@ -214,10 +245,12 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass([ "--interactive", "--fatal-deprecation=elseif", - "--silence-deprecation=elseif" + "--silence-deprecation=elseif", ]); await expectLater( - sass.stderr, emits(contains("Ignoring setting to silence"))); + sass.stderr, + emits(contains("Ignoring setting to silence")), + ); await sass.kill(); }); }); @@ -227,10 +260,14 @@ void sharedTests(Future runSass(Iterable arguments)) { setUp(() => d.file("test.scss", "").create()); test("in immediate mode", () async { - var sass = - await runSass(["--fatal-deprecation=unknown", "test.scss"]); + var sass = await runSass([ + "--fatal-deprecation=unknown", + "test.scss", + ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); @@ -239,25 +276,32 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--fatal-deprecation=unknown", - "test.scss:out.css" + "test.scss:out.css", ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); test("in repl mode", () async { - var sass = - await runSass(["--interactive", "--fatal-deprecation=unknown"]); + var sass = await runSass([ + "--interactive", + "--fatal-deprecation=unknown", + ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); }); group("a parse-time deprecation", () { setUp( - () => d.file("test.scss", "@if true {} @elseif false {}").create()); + () => d.file("test.scss", "@if true {} @elseif false {}").create(), + ); test("in immediate mode", () async { var sass = await runSass(["--fatal-deprecation=elseif", "test.scss"]); @@ -270,27 +314,33 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--fatal-deprecation=elseif", - "test.scss:out.css" + "test.scss:out.css", ]); await expectLater(sass.stderr, emits(startsWith("Error: "))); await expectLater( - sass.stdout, - emitsInOrder( - ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + sass.stdout, + emitsInOrder([ + "Sass is watching for changes. Press Ctrl-C to stop.", + "", + ]), + ); await sass.kill(); }); test("in repl mode", () async { - var sass = await runSass( - ["--interactive", "--fatal-deprecation=strict-unary"]); + var sass = await runSass([ + "--interactive", + "--fatal-deprecation=strict-unary", + ]); sass.stdin.writeln("4 -(5)"); await expectLater( - sass.stdout, - emitsInOrder([ - ">> 4 -(5)", - emitsThrough(startsWith("Error: ")), - emitsThrough(contains("Remove this setting")) - ])); + sass.stdout, + emitsInOrder([ + ">> 4 -(5)", + emitsThrough(startsWith("Error: ")), + emitsThrough(contains("Remove this setting")), + ]), + ); // Verify that there's no output written for the previous line. sass.stdin.writeln("1"); @@ -300,14 +350,18 @@ void sharedTests(Future runSass(Iterable arguments)) { }); group("an evaluation-time deprecation", () { - setUp(() => d.file("test.scss", """ - @use 'sass:math'; - a {b: math.random(1px)} - """).create()); + setUp( + () => d.file("test.scss", """ + @use 'sass:math'; + a {b: math.random(1px)} + """).create(), + ); test("in immediate mode", () async { - var sass = await runSass( - ["--fatal-deprecation=function-units", "test.scss"]); + var sass = await runSass([ + "--fatal-deprecation=function-units", + "test.scss", + ]); expect(sass.stderr, emits(startsWith("Error: "))); await sass.shouldExit(65); }); @@ -317,29 +371,35 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--fatal-deprecation=function-units", - "test.scss:out.css" + "test.scss:out.css", ]); await expectLater(sass.stderr, emits(startsWith("Error: "))); await expectLater( - sass.stdout, - emitsInOrder( - ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + sass.stdout, + emitsInOrder([ + "Sass is watching for changes. Press Ctrl-C to stop.", + "", + ]), + ); await sass.kill(); }); test("in repl mode", () async { - var sass = await runSass( - ["--interactive", "--fatal-deprecation=function-units"]); + var sass = await runSass([ + "--interactive", + "--fatal-deprecation=function-units", + ]); sass.stdin.writeln("@use 'sass:math'"); await expectLater(sass.stdout, emits(">> @use 'sass:math'")); sass.stdin.writeln("math.random(1px)"); await expectLater( - sass.stdout, - emitsInOrder([ - ">> math.random(1px)", - emitsThrough(startsWith("Error: ")), - emitsThrough(contains("Remove this setting")) - ])); + sass.stdout, + emitsInOrder([ + ">> math.random(1px)", + emitsThrough(startsWith("Error: ")), + emitsThrough(contains("Remove this setting")), + ]), + ); // Verify that there's no output written for the previous line. sass.stdin.writeln("1"); @@ -356,10 +416,14 @@ void sharedTests(Future runSass(Iterable arguments)) { setUp(() => d.file("test.scss", "").create()); test("in immediate mode", () async { - var sass = await runSass( - ["--future-deprecation=function-units", "test.scss"]); - expect(sass.stderr, - emits(contains("function-units is not a future deprecation"))); + var sass = await runSass([ + "--future-deprecation=function-units", + "test.scss", + ]); + expect( + sass.stderr, + emits(contains("function-units is not a future deprecation")), + ); await sass.shouldExit(0); }); @@ -368,13 +432,17 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--future-deprecation=function-units", - "test.scss:out.css" + "test.scss:out.css", ]); - expect(sass.stderr, - emits(contains("function-units is not a future deprecation"))); + expect( + sass.stderr, + emits(contains("function-units is not a future deprecation")), + ); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); }); @@ -394,8 +462,10 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("in immediate mode", () async { - var sass = - await runSass(["--future-deprecation=import", "test.scss"]); + var sass = await runSass([ + "--future-deprecation=import", + "test.scss", + ]); expect(sass.stderr, emits(startsWith("DEPRECATION WARNING"))); await sass.shouldExit(0); }); @@ -405,11 +475,13 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--future-deprecation=import", - "test.scss:out.css" + "test.scss:out.css", ]); await expectLater( - sass.stderr, emits(startsWith("DEPRECATION WARNING"))); + sass.stderr, + emits(startsWith("DEPRECATION WARNING")), + ); await sass.kill(); }); @@ -428,10 +500,14 @@ void sharedTests(Future runSass(Iterable arguments)) { setUp(() => d.file("test.scss", "").create()); test("in immediate mode", () async { - var sass = - await runSass(["--future-deprecation=unknown", "test.scss"]); + var sass = await runSass([ + "--future-deprecation=unknown", + "test.scss", + ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); @@ -440,18 +516,24 @@ void sharedTests(Future runSass(Iterable arguments)) { "--watch", "--poll", "--future-deprecation=unknown", - "test.scss:out.css" + "test.scss:out.css", ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); test("in repl mode", () async { - var sass = - await runSass(["--interactive", "--future-deprecation=unknown"]); + var sass = await runSass([ + "--interactive", + "--future-deprecation=unknown", + ]); expect( - sass.stdout, emits(contains('Invalid deprecation "unknown".'))); + sass.stdout, + emits(contains('Invalid deprecation "unknown".')), + ); await sass.shouldExit(64); }); }); @@ -466,7 +548,7 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass([ "--fatal-deprecation=import", "--future-deprecation=import", - "test.scss" + "test.scss", ]); expect(sass.stderr, emits(startsWith("Error: "))); await sass.shouldExit(65); @@ -478,13 +560,16 @@ void sharedTests(Future runSass(Iterable arguments)) { "--poll", "--fatal-deprecation=import", "--future-deprecation=import", - "test.scss:out.css" + "test.scss:out.css", ]); await expectLater(sass.stderr, emits(startsWith("Error: "))); await expectLater( - sass.stdout, - emitsInOrder( - ["Sass is watching for changes. Press Ctrl-C to stop.", ""])); + sass.stdout, + emitsInOrder([ + "Sass is watching for changes. Press Ctrl-C to stop.", + "", + ]), + ); await sass.kill(); }); diff --git a/test/cli/shared/errors.dart b/test/cli/shared/errors.dart index 1524d8f20..3ab550ec4 100644 --- a/test/cli/shared/errors.dart +++ b/test/cli/shared/errors.dart @@ -11,21 +11,27 @@ void sharedTests(Future runSass(Iterable arguments)) { test("from invalid arguments", () async { var sass = await runSass(["--asdf"]); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); test("from too many positional arguments", () async { var sass = await runSass(["abc", "def", "ghi"]); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); test("from too many positional arguments with --stdin", () async { var sass = await runSass(["--stdin", "abc", "def"]); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -41,15 +47,16 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: Expected expression.", - " ,", - "1 | a {b: }", - " | ^", - " '", - " test.scss 1:7 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: Expected expression.", + " ,", + "1 | a {b: }", + " | ^", + " '", + " test.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -58,65 +65,68 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: 1px and 1deg have incompatible units.", - " ,", - "1 | a {b: 1px + 1deg}", - " | ^^^^^^^^^^", - " '", - " test.scss 1:7 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: 1px and 1deg have incompatible units.", + " ,", + "1 | a {b: 1px + 1deg}", + " | ^^^^^^^^^^", + " '", + " test.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); test("from an error encountered within a function", () async { await d.file("test.scss", """ -@function a() { - @error "Within A."; -} + @function a() { + @error "Within A."; + } -.b { - c: a(); -} -""").create(); + .b { + c: a(); + } + """).create(); var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: \"Within A.\"", - " ,", - "6 | c: a();", - " | ^^^", - " '", - " test.scss 6:6 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: \"Within A.\"", + " ,", + "6 | c: a();", + " | ^^^", + " '", + " test.scss 6:12 root stylesheet", + ]), + ); await sass.shouldExit(65); }); test("from an error encountered within a mixin", () async { await d.file("test.scss", """ -@mixin a() { - @error "Within A."; -} + @mixin a() { + @error "Within A."; + } -.b { - @include a(); -} -""").create(); + .b { + @include a(); + } + """).create(); var sass = await runSass(["--no-unicode", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: \"Within A.\"", - " ,", - "6 | @include a();", - " | ^^^^^^^^^^^^", - " '", - " test.scss 6:3 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: \"Within A.\"", + " ,", + "6 | @include a();", + " | ^^^^^^^^^^^^", + " '", + " test.scss 6:9 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -125,15 +135,16 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "--color", "test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: Expected expression.", - "\u001b[34m ,\u001b[0m", - "\u001b[34m1 |\u001b[0m a {b: \u001b[31m\u001b[0m}", - "\u001b[34m |\u001b[0m \u001b[31m ^\u001b[0m", - "\u001b[34m '\u001b[0m", - " test.scss 1:7 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: Expected expression.", + "\u001b[34m ,\u001b[0m", + "\u001b[34m1 |\u001b[0m a {b: \u001b[31m\u001b[0m}", + "\u001b[34m |\u001b[0m \u001b[31m ^\u001b[0m", + "\u001b[34m '\u001b[0m", + " test.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); @@ -142,15 +153,16 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["test.scss"]); expect( - sass.stderr, - emitsInOrder([ - "Error: Expected expression.", - " ╷", - "1 │ a {b: }", - " │ ^", - " ╵", - " test.scss 1:7 root stylesheet", - ])); + sass.stderr, + emitsInOrder([ + "Error: Expected expression.", + " ╷", + "1 │ a {b: }", + " │ ^", + " ╵", + " test.scss 1:7 root stylesheet", + ]), + ); await sass.shouldExit(65); }); diff --git a/test/cli/shared/repl.dart b/test/cli/shared/repl.dart index eeefe4f42..431c947e5 100644 --- a/test/cli/shared/repl.dart +++ b/test/cli/shared/repl.dart @@ -18,13 +18,15 @@ void sharedTests(Future runSass(Iterable arguments)) { '--embed-sources', '--embed-source-map', '--update', - '--watch' + '--watch', ]; for (var arg in invalidArgs) { test(arg, () async { var sass = await runSass(["--interactive", arg]); - expect(sass.stdout, - emitsThrough(contains("isn't allowed with --interactive"))); + expect( + sass.stdout, + emitsThrough(contains("isn't allowed with --interactive")), + ); sass.stdin.close(); await sass.shouldExit(64); }); @@ -155,7 +157,9 @@ void sharedTests(Future runSass(Iterable arguments)) { sass.stdin.writeln(r"other.$derived"); await expectLater( - sass.stdout, emitsInOrder([r">> other.$derived", "14"])); + sass.stdout, + emitsInOrder([r">> other.$derived", "14"]), + ); await sass.kill(); }); @@ -166,9 +170,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--interactive"]); sass.stdin.writeln("1 + 2;"); await expectLater( - sass.stdout, - emitsInOrder( - [">> 1 + 2;", " ^", "Error: expected no more input."])); + sass.stdout, + emitsInOrder([ + ">> 1 + 2;", + " ^", + "Error: expected no more input.", + ]), + ); await sass.kill(); }); @@ -176,20 +184,23 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--interactive"]); sass.stdin.writeln("\$foo: 1 + 2;"); await expectLater( - sass.stdout, - emitsInOrder([ - ">> \$foo: 1 + 2;", - " ^", - "Error: expected no more input." - ])); + sass.stdout, + emitsInOrder([ + ">> \$foo: 1 + 2;", + " ^", + "Error: expected no more input.", + ]), + ); await sass.kill(); }); test("a parse error after the end of the input", () async { var sass = await runSass(["--interactive"]); sass.stdin.writeln("foo("); - await expectLater(sass.stdout, - emitsInOrder([">> foo(", " ^", 'Error: expected ")".'])); + await expectLater( + sass.stdout, + emitsInOrder([">> foo(", " ^", 'Error: expected ")".']), + ); await sass.kill(); }); @@ -198,13 +209,14 @@ void sharedTests(Future runSass(Iterable arguments)) { sass.stdin.writeln("@use 'sass:math'"); sass.stdin.writeln("math.max(2, 1 + blue)"); await expectLater( - sass.stdout, - emitsInOrder([ - ">> @use 'sass:math'", - ">> math.max(2, 1 + blue)", - " ^^^^^^^^", - 'Error: Undefined operation "1 + blue".' - ])); + sass.stdout, + emitsInOrder([ + ">> @use 'sass:math'", + ">> math.max(2, 1 + blue)", + " ^^^^^^^^", + 'Error: Undefined operation "1 + blue".', + ]), + ); await sass.kill(); }); @@ -212,9 +224,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--interactive"]); sass.stdin.writeln(r"1 + $x + 3"); await expectLater( - sass.stdout, - emitsInOrder( - [r">> 1 + $x + 3", " ^^", "Error: Undefined variable."])); + sass.stdout, + emitsInOrder([ + r">> 1 + $x + 3", + " ^^", + "Error: Undefined variable.", + ]), + ); await sass.kill(); }); @@ -223,15 +239,16 @@ void sharedTests(Future runSass(Iterable arguments)) { sass.stdin.writeln("call('max', 1, 2) + blue"); await expectLater(sass.stderr, emits(contains("DEPRECATION WARNING"))); await expectLater( - sass.stdout, - emitsInOrder([ - ">> call('max', 1, 2) + blue", - 'Error: Undefined operation "2 + blue".', - " ,", - "1 | call('max', 1, 2) + blue", - " | ^^^^^^^^^^^^^^^^^^^^^^^^", - " '" - ])); + sass.stdout, + emitsInOrder([ + ">> call('max', 1, 2) + blue", + 'Error: Undefined operation "2 + blue".', + " ,", + "1 | call('max', 1, 2) + blue", + " | ^^^^^^^^^^^^^^^^^^^^^^^^", + " '", + ]), + ); await sass.kill(); }); @@ -240,12 +257,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "--interactive"]); sass.stdin.writeln('@use "non-existent"'); await expectLater( - sass.stdout, - emitsInOrder([ - '>> @use "non-existent"', - " ^^^^^^^^^^^^^^^^^^^", - "Error: Can't find stylesheet to import." - ])); + sass.stdout, + emitsInOrder([ + '>> @use "non-existent"', + " ^^^^^^^^^^^^^^^^^^^", + "Error: Can't find stylesheet to import.", + ]), + ); await sass.kill(); }); @@ -253,12 +271,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "--interactive"]); sass.stdin.writeln('@use "other" as'); await expectLater( - sass.stdout, - emitsInOrder([ - '>> @use "other" as', - " ^", - "Error: Expected identifier." - ])); + sass.stdout, + emitsInOrder([ + '>> @use "other" as', + " ^", + "Error: Expected identifier.", + ]), + ); await sass.kill(); }); @@ -268,15 +287,16 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "--interactive"]); sass.stdin.writeln('@use "other"'); await expectLater( - sass.stdout, - emitsInOrder([ - '>> @use "other"', - "Error: Expected expression.", - " ,", - r"1 | $var: 1px +", - " | ^", - " '" - ])); + sass.stdout, + emitsInOrder([ + '>> @use "other"', + "Error: Expected expression.", + " ,", + r"1 | $var: 1px +", + " | ^", + " '", + ]), + ); await sass.kill(); }); @@ -286,15 +306,16 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--no-unicode", "--interactive"]); sass.stdin.writeln('@use "other"'); await expectLater( - sass.stdout, - emitsInOrder([ - '>> @use "other"', - "Error: 1px and 1s have incompatible units.", - " ,", - r"1 | $var: 1px + 1s;", - " | ^^^^^^^^", - " '" - ])); + sass.stdout, + emitsInOrder([ + '>> @use "other"', + "Error: 1px and 1s have incompatible units.", + " ,", + r"1 | $var: 1px + 1s;", + " | ^^^^^^^^", + " '", + ]), + ); await sass.kill(); }); }); @@ -305,14 +326,15 @@ void sharedTests(Future runSass(Iterable arguments)) { sass.stdin.writeln("@use 'sass:math'"); sass.stdin.writeln("math.max(2, 1 + blue)"); await expectLater( - sass.stdout, - emitsInOrder([ - ">> @use 'sass:math'", - ">> math.max(2, 1 + blue)", - "\u001b[31m\u001b[1F\u001b[15C1 + blue", - " ^^^^^^^^", - '\u001b[0mError: Undefined operation "1 + blue".' - ])); + sass.stdout, + emitsInOrder([ + ">> @use 'sass:math'", + ">> math.max(2, 1 + blue)", + "\u001b[31m\u001b[1F\u001b[15C1 + blue", + " ^^^^^^^^", + '\u001b[0mError: Undefined operation "1 + blue".', + ]), + ); await sass.kill(); }); @@ -320,12 +342,13 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await runSass(["--interactive", "--color"]); sass.stdin.writeln("foo("); await expectLater( - sass.stdout, - emitsInOrder([ - ">> foo(", - "\u001b[31m ^", - '\u001b[0mError: expected ")".' - ])); + sass.stdout, + emitsInOrder([ + ">> foo(", + "\u001b[31m ^", + '\u001b[0mError: expected ")".', + ]), + ); await sass.kill(); }); }); diff --git a/test/cli/shared/source_maps.dart b/test/cli/shared/source_maps.dart index a2d0ad2e4..d6f22a9b0 100644 --- a/test/cli/shared/source_maps.dart +++ b/test/cli/shared/source_maps.dart @@ -35,55 +35,79 @@ void sharedTests(Future runSass(Iterable arguments)) { test("contains mappings", () { var result = sass.compileStringToResult("a {b: 1 + 2}", sourceMap: true); - expect(map, - containsPair("mappings", result.sourceMap!.toJson()["mappings"])); + expect( + map, + containsPair("mappings", result.sourceMap!.toJson()["mappings"]), + ); }); }); group("with multiple sources", () { setUp(() async { await d.file("test.scss", """ - @use 'dir/other'; - x {y: z} - """).create(); + @use 'dir/other'; + x {y: z} + """).create(); await d.dir("dir", [d.file("other.scss", "a {b: 1 + 2}")]).create(); }); test("refers to them using relative URLs by default", () async { await (await runSass(["test.scss", "out.css"])).shouldExit(0); - expect(_readJson("out.css.map"), - containsPair("sources", ["dir/other.scss", "test.scss"])); + expect( + _readJson("out.css.map"), + containsPair("sources", ["dir/other.scss", "test.scss"]), + ); }); - test("refers to them using relative URLs with --source-map-urls=relative", - () async { - await (await runSass( - ["--source-map-urls=relative", "test.scss", "out.css"])) - .shouldExit(0); - expect(_readJson("out.css.map"), - containsPair("sources", ["dir/other.scss", "test.scss"])); - }); + test( + "refers to them using relative URLs with --source-map-urls=relative", + () async { + await (await runSass([ + "--source-map-urls=relative", + "test.scss", + "out.css", + ])) + .shouldExit(0); + expect( + _readJson("out.css.map"), + containsPair("sources", ["dir/other.scss", "test.scss"]), + ); + }, + ); - test("refers to them using absolute URLs with --source-map-urls=absolute", - () async { - await (await runSass( - ["--source-map-urls=absolute", "test.scss", "out.css"])) - .shouldExit(0); - expect( + test( + "refers to them using absolute URLs with --source-map-urls=absolute", + () async { + await (await runSass([ + "--source-map-urls=absolute", + "test.scss", + "out.css", + ])) + .shouldExit(0); + expect( _readJson("out.css.map"), containsPair("sources", [ p.toUri(canonicalize(d.path("dir/other.scss"))).toString(), - p.toUri(canonicalize(d.path("test.scss"))).toString() - ])); - }); + p.toUri(canonicalize(d.path("test.scss"))).toString(), + ]), + ); + }, + ); test("includes source contents with --embed-sources", () async { - await (await runSass(["--embed-sources", "test.scss", "out.css"])) + await (await runSass([ + "--embed-sources", + "test.scss", + "out.css", + ])) .shouldExit(0); expect( - _readJson("out.css.map"), - containsPair("sourcesContent", - ["a {b: 1 + 2}", readFile(d.path("test.scss"))])); + _readJson("out.css.map"), + containsPair("sourcesContent", [ + "a {b: 1 + 2}", + readFile(d.path("test.scss")), + ]), + ); }); }); @@ -130,14 +154,18 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("refers to a source", () { - expect(_readJson("out/test.css.map"), - containsPair("sources", ["../in/test.scss"])); + expect( + _readJson("out/test.css.map"), + containsPair("sources", ["../in/test.scss"]), + ); }); test("includes a source map comment", () async { await d - .file("out/test.css", - endsWith("\n\n/*# sourceMappingURL=test.css.map */\n")) + .file( + "out/test.css", + endsWith("\n\n/*# sourceMappingURL=test.css.map */\n"), + ) .validate(); }); }); @@ -149,9 +177,11 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(0); expect( - _readJson("out.css.map"), - containsPair("sources", - [Uri.dataFromString("a {b: c}\n", encoding: utf8).toString()])); + _readJson("out.css.map"), + containsPair("sources", [ + Uri.dataFromString("a {b: c}\n", encoding: utf8).toString(), + ]), + ); }); group("with --no-source-map,", () { @@ -160,7 +190,11 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("no source map is generated", () async { - await (await runSass(["--no-source-map", "test.scss", "out.css"])) + await (await runSass([ + "--no-source-map", + "test.scss", + "out.css", + ])) .shouldExit(0); await d.file("out.css", isNot(contains("/*#"))).validate(); @@ -172,32 +206,52 @@ void sharedTests(Future runSass(Iterable arguments)) { "--no-source-map", "--source-map-urls=absolute", "test.scss", - "out.css" + "out.css", ]); - expect(sass.stdout, - emits("--source-map-urls isn't allowed with --no-source-map.")); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emits("--source-map-urls isn't allowed with --no-source-map."), + ); + expect( + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); test("--embed-sources is disallowed", () async { - var sass = await runSass( - ["--no-source-map", "--embed-sources", "test.scss", "out.css"]); - expect(sass.stdout, - emits("--embed-sources isn't allowed with --no-source-map.")); + var sass = await runSass([ + "--no-source-map", + "--embed-sources", + "test.scss", + "out.css", + ]); + expect( + sass.stdout, + emits("--embed-sources isn't allowed with --no-source-map."), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); test("--embed-source-map is disallowed", () async { - var sass = await runSass( - ["--no-source-map", "--embed-source-map", "test.scss", "out.css"]); - expect(sass.stdout, - emits("--embed-source-map isn't allowed with --no-source-map.")); + var sass = await runSass([ + "--no-source-map", + "--embed-source-map", + "test.scss", + "out.css", + ]); + expect( + sass.stdout, + emits("--embed-source-map isn't allowed with --no-source-map."), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); }); @@ -207,11 +261,16 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "a {b: c}").create(); var sass = await runSass(["--source-map", "test.scss"]); expect( - sass.stdout, - emits("When printing to stdout, --source-map requires " - "--embed-source-map.")); + sass.stdout, + emits( + "When printing to stdout, --source-map requires " + "--embed-source-map.", + ), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -219,11 +278,16 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "a {b: c}").create(); var sass = await runSass(["--source-map-urls=absolute", "test.scss"]); expect( - sass.stdout, - emits("When printing to stdout, --source-map-urls requires " - "--embed-source-map.")); + sass.stdout, + emits( + "When printing to stdout, --source-map-urls requires " + "--embed-source-map.", + ), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -231,11 +295,16 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "a {b: c}").create(); var sass = await runSass(["--embed-sources", "test.scss"]); expect( - sass.stdout, - emits("When printing to stdout, --embed-sources requires " - "--embed-source-map.")); + sass.stdout, + emits( + "When printing to stdout, --embed-sources requires " + "--embed-source-map.", + ), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -243,14 +312,22 @@ void sharedTests(Future runSass(Iterable arguments)) { "--source-map-urls=relative is disallowed even with " "--embed-source-map", () async { await d.file("test.scss", "a {b: c}").create(); - var sass = await runSass( - ["--source-map-urls=relative", "--embed-source-map", "test.scss"]); + var sass = await runSass([ + "--source-map-urls=relative", + "--embed-source-map", + "test.scss", + ]); expect( - sass.stdout, - emits("--source-map-urls=relative isn't allowed when printing to " - "stdout.")); + sass.stdout, + emits( + "--source-map-urls=relative isn't allowed when printing to " + "stdout.", + ), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); @@ -261,7 +338,7 @@ void sharedTests(Future runSass(Iterable arguments)) { "--source-map-urls=absolute", "--embed-sources", "--embed-source-map", - "test.scss" + "test.scss", ]); var css = (await sass.stdout.rest.toList()).join("\n"); await sass.shouldExit(0); @@ -280,17 +357,25 @@ void sharedTests(Future runSass(Iterable arguments)) { Map? map; group("with the target in the same directory", () { setUp(() async { - await (await runSass(["--embed-source-map", "test.scss", "out.css"])) + await (await runSass([ + "--embed-source-map", + "test.scss", + "out.css", + ])) .shouldExit(0); var css = readFile(d.path("out.css")); map = embeddedSourceMap(css); }); test("contains mappings in the generated CSS", () { - var result = - sass.compileStringToResult("a {b: 1 + 2}", sourceMap: true); - expect(map, - containsPair("mappings", result.sourceMap!.toJson()["mappings"])); + var result = sass.compileStringToResult( + "a {b: 1 + 2}", + sourceMap: true, + ); + expect( + map, + containsPair("mappings", result.sourceMap!.toJson()["mappings"]), + ); }); test("refers to the source file", () { @@ -312,7 +397,7 @@ void sharedTests(Future runSass(Iterable arguments)) { "--embed-source-map", "--embed-sources", "test.scss", - "out.css" + "out.css", ])) .shouldExit(0); var css = readFile(d.path("out.css")); @@ -326,8 +411,11 @@ void sharedTests(Future runSass(Iterable arguments)) { group("with the target in a different directory", () { setUp(() async { ensureDir(d.path("dir")); - await (await runSass( - ["--embed-source-map", "test.scss", "dir/out.css"])) + await (await runSass([ + "--embed-source-map", + "test.scss", + "dir/out.css", + ])) .shouldExit(0); var css = readFile(d.path("dir/out.css")); map = embeddedSourceMap(css); @@ -345,14 +433,15 @@ void sharedTests(Future runSass(Iterable arguments)) { test("with stdout as the target", () async { var sass = await runSass(["--embed-source-map", "test.scss"]); expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - "", - startsWith("/*# sourceMappingURL=data:") - ])); + sass.stdout, + emitsInOrder([ + "a {", + " b: 3;", + "}", + "", + startsWith("/*# sourceMappingURL=data:"), + ]), + ); await sass.shouldExit(0); }); }); diff --git a/test/cli/shared/update.dart b/test/cli/shared/update.dart index 260c1d132..a12fe6831 100644 --- a/test/cli/shared/update.dart +++ b/test/cli/shared/update.dart @@ -68,11 +68,12 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await update(["test1.scss:out1.css", "test2.scss:out2.css"]); expect( - sass.stdout, - emitsInAnyOrder([ - endsWith('Compiled test1.scss to out1.css.'), - endsWith('Compiled test2.scss to out2.css.') - ])); + sass.stdout, + emitsInAnyOrder([ + endsWith('Compiled test1.scss to out1.css.'), + endsWith('Compiled test2.scss to out2.css.'), + ]), + ); await sass.shouldExit(0); await d @@ -87,11 +88,12 @@ void sharedTests(Future runSass(Iterable arguments)) { sass = await update(["test1.scss:out1.css", "test2.scss:out2.css"]); expect( - sass.stdout, - emitsInAnyOrder([ - endsWith('Compiled test1.scss to out1.css.'), - endsWith('Compiled test2.scss to out2.css.') - ])); + sass.stdout, + emitsInAnyOrder([ + endsWith('Compiled test1.scss to out1.css.'), + endsWith('Compiled test2.scss to out2.css.'), + ]), + ); await sass.shouldExit(0); await d @@ -273,20 +275,22 @@ void sharedTests(Future runSass(Iterable arguments)) { }); }); - test("deletes a CSS file when a file has an error with --no-error-css", - () async { - await d.file("test.scss", "a {b: c}").create(); - await (await update(["test.scss:out.css"])).shouldExit(0); - await d.file("out.css", anything).validate(); + test( + "deletes a CSS file when a file has an error with --no-error-css", + () async { + await d.file("test.scss", "a {b: c}").create(); + await (await update(["test.scss:out.css"])).shouldExit(0); + await d.file("out.css", anything).validate(); - await d.file("test.scss", "a {b: }").create(); - var sass = await update(["--no-error-css", "test.scss:out.css"]); - expect(sass.stderr, emits("Error: Expected expression.")); - expect(sass.stderr, emitsThrough(contains("test.scss 1:7"))); - await sass.shouldExit(65); + await d.file("test.scss", "a {b: }").create(); + var sass = await update(["--no-error-css", "test.scss:out.css"]); + expect(sass.stderr, emits("Error: Expected expression.")); + expect(sass.stderr, emitsThrough(contains("test.scss 1:7"))); + await sass.shouldExit(65); - await d.nothing("out.css").validate(); - }); + await d.nothing("out.css").validate(); + }, + ); group("doesn't allow", () { test("--stdin", () async { @@ -297,8 +301,10 @@ void sharedTests(Future runSass(Iterable arguments)) { test("printing to stderr", () async { var sass = await update(["test.scss"]); - expect(sass.stdout, - emits('--update is not allowed when printing to stdout.')); + expect( + sass.stdout, + emits('--update is not allowed when printing to stdout.'), + ); await sass.shouldExit(64); }); }); diff --git a/test/cli/shared/watch.dart b/test/cli/shared/watch.dart index f2bfc6d12..0958fd4f8 100644 --- a/test/cli/shared/watch.dart +++ b/test/cli/shared/watch.dart @@ -15,15 +15,23 @@ void sharedTests(Future runSass(Iterable arguments)) { test("--poll may not be passed without --watch", () async { var sass = await runSass(["--poll", "-"]); await expectLater( - sass.stdout, emits("--poll may not be passed without --watch.")); + sass.stdout, + emits("--poll may not be passed without --watch."), + ); expect( - sass.stdout, emitsThrough(contains("Print this usage information."))); + sass.stdout, + emitsThrough(contains("Print this usage information.")), + ); await sass.shouldExit(64); }); for (var poll in [true, false]) { - Future watch(Iterable arguments) => runSass( - ["--no-source-map", "--watch", ...arguments, if (poll) "--poll"]); + Future watch(Iterable arguments) => runSass([ + "--no-source-map", + "--watch", + ...arguments, + if (poll) "--poll", + ]); /// Returns a future that completes after a delay if [poll] is `true`. /// @@ -41,7 +49,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await sass.kill(); @@ -67,13 +77,19 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test2.scss", "x {y: z}").create(); var message = 'Error: Expected expression.'; - var sass = - await watch(["test1.scss:out1.css", "test2.scss:out2.css"]); + var sass = await watch([ + "test1.scss:out1.css", + "test2.scss:out2.css", + ]); await expectLater(sass.stderr, emits(message)); await expectLater( - sass.stderr, emitsThrough(contains('test1.scss 1:7'))); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test2.scss to out2.css.'))); + sass.stderr, + emitsThrough(contains('test1.scss 1:7')), + ); + await expectLater( + sass.stdout, + emitsThrough(endsWith('Compiled test2.scss to out2.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await sass.kill(); @@ -90,14 +106,20 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch([ "--no-error-css", "test1.scss:out1.css", - "test2.scss:out2.css" + "test2.scss:out2.css", ]); await expectLater( - sass.stderr, emits('Error: Expected expression.')); + sass.stderr, + emits('Error: Expected expression.'), + ); + await expectLater( + sass.stderr, + emitsThrough(contains('test1.scss 1:7')), + ); await expectLater( - sass.stderr, emitsThrough(contains('test1.scss 1:7'))); - await expectLater(sass.stdout, - emitsThrough(endsWith('Compiled test2.scss to out2.css.'))); + sass.stdout, + emitsThrough(endsWith('Compiled test2.scss to out2.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await sass.kill(); @@ -115,17 +137,18 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch([ "--stop-on-error", "test1.scss:out1.css", - "test2.scss:out2.css" + "test2.scss:out2.css", ]); var message = 'Error: Expected expression.'; await expectLater( - sass.stderr, - emitsInOrder([ - message, - emitsThrough(contains('test1.scss 1:7')), - emitsDone - ])); + sass.stderr, + emitsInOrder([ + message, + emitsThrough(contains('test1.scss 1:7')), + emitsDone, + ]), + ); await sass.shouldExit(65); await d.file("out1.css", contains(message)).validate(); @@ -138,13 +161,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("test.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -156,18 +183,22 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test.scss", "a {b: c}")]).create(); var sass = await watch(["dir:out"]); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test.scss', 'out/test.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test.scss', 'out/test.css'))), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.dir("dir", [d.file("test.scss", "x {y: z}")]).create(); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test.scss', 'out/test.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test.scss', 'out/test.css'))), + ); await sass.kill(); await d.dir("out", [ - d.file("test.css", equalsIgnoringWhitespace("x { y: z; }")) + d.file("test.css", equalsIgnoringWhitespace("x { y: z; }")), ]).validate(); }); @@ -178,13 +209,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["."]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to test.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to test.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("test.scss", "r {o: g}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to test.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to test.css.')), + ); // When using the native Node.js watcher on Linux, the "modify" event // from writing test.css can interfere with the "modify" event from @@ -194,7 +229,9 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to test.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to test.css.')), + ); await sass.kill(); @@ -208,16 +245,23 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("_other.scss", "a {b: c}").create(); await d.file("test.scss", "@import 'other'").create(); - var sass = await watch( - ["--silence-deprecation", "import", "test.scss:out.css"]); + var sass = await watch([ + "--silence-deprecation", + "import", + "test.scss:out.css", + ]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -231,13 +275,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -251,13 +299,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -274,14 +326,18 @@ void sharedTests(Future runSass(Iterable arguments)) { """).create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -297,14 +353,18 @@ void sharedTests(Future runSass(Iterable arguments)) { """).create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -320,14 +380,18 @@ void sharedTests(Future runSass(Iterable arguments)) { """).create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -343,14 +407,18 @@ void sharedTests(Future runSass(Iterable arguments)) { """).create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -366,20 +434,28 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "a {b: }").create(); await expectLater( - sass.stderr, emits('Error: Expected expression.')); + sass.stderr, + emits('Error: Expected expression.'), + ); await expectLater( - sass.stderr, emitsThrough(contains('test.scss 1:1'))); + sass.stderr, + emitsThrough(contains('test.scss 1:1')), + ); await tickIfPoll(); await d.file("_other.scss", "q {r: s}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await tick; await d .file("out.css", equalsIgnoringWhitespace("q { r: s; }")) @@ -387,7 +463,9 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("_other.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await tick; await d .file("out.css", equalsIgnoringWhitespace("x { y: z; }")) @@ -402,7 +480,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); @@ -414,7 +494,9 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -427,7 +509,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); @@ -435,7 +519,9 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "a {b: }").create(); await expectLater(sass.stderr, emits(message)); await expectLater( - sass.stderr, emitsThrough(contains('test.scss 1:7'))); + sass.stderr, + emitsThrough(contains('test.scss 1:7')), + ); await sass.kill(); await d.file("out.css", contains(message)).validate(); @@ -446,19 +532,22 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["--stop-on-error", "test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); var message = 'Error: Expected expression.'; await d.file("test.scss", "a {b: }").create(); await expectLater( - sass.stderr, - emitsInOrder([ - message, - emitsThrough(contains('test.scss 1:7')), - emitsDone - ])); + sass.stderr, + emitsInOrder([ + message, + emitsThrough(contains('test.scss 1:7')), + emitsDone, + ]), + ); await sass.shouldExit(65); await d.file("out.css", contains(message)).validate(); @@ -471,7 +560,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); @@ -479,7 +570,9 @@ void sharedTests(Future runSass(Iterable arguments)) { d.file("_other.scss").io.deleteSync(); await expectLater(sass.stderr, emits(message)); await expectLater( - sass.stderr, emitsThrough(contains('test.scss 1:1'))); + sass.stderr, + emitsThrough(contains('test.scss 1:1')), + ); await sass.kill(); await d.file("out.css", contains(message)).validate(); @@ -492,13 +585,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["-I", "dir", "test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); d.file("_other.scss").io.deleteSync(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -512,14 +609,18 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "@use 'other'").create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stderr, - emits("Error: It's not clear which file to import. Found:")); + await expectLater( + sass.stderr, + emits("Error: It's not clear which file to import. Found:"), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); d.file("_other.sass").io.deleteSync(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -534,16 +635,22 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.file("test.scss", "@use 'other'").create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stderr, - emits("Error: Can't find stylesheet to import.")); await expectLater( - sass.stderr, emitsThrough(contains("test.scss 1:1"))); + sass.stderr, + emits("Error: Can't find stylesheet to import."), + ); + await expectLater( + sass.stderr, + emitsThrough(contains("test.scss 1:1")), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "a {b: c}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -556,16 +663,22 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir").create(); var sass = await watch(["-I", "dir", "test.scss:out.css"]); - await expectLater(sass.stderr, - emits("Error: Can't find stylesheet to import.")); await expectLater( - sass.stderr, emitsThrough(contains("test.scss 1:1"))); + sass.stderr, + emits("Error: Can't find stylesheet to import."), + ); + await expectLater( + sass.stderr, + emitsThrough(contains("test.scss 1:1")), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.dir("dir", [d.file("_other.scss", "a {b: c}")]).create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -574,20 +687,27 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("on a load path that was created", () async { - await d - .dir("dir1", [d.file("test.scss", "@use 'other'")]).create(); + await d.dir("dir1", [ + d.file("test.scss", "@use 'other'"), + ]).create(); var sass = await watch(["-I", "dir2", "dir1:out"]); - await expectLater(sass.stderr, - emits("Error: Can't find stylesheet to import.")); - await expectLater(sass.stderr, - emitsThrough(contains("${p.join('dir1', 'test.scss')} 1:1"))); + await expectLater( + sass.stderr, + emits("Error: Can't find stylesheet to import."), + ); + await expectLater( + sass.stderr, + emitsThrough(contains("${p.join('dir1', 'test.scss')} 1:1")), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.dir("dir2", [d.file("_other.scss", "a {b: c}")]).create(); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir1/test.scss', 'out/test.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir1/test.scss', 'out/test.css'))), + ); await sass.kill(); await d @@ -602,7 +722,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); @@ -620,16 +742,25 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir2", [d.file("_other.scss", "a {b: c}")]).create(); await d.dir("dir1").create(); - var sass = await watch( - ["-I", "dir1", "-I", "dir2", "test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + var sass = await watch([ + "-I", + "dir1", + "-I", + "dir2", + "test.scss:out.css", + ]); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.dir("dir1", [d.file("_other.scss", "x {y: z}")]).create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -642,14 +773,18 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("_other.scss", "a {b: c}")]).create(); var sass = await watch(["-I", "dir", "test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -659,18 +794,23 @@ void sharedTests(Future runSass(Iterable arguments)) { test("because it's not an index", () async { await d.file("test.scss", "@use 'other'").create(); - await d - .dir("other", [d.file("_index.scss", "a {b: c}")]).create(); + await d.dir("other", [ + d.file("_index.scss", "a {b: c}"), + ]).create(); var sass = await watch(["test.scss:out.css"]); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("_other.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith('Compiled test.scss to out.css.'))); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await sass.kill(); await d @@ -688,18 +828,25 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test.scss", "a {b: }")]).create(); await expectLater( - sass.stderr, emits('Error: Expected expression.')); + sass.stderr, + emits('Error: Expected expression.'), + ); await tickIfPoll(); await d.dir("dir", [d.file("test.scss", "a {b: c}")]).create(); await expectLater( - sass.stdout, - emits(endsWith('Compiled ${p.join('dir', 'test.scss')} to ' - '${p.join('out', 'test.css')}.'))); + sass.stdout, + emits( + endsWith( + 'Compiled ${p.join('dir', 'test.scss')} to ' + '${p.join('out', 'test.css')}.', + ), + ), + ); await sass.kill(); await d.dir("out", [ - d.file("test.css", equalsIgnoringWhitespace("a { b: c; }")) + d.file("test.css", equalsIgnoringWhitespace("a { b: c; }")), ]).validate(); }); }); @@ -710,13 +857,17 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.css:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.css to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.css to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.file("test.css", "x {y: z}").create(); await expectLater( - sass.stdout, emits(endsWith('Compiled test.css to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.css to out.css.')), + ); await sass.kill(); await d @@ -729,53 +880,61 @@ void sharedTests(Future runSass(Iterable arguments)) { test("when an unrelated file is modified", () async { await d.dir("dir", [ d.file("test1.scss", "a {b: c}"), - d.file("test2.scss", "a {b: c}") + d.file("test2.scss", "a {b: c}"), ]).create(); var sass = await watch(["dir:out"]); await expectLater( - sass.stdout, - emitsInAnyOrder([ - endsWith(_compiled('dir/test1.scss', 'out/test1.css')), - endsWith(_compiled('dir/test2.scss', 'out/test2.css')) - ])); + sass.stdout, + emitsInAnyOrder([ + endsWith(_compiled('dir/test1.scss', 'out/test1.css')), + endsWith(_compiled('dir/test2.scss', 'out/test2.css')), + ]), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); await d.dir("dir", [d.file("test2.scss", "x {y: z}")]).create(); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test2.scss', 'out/test2.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test2.scss', 'out/test2.css'))), + ); expect( - sass.stdout, - neverEmits( - endsWith(_compiled('dir/test1.scss', 'out/test1.css')))); + sass.stdout, + neverEmits(endsWith(_compiled('dir/test1.scss', 'out/test1.css'))), + ); await tick; await sass.kill(); }); test( - "when a potential dependency that's not actually imported is added", - () async { - await d.file("test.scss", "@use 'other'").create(); - await d.file("_other.scss", "a {b: c}").create(); - await d.dir("dir").create(); + "when a potential dependency that's not actually imported is added", + () async { + await d.file("test.scss", "@use 'other'").create(); + await d.file("_other.scss", "a {b: c}").create(); + await d.dir("dir").create(); - var sass = await watch(["-I", "dir", "test.scss:out.css"]); - await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); - await expectLater(sass.stdout, _watchingForChanges); - await tickIfPoll(); + var sass = await watch(["-I", "dir", "test.scss:out.css"]); + await expectLater( + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); + await expectLater(sass.stdout, _watchingForChanges); + await tickIfPoll(); - await d.dir("dir", [d.file("_other.scss", "a {b: c}")]).create(); - expect(sass.stdout, - neverEmits(endsWith('Compiled test.scss to out.css.'))); - await tick; - await sass.kill(); + await d.dir("dir", [d.file("_other.scss", "a {b: c}")]).create(); + expect( + sass.stdout, + neverEmits(endsWith('Compiled test.scss to out.css.')), + ); + await tick; + await sass.kill(); - await d - .file("out.css", equalsIgnoringWhitespace("a { b: c; }")) - .validate(); - }); + await d + .file("out.css", equalsIgnoringWhitespace("a { b: c; }")) + .validate(); + }, + ); }); group("deletes the CSS", () { @@ -784,7 +943,9 @@ void sharedTests(Future runSass(Iterable arguments)) { var sass = await watch(["test.scss:out.css"]); await expectLater( - sass.stdout, emits(endsWith('Compiled test.scss to out.css.'))); + sass.stdout, + emits(endsWith('Compiled test.scss to out.css.')), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); @@ -799,14 +960,18 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test.scss", "a {b: c}")]).create(); var sass = await watch(["dir:out"]); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test.scss', 'out/test.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test.scss', 'out/test.css'))), + ); await expectLater(sass.stdout, _watchingForChanges); await tickIfPoll(); d.file("dir/test.scss").io.deleteSync(); await expectLater( - sass.stdout, emits('Deleted ${p.join('out', 'test.css')}.')); + sass.stdout, + emits('Deleted ${p.join('out', 'test.css')}.'), + ); await sass.kill(); await d.dir("dir", [d.nothing("out.css")]).validate(); @@ -821,12 +986,14 @@ void sharedTests(Future runSass(Iterable arguments)) { await tickIfPoll(); await d.dir("dir", [d.file("test.scss", "a {b: c}")]).create(); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test.scss', 'out/test.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test.scss', 'out/test.css'))), + ); await sass.kill(); await d.dir("out", [ - d.file("test.css", equalsIgnoringWhitespace("a { b: c; }")) + d.file("test.css", equalsIgnoringWhitespace("a { b: c; }")), ]).validate(); }); @@ -838,8 +1005,10 @@ void sharedTests(Future runSass(Iterable arguments)) { await tickIfPoll(); await d.dir("dir", [d.file("_test.scss", "a {b: c}")]).create(); - expect(sass.stdout, - neverEmits(endsWith(_compiled('dir/test.scss', 'out/test.css')))); + expect( + sass.stdout, + neverEmits(endsWith(_compiled('dir/test.scss', 'out/test.css'))), + ); await tick; await sass.kill(); @@ -861,8 +1030,10 @@ void sharedTests(Future runSass(Iterable arguments)) { // did incorrectly trigger a compilation, it would emit a message // before the message for this change. await d.file("dir/test2.scss", "x {y: z}").create(); - await expectLater(sass.stdout, - emits(endsWith(_compiled('dir/test2.scss', 'dir/test2.css')))); + await expectLater( + sass.stdout, + emits(endsWith(_compiled('dir/test2.scss', 'dir/test2.css'))), + ); await sass.kill(); @@ -878,8 +1049,10 @@ void sharedTests(Future runSass(Iterable arguments)) { test("printing to stderr", () async { var sass = await watch(["test.scss"]); - expect(sass.stdout, - emits('--watch is not allowed when printing to stdout.')); + expect( + sass.stdout, + emits('--watch is not allowed when printing to stdout.'), + ); await sass.shouldExit(64); }); }); @@ -893,5 +1066,7 @@ String _compiled(String from, String to) => 'Compiled ${p.normalize(from)} to ${p.normalize(to)}.'; /// Matches the output that indicates that Sass is watching for changes. -final _watchingForChanges = - emitsInOrder(["Sass is watching for changes. Press Ctrl-C to stop.", ""]); +final _watchingForChanges = emitsInOrder([ + "Sass is watching for changes. Press Ctrl-C to stop.", + "", +]); diff --git a/test/compressed_test.dart b/test/compressed_test.dart index e3368dcaa..a8dc25c28 100644 --- a/test/compressed_test.dart +++ b/test/compressed_test.dart @@ -36,13 +36,17 @@ void main() { group("in prefixed pseudos", () { test("preserves whitespace", () { - expect(_compile("a:nth-child(2n of b) {x: y}"), - equals("a:nth-child(2n of b){x:y}")); + expect( + _compile("a:nth-child(2n of b) {x: y}"), + equals("a:nth-child(2n of b){x:y}"), + ); }); test("removes whitespace after commas", () { - expect(_compile("a:nth-child(2n of b, c) {x: y}"), - equals("a:nth-child(2n of b,c){x:y}")); + expect( + _compile("a:nth-child(2n of b, c) {x: y}"), + equals("a:nth-child(2n of b,c){x:y}"), + ); }); }); @@ -64,36 +68,45 @@ void main() { group("of custom properties", () { test("folds whitespace for multiline properties", () { - expect(_compile(""" - a { - --foo: { - q: r; - b { - s: t; + expect( + _compile(""" + a { + --foo: { + q: r; + b { + s: t; + } } } - } - """), equals("a{--foo: { q: r; b { s: t; } } }")); + """), + equals("a{--foo: { q: r; b { s: t; } } }"), + ); }); test("folds whitespace for single-line properties", () { - expect(_compile(""" - a { - --foo: a b\t\tc; - } - """), equals("a{--foo: a b\tc}")); + expect( + _compile(""" + a { + --foo: a b\t\tc; + } + """), + equals("a{--foo: a b\tc}"), + ); }); test("preserves semicolons when necessary", () { - expect(_compile(""" - a { - --foo: { - a: b; - }; - --bar: x y; - --baz: q r; - } - """), equals("a{--foo: { a: b; };--bar: x y;--baz: q r}")); + expect( + _compile(""" + a { + --foo: { + a: b; + }; + --bar: x y; + --baz: q r; + } + """), + equals("a{--foo: { a: b; };--bar: x y;--baz: q r}"), + ); }); }); }); @@ -113,10 +126,13 @@ void main() { }); test("don't include spaces around slashes", () { - expect(_compile(""" - @use "sass:list"; - a {b: list.slash(x, y, z)} - """), equals("a{b:x/y/z}")); + expect( + _compile(""" + @use "sass:list"; + a {b: list.slash(x, y, z)} + """), + equals("a{b:x/y/z}"), + ); }); test("do include spaces when space-separated", () { @@ -138,8 +154,10 @@ void main() { }); test("use rgba() when necessary", () { - expect(_compile("a {b: rgba(255, 0, 0, 0.5)}"), - equals("a{b:rgba(255,0,0,.5)}")); + expect( + _compile("a {b: rgba(255, 0, 0, 0.5)}"), + equals("a{b:rgba(255,0,0,.5)}"), + ); }); test("don't error when there's no name", () { @@ -153,9 +171,11 @@ void main() { var escape = "\\${character.toRadixString(16)}"; test(escape, () { expect( - _compile("a {b: $escape}"), - equalsIgnoringWhitespace( - "a{b:${String.fromCharCode(character)}}")); + _compile("a {b: $escape}"), + equalsIgnoringWhitespace( + "a{b:${String.fromCharCode(character)}}", + ), + ); }); } @@ -189,25 +209,33 @@ void main() { group("@supports", () { test("removes whitespace around the condition", () { - expect(_compile("@supports (display: flex) {a {b: c}}"), - equals("@supports(display: flex){a{b:c}}")); + expect( + _compile("@supports (display: flex) {a {b: c}}"), + equals("@supports(display: flex){a{b:c}}"), + ); }); test("preserves whitespace before the condition if necessary", () { - expect(_compile("@supports not (display: flex) {a {b: c}}"), - equals("@supports not (display: flex){a{b:c}}")); + expect( + _compile("@supports not (display: flex) {a {b: c}}"), + equals("@supports not (display: flex){a{b:c}}"), + ); }); }); group("@media", () { test("removes whitespace around the query", () { - expect(_compile("@media (min-width: 900px) {a {b: c}}"), - equals("@media(min-width: 900px){a{b:c}}")); + expect( + _compile("@media (min-width: 900px) {a {b: c}}"), + equals("@media(min-width: 900px){a{b:c}}"), + ); }); test("preserves whitespace before the query if necessary", () { - expect(_compile("@media screen {a {b: c}}"), - equals("@media screen{a{b:c}}")); + expect( + _compile("@media screen {a {b: c}}"), + equals("@media screen{a{b:c}}"), + ); }); // Removing whitespace after "and", "or", or "not" is forbidden because it @@ -215,50 +243,65 @@ void main() { group('preserves whitespace when necessary', () { test('around "and"', () { expect( - _compile(""" - @media screen and (min-width: 900px) and (max-width: 100px) { - a {b: c} - } - """), - equals("@media screen and (min-width: 900px)and (max-width: 100px)" - "{a{b:c}}")); + _compile(""" + @media screen and (min-width: 900px) and (max-width: 100px) { + a {b: c} + } + """), + equals( + "@media screen and (min-width: 900px)and (max-width: 100px)" + "{a{b:c}}", + ), + ); }); test('around "or"', () { expect( - _compile(""" - @media (min-width: 900px) or (max-width: 100px) or (print) { - a {b: c} - } - """), - equals("@media(min-width: 900px)or (max-width: 100px)or (print)" - "{a{b:c}}")); + _compile(""" + @media (min-width: 900px) or (max-width: 100px) or (print) { + a {b: c} + } + """), + equals( + "@media(min-width: 900px)or (max-width: 100px)or (print)" + "{a{b:c}}", + ), + ); }); test('after "not"', () { - expect(_compile(""" - @media not (min-width: 900px) { - a {b: c} - } - """), equals("@media not (min-width: 900px){a{b:c}}")); + expect( + _compile(""" + @media not (min-width: 900px) { + a {b: c} + } + """), + equals("@media not (min-width: 900px){a{b:c}}"), + ); }); }); test("preserves whitespace around the modifier", () { - expect(_compile("@media only screen {a {b: c}}"), - equals("@media only screen{a{b:c}}")); + expect( + _compile("@media only screen {a {b: c}}"), + equals("@media only screen{a{b:c}}"), + ); }); }); group("@keyframes", () { test("removes whitespace after the selector", () { - expect(_compile("@keyframes a {from {a: b}}"), - equals("@keyframes a{from{a:b}}")); + expect( + _compile("@keyframes a {from {a: b}}"), + equals("@keyframes a{from{a:b}}"), + ); }); test("removes whitespace after commas", () { - expect(_compile("@keyframes a {from, to {a: b}}"), - equals("@keyframes a{from,to{a:b}}")); + expect( + _compile("@keyframes a {from, to {a: b}}"), + equals("@keyframes a{from,to{a:b}}"), + ); }); }); @@ -273,47 +316,62 @@ void main() { }); test("removes whitespace before a media query", () { - expect(_compile('@import "foo.css" screen;'), - equals('@import"foo.css"screen')); + expect( + _compile('@import "foo.css" screen;'), + equals('@import"foo.css"screen'), + ); }); test("removes whitespace before a supports condition", () { - expect(_compile('@import "foo.css" supports(display: flex);'), - equals('@import"foo.css"supports(display: flex)')); + expect( + _compile('@import "foo.css" supports(display: flex);'), + equals('@import"foo.css"supports(display: flex)'), + ); }); }); group("comments", () { test("are removed", () { expect(_compile("/* foo bar */"), isEmpty); - expect(_compile(""" - a { - b: c; - /* foo bar */ - d: e; - } - """), equals("a{b:c;d:e}")); + expect( + _compile(""" + a { + b: c; + /* foo bar */ + d: e; + } + """), + equals("a{b:c;d:e}"), + ); }); test("remove their parents if they're the only contents", () { expect(_compile("a {/* foo bar */}"), isEmpty); - expect(_compile(""" - a { - /* foo bar */ - /* baz bang */ - } - """), isEmpty); + expect( + _compile(""" + a { + /* foo bar */ + /* baz bang */ + } + """), + isEmpty, + ); }); test("are preserved with /*!", () { expect(_compile("/*! foo bar */"), equals("/*! foo bar */")); expect( - _compile("/*! foo */\n/*! bar */"), equals("/*! foo *//*! bar */")); - expect(_compile(""" - a { - /*! foo bar */ - } - """), equals("a{/*! foo bar */}")); + _compile("/*! foo */\n/*! bar */"), + equals("/*! foo *//*! bar */"), + ); + expect( + _compile(""" + a { + /*! foo bar */ + } + """), + equals("a{/*! foo bar */}"), + ); }); }); } diff --git a/test/dart_api/function_test.dart b/test/dart_api/function_test.dart index 68403d424..a11f5c465 100644 --- a/test/dart_api/function_test.dart +++ b/test/dart_api/function_test.dart @@ -14,113 +14,170 @@ void main() { test( "new Callable() throws a SassFormatException if the argument list is " "invalid", () { - expect(() => Callable("foo", "arg", (_) => sassNull), - throwsA(const TypeMatcher())); + expect( + () => Callable("foo", "arg", (_) => sassNull), + throwsA(const TypeMatcher()), + ); }); test( "new AsyncCallable() throws a SassFormatException if the argument list " "is invalid", () { - expect(() => AsyncCallable("foo", "arg", (_) async => sassNull), - throwsA(const TypeMatcher())); + expect( + () => AsyncCallable("foo", "arg", (_) async => sassNull), + throwsA(const TypeMatcher()), + ); }); test("passes an argument to a custom function and uses its return value", () { - var css = compileString('a {b: foo(bar)}', functions: [ - Callable("foo", r"$arg", expectAsync1((arguments) { - expect(arguments, hasLength(1)); - expect(arguments.first.assertString().text, equals("bar")); - return SassString("result", quotes: false); - })) - ]); + var css = compileString( + 'a {b: foo(bar)}', + functions: [ + Callable( + "foo", + r"$arg", + expectAsync1((arguments) { + expect(arguments, hasLength(1)); + expect(arguments.first.assertString().text, equals("bar")); + return SassString("result", quotes: false); + }), + ), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: result; }")); }); test("runs a function asynchronously", () async { - var css = await compileStringAsync('a {b: foo(bar)}', functions: [ - AsyncCallable("foo", r"$arg", expectAsync1((arguments) async { - expect(arguments, hasLength(1)); - expect(arguments.first.assertString().text, equals("bar")); - await pumpEventQueue(); - return SassString("result", quotes: false); - })) - ]); + var css = await compileStringAsync( + 'a {b: foo(bar)}', + functions: [ + AsyncCallable( + "foo", + r"$arg", + expectAsync1((arguments) async { + expect(arguments, hasLength(1)); + expect(arguments.first.assertString().text, equals("bar")); + await pumpEventQueue(); + return SassString("result", quotes: false); + }), + ), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: result; }")); }); test("passes no arguments to a custom function", () { expect( - compileString('a {b: foo()}', functions: [ - Callable("foo", "", expectAsync1((arguments) { - expect(arguments, isEmpty); - return sassNull; - })) - ]), - isEmpty); + compileString( + 'a {b: foo()}', + functions: [ + Callable( + "foo", + "", + expectAsync1((arguments) { + expect(arguments, isEmpty); + return sassNull; + }), + ), + ], + ), + isEmpty, + ); }); test("passes multiple arguments to a custom function", () { expect( - compileString('a {b: foo(x, y, z)}', functions: [ - Callable("foo", r"$arg1, $arg2, $arg3", expectAsync1((arguments) { - expect(arguments, hasLength(3)); - expect(arguments[0].assertString().text, equals("x")); - expect(arguments[1].assertString().text, equals("y")); - expect(arguments[2].assertString().text, equals("z")); - return sassNull; - })) - ]), - isEmpty); + compileString( + 'a {b: foo(x, y, z)}', + functions: [ + Callable( + "foo", + r"$arg1, $arg2, $arg3", + expectAsync1((arguments) { + expect(arguments, hasLength(3)); + expect(arguments[0].assertString().text, equals("x")); + expect(arguments[1].assertString().text, equals("y")); + expect(arguments[2].assertString().text, equals("z")); + return sassNull; + }), + ), + ], + ), + isEmpty, + ); }); test("gracefully handles a custom function throwing", () { expect(() { - compileString('a {b: foo()}', - functions: [Callable("foo", "", (arguments) => throw "heck")]); + compileString( + 'a {b: foo()}', + functions: [Callable("foo", "", (arguments) => throw "heck")], + ); }, throwsA(const TypeMatcher())); }); test("supports default argument values", () { - var css = compileString('a {b: foo()}', functions: [ - Callable("foo", r"$arg: 1", expectAsync1((arguments) { - expect(arguments, hasLength(1)); - expect(arguments.first.assertNumber().value, equals(1)); - return arguments.first; - })) - ]); + var css = compileString( + 'a {b: foo()}', + functions: [ + Callable( + "foo", + r"$arg: 1", + expectAsync1((arguments) { + expect(arguments, hasLength(1)); + expect(arguments.first.assertNumber().value, equals(1)); + return arguments.first; + }), + ), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: 1; }")); }); test("supports argument lists", () { - var css = compileString('a {b: foo(1, 2, 3)}', functions: [ - Callable("foo", r"$args...", expectAsync1((arguments) { - expect(arguments, hasLength(1)); - var list = arguments[0] as SassArgumentList; - expect(list.asList, hasLength(3)); - expect(list.asList[0].assertNumber().value, equals(1)); - expect(list.asList[1].assertNumber().value, equals(2)); - expect(list.asList[2].assertNumber().value, equals(3)); - return arguments.first; - })) - ]); + var css = compileString( + 'a {b: foo(1, 2, 3)}', + functions: [ + Callable( + "foo", + r"$args...", + expectAsync1((arguments) { + expect(arguments, hasLength(1)); + var list = arguments[0] as SassArgumentList; + expect(list.asList, hasLength(3)); + expect(list.asList[0].assertNumber().value, equals(1)); + expect(list.asList[1].assertNumber().value, equals(2)); + expect(list.asList[2].assertNumber().value, equals(3)); + return arguments.first; + }), + ), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: 1, 2, 3; }")); }); test("supports keyword arguments", () { - var css = compileString(r'a {b: foo($bar: 1)}', functions: [ - Callable("foo", r"$args...", expectAsync1((arguments) { - expect(arguments, hasLength(1)); - var list = arguments[0] as SassArgumentList; - expect(list.asList, hasLength(0)); - expect(list.keywords, contains("bar")); - expect(list.keywords["bar"]!.assertNumber().value, equals(1)); - return list.keywords["bar"]!; - })) - ]); + var css = compileString( + r'a {b: foo($bar: 1)}', + functions: [ + Callable( + "foo", + r"$args...", + expectAsync1((arguments) { + expect(arguments, hasLength(1)); + var list = arguments[0] as SassArgumentList; + expect(list.asList, hasLength(0)); + expect(list.keywords, contains("bar")); + expect(list.keywords["bar"]!.assertNumber().value, equals(1)); + return list.keywords["bar"]!; + }), + ), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: 1; }")); }); @@ -128,24 +185,40 @@ void main() { group("are dash-normalized", () { test("when defined with dashes", () { expect( - compileString('a {b: foo_bar()}', functions: [ - Callable("foo-bar", "", expectAsync1((arguments) { - expect(arguments, isEmpty); - return sassNull; - })) - ]), - isEmpty); + compileString( + 'a {b: foo_bar()}', + functions: [ + Callable( + "foo-bar", + "", + expectAsync1((arguments) { + expect(arguments, isEmpty); + return sassNull; + }), + ), + ], + ), + isEmpty, + ); }); test("when defined with underscores", () { expect( - compileString('a {b: foo-bar()}', functions: [ - Callable("foo_bar", "", expectAsync1((arguments) { - expect(arguments, isEmpty); - return sassNull; - })) - ]), - isEmpty); + compileString( + 'a {b: foo-bar()}', + functions: [ + Callable( + "foo_bar", + "", + expectAsync1((arguments) { + expect(arguments, isEmpty); + return sassNull; + }), + ), + ], + ), + isEmpty, + ); }); }); } diff --git a/test/dart_api/importer_test.dart b/test/dart_api/importer_test.dart index 0b47eccde..c7e7aa474 100644 --- a/test/dart_api/importer_test.dart +++ b/test/dart_api/importer_test.dart @@ -17,175 +17,244 @@ import '../utils.dart'; void main() { test("uses an importer to resolve a @use", () { - var css = compileString('@use "orange";', importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - }) - ]); + var css = compileString( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }), + ], + ); expect(css, equals(".orange {\n color: orange;\n}")); }); test("passes the canonicalized URL to the importer", () { - var css = compileString('@use "orange";', importers: [ - TestImporter((url) => Uri.parse('u:blue'), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - }) - ]); + var css = compileString( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse('u:blue'), (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }), + ], + ); expect(css, equals(".blue {\n color: blue;\n}")); }); test("only invokes the importer once for a given canonicalization", () { - var css = compileString(""" - @import "orange"; - @import "orange"; - """, importers: [ - TestImporter( + var css = compileString( + """ + @import "orange"; + @import "orange"; + """, + importers: [ + TestImporter( (url) => Uri.parse('u:blue'), expectAsync1((url) { var color = url.path; return ImporterResult('.$color {color: $color}', indented: false); - }, count: 1)) - ]); - - expect(css, equals(""" -.blue { - color: blue; -} + }, count: 1), + ), + ], + ); -.blue { - color: blue; -}""")); + expect( + css, + equalsIgnoringWhitespace(""" + .blue { + color: blue; + } + + .blue { + color: blue; + } + """), + ); }); test("resolves URLs relative to the pre-canonicalized URL", () { var times = 0; - var css = compileString('@use "foo:bar/baz";', - importers: [ - TestImporter( - expectAsync1((url) { - times++; - if (times == 1) return Uri(path: 'first'); + var css = compileString( + '@use "foo:bar/baz";', + importers: [ + TestImporter( + expectAsync1((url) { + times++; + if (times == 1) return Uri(path: 'first'); - expect(url, equals(Uri.parse('foo:bar/bang'))); - return Uri(path: 'second'); - }, count: 2), - expectAsync1((url) { - return ImporterResult( - times == 1 - ? ''' + expect(url, equals(Uri.parse('foo:bar/bang'))); + return Uri(path: 'second'); + }, count: 2), + expectAsync1((url) { + return ImporterResult( + times == 1 + ? ''' .first {url: "$url"} @import "bang"; ''' - : '.second {url: "$url"}', - indented: false); - }, count: 2)) - ], - logger: Logger.quiet); + : '.second {url: "$url"}', + indented: false, + ); + }, count: 2), + ), + ], + logger: Logger.quiet, + ); - expect(css, equalsIgnoringWhitespace(''' + expect( + css, + equalsIgnoringWhitespace(''' .first { url: "first"; } .second { url: "second"; } - ''')); + '''), + ); }); group("the imported URL", () { // Regression test for #1137. test("isn't changed if it's root-relative", () { - compileString('@use "/orange";', importers: [ - TestImporter(expectAsync1((url) { - expect(url, equals(Uri.parse("/orange"))); - return Uri.parse("u:$url"); - }), (url) => ImporterResult('', syntax: Syntax.scss)) - ]); + compileString( + '@use "/orange";', + importers: [ + TestImporter( + expectAsync1((url) { + expect(url, equals(Uri.parse("/orange"))); + return Uri.parse("u:$url"); + }), + (url) => ImporterResult('', syntax: Syntax.scss), + ), + ], + ); }); test("is converted to a file: URL if it's an absolute Windows path", () { - compileString('@import "C:/orange";', importers: [ - TestImporter(expectAsync1((url) { - expect(url, equals(Uri.parse("file:///C:/orange"))); - return Uri.parse("u:$url"); - }), (url) => ImporterResult('', syntax: Syntax.scss)) - ]); + compileString( + '@import "C:/orange";', + importers: [ + TestImporter( + expectAsync1((url) { + expect(url, equals(Uri.parse("file:///C:/orange"))); + return Uri.parse("u:$url"); + }), + (url) => ImporterResult('', syntax: Syntax.scss), + ), + ], + ); }); }); group("the containing URL", () { test("is null for a potentially canonical scheme", () { late TestImporter importer; - compileString('@use "u:orange";', - importers: [ - importer = TestImporter(expectAsync1((url) { + compileString( + '@use "u:orange";', + importers: [ + importer = TestImporter( + expectAsync1((url) { expect(importer.publicContainingUrl, isNull); return url; - }), (_) => ImporterResult('', indented: false)) - ], - url: 'x:original.scss'); + }), + (_) => ImporterResult('', indented: false), + ), + ], + url: 'x:original.scss', + ); }); test("throws an error outside canonicalize", () { late TestImporter importer; - compileString('@use "orange";', importers: [ - importer = - TestImporter((url) => Uri.parse("u:$url"), expectAsync1((url) { - expect(() => importer.publicContainingUrl, throwsStateError); - return ImporterResult('', indented: false); - })) - ]); + compileString( + '@use "orange";', + importers: [ + importer = TestImporter( + (url) => Uri.parse("u:$url"), + expectAsync1((url) { + expect(() => importer.publicContainingUrl, throwsStateError); + return ImporterResult('', indented: false); + }), + ), + ], + ); }); group("for a non-canonical scheme", () { test("is set to the original URL", () { late TestImporter importer; - compileString('@use "u:orange";', - importers: [ - importer = TestImporter(expectAsync1((url) { - expect(importer.publicContainingUrl, - equals(Uri.parse('x:original.scss'))); + compileString( + '@use "u:orange";', + importers: [ + importer = TestImporter( + expectAsync1((url) { + expect( + importer.publicContainingUrl, + equals(Uri.parse('x:original.scss')), + ); return url.replace(scheme: 'x'); - }), (_) => ImporterResult('', indented: false), - nonCanonicalSchemes: {'u'}) - ], - url: 'x:original.scss'); + }), + (_) => ImporterResult('', indented: false), + nonCanonicalSchemes: {'u'}, + ), + ], + url: 'x:original.scss', + ); }); test("is null if the original URL is null", () { late TestImporter importer; - compileString('@use "u:orange";', importers: [ - importer = TestImporter(expectAsync1((url) { - expect(importer.publicContainingUrl, isNull); - return url.replace(scheme: 'x'); - }), (_) => ImporterResult('', indented: false), - nonCanonicalSchemes: {'u'}) - ]); + compileString( + '@use "u:orange";', + importers: [ + importer = TestImporter( + expectAsync1((url) { + expect(importer.publicContainingUrl, isNull); + return url.replace(scheme: 'x'); + }), + (_) => ImporterResult('', indented: false), + nonCanonicalSchemes: {'u'}, + ), + ], + ); }); }); group("for a schemeless load", () { test("is set to the original URL", () { late TestImporter importer; - compileString('@use "orange";', - importers: [ - importer = TestImporter(expectAsync1((url) { - expect(importer.publicContainingUrl, - equals(Uri.parse('x:original.scss'))); + compileString( + '@use "orange";', + importers: [ + importer = TestImporter( + expectAsync1((url) { + expect( + importer.publicContainingUrl, + equals(Uri.parse('x:original.scss')), + ); return Uri.parse("u:$url"); - }), (_) => ImporterResult('', indented: false)) - ], - url: 'x:original.scss'); + }), + (_) => ImporterResult('', indented: false), + ), + ], + url: 'x:original.scss', + ); }); test("is null if the original URL is null", () { late TestImporter importer; - compileString('@use "orange";', importers: [ - importer = TestImporter(expectAsync1((url) { - expect(importer.publicContainingUrl, isNull); - return Uri.parse("u:$url"); - }), (_) => ImporterResult('', indented: false)) - ]); + compileString( + '@use "orange";', + importers: [ + importer = TestImporter( + expectAsync1((url) { + expect(importer.publicContainingUrl, isNull); + return Uri.parse("u:$url"); + }), + (_) => ImporterResult('', indented: false), + ), + ], + ); }); }); }); @@ -194,157 +263,229 @@ void main() { "throws an error if the importer returns a canonical URL with a " "non-canonical scheme", () { expect( - () => compileString('@use "orange";', importers: [ - TestImporter(expectAsync1((url) => Uri.parse("u:$url")), - (_) => ImporterResult('', indented: false), - nonCanonicalSchemes: {'u'}) - ]), throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect(error.toString(), - contains("uses a scheme declared as non-canonical")); - return true; - }))); + () => compileString( + '@use "orange";', + importers: [ + TestImporter( + expectAsync1((url) => Uri.parse("u:$url")), + (_) => ImporterResult('', indented: false), + nonCanonicalSchemes: {'u'}, + ), + ], + ), + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + contains("uses a scheme declared as non-canonical"), + ); + return true; + }), + ), + ); }); test("uses an importer's source map URL", () { - var result = compileStringToResult('@use "orange";', - importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', - sourceMapUrl: Uri.parse("u:blue"), indented: false); - }) - ], - sourceMap: true); + var result = compileStringToResult( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + var color = url.path; + return ImporterResult( + '.$color {color: $color}', + sourceMapUrl: Uri.parse("u:blue"), + indented: false, + ); + }), + ], + sourceMap: true, + ); expect(result.sourceMap!.urls, contains("u:blue")); }); test("uses a data: source map URL if the importer doesn't provide one", () { - var result = compileStringToResult('@use "orange";', - importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - }) - ], - sourceMap: true); + var result = compileStringToResult( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }), + ], + sourceMap: true, + ); expect( - result.sourceMap!.urls, - contains(Uri.dataFromString(".orange {color: orange}", encoding: utf8) - .toString())); + result.sourceMap!.urls, + contains( + Uri.dataFromString( + ".orange {color: orange}", + encoding: utf8, + ).toString(), + ), + ); }); test("wraps an error in canonicalize()", () { - expect(() { - compileString('@use "orange";', importers: [ - TestImporter((url) { - throw "this import is bad actually"; - }, expectNever1) - ]); - }, throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), startsWith("Error: this import is bad actually")); - return true; - }))); + expect( + () { + compileString( + '@use "orange";', + importers: [ + TestImporter((url) { + throw "this import is bad actually"; + }, expectNever1), + ], + ); + }, + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith("Error: this import is bad actually"), + ); + return true; + }), + ), + ); }); test("wraps an error in load()", () { - expect(() { - compileString('@use "orange";', importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - throw "this import is bad actually"; - }) - ]); - }, throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect( - error.toString(), startsWith("Error: this import is bad actually")); - return true; - }))); + expect( + () { + compileString( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + throw "this import is bad actually"; + }), + ], + ); + }, + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith("Error: this import is bad actually"), + ); + return true; + }), + ), + ); }); test("prefers .message to .toString() for an importer error", () { - expect(() { - compileString('@use "orange";', importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - throw FormatException("bad format somehow"); - }) - ]); - }, throwsA(predicate((error) { - expect(error, const TypeMatcher()); - // FormatException.toString() starts with "FormatException:", but - // the error message should not. - expect(error.toString(), startsWith("Error: bad format somehow")); - return true; - }))); + expect( + () { + compileString( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + throw FormatException("bad format somehow"); + }), + ], + ); + }, + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + // FormatException.toString() starts with "FormatException:", but + // the error message should not. + expect(error.toString(), startsWith("Error: bad format somehow")); + return true; + }), + ), + ); }); test("avoids importer when only load() returns null", () { - expect(() { - compileString('@use "orange";', importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) => null) - ]); - }, throwsA(predicate((error) { - expect(error, const TypeMatcher()); - expect(error.toString(), - startsWith("Error: Can't find stylesheet to import")); - return true; - }))); + expect( + () { + compileString( + '@use "orange";', + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) => null), + ], + ); + }, + throwsA( + predicate((error) { + expect(error, const TypeMatcher()); + expect( + error.toString(), + startsWith("Error: Can't find stylesheet to import"), + ); + return true; + }), + ), + ); }); group("compileString()'s importer option", () { test("loads relative imports from the entrypoint", () { - var css = compileString('@use "orange";', - importer: TestImporter((url) => Uri.parse("u:$url"), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - })); + var css = compileString( + '@use "orange";', + importer: TestImporter((url) => Uri.parse("u:$url"), (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }), + ); expect(css, equals(".orange {\n color: orange;\n}")); }); test("loads imports relative to the entrypoint's URL", () { - var css = compileString('@use "baz/qux";', - importer: TestImporter((url) => url.resolve("bang"), (url) { - return ImporterResult('a {result: "${url.path}"}', indented: false); - }), - url: Uri.parse("u:foo/bar")); + var css = compileString( + '@use "baz/qux";', + importer: TestImporter((url) => url.resolve("bang"), (url) { + return ImporterResult('a {result: "${url.path}"}', indented: false); + }), + url: Uri.parse("u:foo/bar"), + ); expect(css, equals('a {\n result: "foo/baz/bang";\n}')); }); test("doesn't load absolute imports", () { - var css = compileString('@use "u:orange";', - importer: TestImporter((_) => throw "Should not be called", - (_) => throw "Should not be called"), - importers: [ - TestImporter((url) => url, (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - }) - ]); + var css = compileString( + '@use "u:orange";', + importer: TestImporter( + (_) => throw "Should not be called", + (_) => throw "Should not be called", + ), + importers: [ + TestImporter((url) => url, (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }), + ], + ); expect(css, equals(".orange {\n color: orange;\n}")); }); test("doesn't load from other importers", () { - var css = compileString('@use "u:midstream";', - importer: TestImporter((_) => throw "Should not be called", - (_) => throw "Should not be called"), - importers: [ - TestImporter((url) => url, (url) { - if (url.path == "midstream") { - return ImporterResult("@use 'orange';", indented: false); - } else { - var color = url.path; - return ImporterResult('.$color {color: $color}', - indented: false); - } - }) - ]); + var css = compileString( + '@use "u:midstream";', + importer: TestImporter( + (_) => throw "Should not be called", + (_) => throw "Should not be called", + ), + importers: [ + TestImporter((url) => url, (url) { + if (url.path == "midstream") { + return ImporterResult("@use 'orange';", indented: false); + } else { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + } + }), + ], + ); expect(css, equals(".orange {\n color: orange;\n}")); }); @@ -364,8 +505,10 @@ void main() { }); test("false from meta.load-css", () { - compileString('@use "sass:meta"; @include meta.load-css("")', - importers: [FromImportImporter(false)]); + compileString( + '@use "sass:meta"; @include meta.load-css("")', + importers: [FromImportImporter(false)], + ); }); }); } diff --git a/test/dart_api/logger_test.dart b/test/dart_api/logger_test.dart index dbd3f205e..d7c0cbe00 100644 --- a/test/dart_api/logger_test.dart +++ b/test/dart_api/logger_test.dart @@ -17,216 +17,319 @@ void main() { group("with @warn", () { test("passes the message and stack trace to the logger", () { var mustBeCalled = expectAsync0(() {}); - compileString(''' - @mixin foo {@warn heck} - @include foo; - ''', logger: _TestLogger.withWarn((message, - {span, trace, deprecation = false}) { - expect(message, equals("heck")); - expect(span, isNull); - expect(trace!.frames.first.member, equals('foo()')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + compileString( + ''' + @mixin foo {@warn heck} + @include foo; + ''', + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("heck")); + expect(span, isNull); + expect(trace!.frames.first.member, equals('foo()')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); }); test("stringifies the argument", () { var mustBeCalled = expectAsync0(() {}); - compileString('@warn #abc', logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, equals("#abc")); - mustBeCalled(); - })); + compileString( + '@warn #abc', + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("#abc")); + mustBeCalled(); + }), + ); }); test("doesn't inspect the argument", () { var mustBeCalled = expectAsync0(() {}); - compileString('@warn null', logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, isEmpty); - mustBeCalled(); - })); + compileString( + '@warn null', + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, isEmpty); + mustBeCalled(); + }), + ); }); }); group("with @debug", () { test("passes the message and span to the logger", () { - compileString('@debug heck', - logger: _TestLogger.withDebug(expectAsync2((message, span) { - expect(message, equals("heck")); - expect(span.start.line, equals(0)); - expect(span.start.column, equals(0)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(11)); - }))); + compileString( + '@debug heck', + logger: _TestLogger.withDebug( + expectAsync2((message, span) { + expect(message, equals("heck")); + expect(span.start.line, equals(0)); + expect(span.start.column, equals(0)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(11)); + }), + ), + ); }); test("stringifies the argument", () { - compileString('@debug #abc', - logger: _TestLogger.withDebug(expectAsync2((message, span) { - expect(message, equals("#abc")); - }))); + compileString( + '@debug #abc', + logger: _TestLogger.withDebug( + expectAsync2((message, span) { + expect(message, equals("#abc")); + }), + ), + ); }); test("inspects the argument", () { - compileString('@debug null', - logger: _TestLogger.withDebug(expectAsync2((message, span) { - expect(message, equals("null")); - }))); + compileString( + '@debug null', + logger: _TestLogger.withDebug( + expectAsync2((message, span) { + expect(message, equals("null")); + }), + ), + ); }); }); test("with a parser warning passes the message, span, and trace", () { var mustBeCalled = expectAsync0(() {}); - compileString('a {b: c && d}', logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, contains('"&&" means two copies')); - - expect(span!.start.line, equals(0)); - expect(span.start.column, equals(8)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(10)); - - expect(trace!.frames.first.member, equals('root stylesheet')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + compileString( + 'a {b: c && d}', + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, contains('"&&" means two copies')); + + expect(span!.start.line, equals(0)); + expect(span.start.column, equals(8)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(10)); + + expect(trace!.frames.first.member, equals('root stylesheet')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); }); test("with a runner warning passes the message, span, and trace", () { var mustBeCalled = expectAsync0(() {}); - compileString(''' + compileString( + ''' @mixin foo {#{blue} {x: y}} @include foo; - ''', logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, contains("color value blue")); - - expect(span!.start.line, equals(0)); - expect(span.start.column, equals(22)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(26)); - - expect(trace!.frames.first.member, equals('foo()')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + ''', + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, contains("color value blue")); + + expect(span!.start.line, equals(0)); + expect(span.start.column, equals(22)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(26)); + + expect(trace!.frames.first.member, equals('foo()')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); }); group("with warn()", () { group("from a function", () { test("synchronously", () { var mustBeCalled = expectAsync0(() {}); - compileString(""" - @function bar() {@return foo()} - a {b: bar()} - """, functions: [ - Callable("foo", "", expectAsync1((_) { - warn("heck"); - return sassNull; - })) - ], logger: _TestLogger.withWarn((message, - {span, trace, deprecation = false}) { - expect(message, equals("heck")); - - expect(span!.start.line, equals(0)); - expect(span.start.column, equals(33)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(38)); - - expect(trace!.frames.first.member, equals('bar()')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + compileString( + """ + @function bar() {@return foo()} + a {b: bar()} + """, + functions: [ + Callable( + "foo", + "", + expectAsync1((_) { + warn("heck"); + return sassNull; + }), + ), + ], + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("heck")); + + expect(span!.start.line, equals(0)); + expect(span.start.column, equals(37)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(42)); + + expect(trace!.frames.first.member, equals('bar()')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); }); test("asynchronously", () { var mustBeCalled = expectAsync0(() {}); - compileStringAsync(""" - @function bar() {@return foo()} - a {b: bar()} - """, functions: [ - AsyncCallable("foo", "", expectAsync1((_) async { - warn("heck"); - return sassNull; - })) - ], logger: _TestLogger.withWarn((message, - {span, trace, deprecation = false}) { - expect(message, equals("heck")); - - expect(span!.start.line, equals(0)); - expect(span.start.column, equals(33)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(38)); - - expect(trace!.frames.first.member, equals('bar()')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + compileStringAsync( + """ + @function bar() {@return foo()} + a {b: bar()} + """, + functions: [ + AsyncCallable( + "foo", + "", + expectAsync1((_) async { + warn("heck"); + return sassNull; + }), + ), + ], + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("heck")); + + expect(span!.start.line, equals(0)); + expect(span.start.column, equals(37)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(42)); + + expect(trace!.frames.first.member, equals('bar()')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); }); test("asynchronously after a gap", () { var mustBeCalled = expectAsync0(() {}); - compileStringAsync(""" - @function bar() {@return foo()} - a {b: bar()} - """, functions: [ - AsyncCallable("foo", "", expectAsync1((_) async { - await Future.delayed(Duration.zero); + compileStringAsync( + """ + @function bar() {@return foo()} + a {b: bar()} + """, + functions: [ + AsyncCallable( + "foo", + "", + expectAsync1((_) async { + await Future.delayed(Duration.zero); + warn("heck"); + return sassNull; + }), + ), + ], + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("heck")); + + expect(span!.start.line, equals(0)); + expect(span.start.column, equals(37)); + expect(span.end.line, equals(0)); + expect(span.end.column, equals(42)); + + expect(trace!.frames.first.member, equals('bar()')); + expect(deprecation, isFalse); + mustBeCalled(); + }), + ); + }); + }); + + test("from an importer", () { + var mustBeCalled = expectAsync0(() {}); + compileString( + "@use 'foo';", + importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { warn("heck"); - return sassNull; - })) - ], logger: _TestLogger.withWarn((message, - {span, trace, deprecation = false}) { + return ImporterResult("", indented: false); + }), + ], + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { expect(message, equals("heck")); expect(span!.start.line, equals(0)); - expect(span.start.column, equals(33)); + expect(span.start.column, equals(0)); expect(span.end.line, equals(0)); - expect(span.end.column, equals(38)); + expect(span.end.column, equals(10)); - expect(trace!.frames.first.member, equals('bar()')); + expect(trace!.frames.first.member, equals('@use')); expect(deprecation, isFalse); mustBeCalled(); - })); - }); - }); - - test("from an importer", () { - var mustBeCalled = expectAsync0(() {}); - compileString("@use 'foo';", importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - warn("heck"); - return ImporterResult("", indented: false); - }) - ], logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, equals("heck")); - - expect(span!.start.line, equals(0)); - expect(span.start.column, equals(0)); - expect(span.end.line, equals(0)); - expect(span.end.column, equals(10)); - - expect(trace!.frames.first.member, equals('@use')); - expect(deprecation, isFalse); - mustBeCalled(); - })); + }), + ); }); test("with deprecation", () { var mustBeCalled = expectAsync0(() {}); - compileString("a {b: foo()}", functions: [ - Callable("foo", "", expectAsync1((_) { - warn("heck", deprecation: true); - return sassNull; - })) - ], logger: - _TestLogger.withWarn((message, {span, trace, deprecation = false}) { - expect(message, equals("heck")); - expect(deprecation, isTrue); - mustBeCalled(); - })); + compileString( + "a {b: foo()}", + functions: [ + Callable( + "foo", + "", + expectAsync1((_) { + warn("heck", deprecation: true); + return sassNull; + }), + ), + ], + logger: _TestLogger.withWarn(( + message, { + span, + trace, + deprecation = false, + }) { + expect(message, equals("heck")); + expect(deprecation, isTrue); + mustBeCalled(); + }), + ); }); }); } @@ -241,8 +344,12 @@ class _TestLogger implements Logger { _TestLogger.withDebug(this._debug) : _warn = const Logger.stderr().warn; - void warn(String message, - {FileSpan? span, Trace? trace, bool deprecation = false}) => + void warn( + String message, { + FileSpan? span, + Trace? trace, + bool deprecation = false, + }) => _warn(message, span: span, trace: trace, deprecation: deprecation); void debug(String message, SourceSpan span) => _debug(message, span); } diff --git a/test/dart_api/test_importer.dart b/test/dart_api/test_importer.dart index fda884c93..144d1743f 100644 --- a/test/dart_api/test_importer.dart +++ b/test/dart_api/test_importer.dart @@ -15,9 +15,11 @@ class TestImporter extends Importer { /// implementations can access them. Uri? get publicContainingUrl => containingUrl; - TestImporter(this._canonicalize, this._load, - {Iterable? nonCanonicalSchemes}) - : _nonCanonicalSchemes = nonCanonicalSchemes == null + TestImporter( + this._canonicalize, + this._load, { + Iterable? nonCanonicalSchemes, + }) : _nonCanonicalSchemes = nonCanonicalSchemes == null ? const {} : Set.unmodifiable(nonCanonicalSchemes); diff --git a/test/dart_api/value/calculation_test.dart b/test/dart_api/value/calculation_test.dart index 4ebe47d62..bcbb6d864 100644 --- a/test/dart_api/value/calculation_test.dart +++ b/test/dart_api/value/calculation_test.dart @@ -33,47 +33,66 @@ void main() { group('SassCalculation simplifies', () { test('calc()', () { - expect(SassCalculation.calc(SassNumber(1)).assertNumber(), - equals(SassNumber(1))); + expect( + SassCalculation.calc(SassNumber(1)).assertNumber(), + equals(SassNumber(1)), + ); }); test('min()', () { - expect(SassCalculation.min([SassNumber(1), SassNumber(2)]).assertNumber(), - equals(SassNumber(1))); + expect( + SassCalculation.min([SassNumber(1), SassNumber(2)]).assertNumber(), + equals(SassNumber(1)), + ); }); test('max()', () { - expect(SassCalculation.max([SassNumber(1), SassNumber(2)]).assertNumber(), - equals(SassNumber(2))); + expect( + SassCalculation.max([SassNumber(1), SassNumber(2)]).assertNumber(), + equals(SassNumber(2)), + ); }); test('clamp()', () { expect( - SassCalculation.clamp(SassNumber(1), SassNumber(2), SassNumber(3)) - .assertNumber(), - equals(SassNumber(2))); + SassCalculation.clamp( + SassNumber(1), + SassNumber(2), + SassNumber(3), + ).assertNumber(), + equals(SassNumber(2)), + ); }); test('operations', () { expect( - SassCalculation.calc(SassCalculation.operate( - CalculationOperator.plus, - SassCalculation.operate( - CalculationOperator.minus, - SassCalculation.operate( - CalculationOperator.times, - SassCalculation.operate(CalculationOperator.dividedBy, - SassNumber(5), SassNumber(2)), - SassNumber(3)), - SassNumber(4)), - SassNumber(5))) - .assertNumber(), - equals(SassNumber(8.5))); + SassCalculation.calc( + SassCalculation.operate( + CalculationOperator.plus, + SassCalculation.operate( + CalculationOperator.minus, + SassCalculation.operate( + CalculationOperator.times, + SassCalculation.operate( + CalculationOperator.dividedBy, + SassNumber(5), + SassNumber(2), + ), + SassNumber(3), + ), + SassNumber(4), + ), + SassNumber(5), + ), + ).assertNumber(), + equals(SassNumber(8.5)), + ); }); test('interpolation', () { - var result = SassCalculation.calc(CalculationInterpolation('1 + 2')) - .assertCalculation(); + var result = SassCalculation.calc( + CalculationInterpolation('1 + 2'), + ).assertCalculation(); expect(result.name, equals('calc')); expect(result.arguments[0], equals(SassString('(1 + 2)'))); }); diff --git a/test/dart_api/value/color_test.dart b/test/dart_api/value/color_test.dart index e0a6f3028..63eab959e 100644 --- a/test/dart_api/value/color_test.dart +++ b/test/dart_api/value/color_test.dart @@ -80,9 +80,11 @@ void main() { test("equals an equivalent legacy color", () { expect( - value, - equalsWithHash( - SassColor.hsl(210, 65.3846153846154, 20.392156862745097))); + value, + equalsWithHash( + SassColor.hsl(210, 65.3846153846154, 20.392156862745097), + ), + ); }); test("does not equal an equivalent non-legacy color", () { @@ -102,64 +104,93 @@ void main() { group("toSpace", () { test("converts the color to a given space", () { expect( - value.toSpace(ColorSpace.lab), - equals(SassColor.lab( - 20.675469453386192, -2.276792630515417, -24.59314874484676))); + value.toSpace(ColorSpace.lab), + equals( + SassColor.lab( + 20.675469453386192, + -2.276792630515417, + -24.59314874484676, + ), + ), + ); }); test("with legacyMissing: true, makes a powerless channel missing", () { expect( - SassColor.rgb(0, 0, 0) - .toSpace(ColorSpace.hsl) - .isChannelMissing("hue"), - isTrue); + SassColor.rgb( + 0, + 0, + 0, + ).toSpace(ColorSpace.hsl).isChannelMissing("hue"), + isTrue, + ); }); test("with legacyMissing: false, makes a powerless channel zero", () { - var result = SassColor.rgb(0, 0, 0) - .toSpace(ColorSpace.hsl, legacyMissing: false); + var result = SassColor.rgb( + 0, + 0, + 0, + ).toSpace(ColorSpace.hsl, legacyMissing: false); expect(result.isChannelMissing("hue"), isFalse); expect(result.channel("hue"), equals(0)); }); test( - "even with legacyMissing: false, preserves missing channels for same " - "space", () { - expect( + "even with legacyMissing: false, preserves missing channels for same " + "space", + () { + expect( SassColor.rgb(0, null, 0) .toSpace(ColorSpace.rgb, legacyMissing: false) .isChannelMissing("green"), - isTrue); - }); + isTrue, + ); + }, + ); }); group("toGamut() brings the color into its gamut", () { setUp(() => value = parseValue("rgb(300 200 100)") as SassColor); test("with clip", () { - expect(value.toGamut(GamutMapMethod.clip), - equals(SassColor.rgb(255, 200, 100))); + expect( + value.toGamut(GamutMapMethod.clip), + equals(SassColor.rgb(255, 200, 100)), + ); }); test("with localMinde", () { // TODO: update - expect(value.toGamut(GamutMapMethod.localMinde), - equals(SassColor.rgb(255, 200, 100))); + expect( + value.toGamut(GamutMapMethod.localMinde), + equals(SassColor.rgb(255, 200, 100)), + ); }); }); group("changeRgb()", () { test("changes RGB values", () { - expect(value.changeRgb(red: 0xAA), - equals(SassColor.rgb(0xAA, 0x34, 0x56))); - expect(value.changeRgb(green: 0xAA), - equals(SassColor.rgb(0x12, 0xAA, 0x56))); - expect(value.changeRgb(blue: 0xAA), - equals(SassColor.rgb(0x12, 0x34, 0xAA))); - expect(value.changeRgb(alpha: 0.5), - equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5))); - expect(value.changeRgb(red: 0xAA, green: 0xAA, blue: 0xAA, alpha: 0.5), - equals(SassColor.rgb(0xAA, 0xAA, 0xAA, 0.5))); + expect( + value.changeRgb(red: 0xAA), + equals(SassColor.rgb(0xAA, 0x34, 0x56)), + ); + expect( + value.changeRgb(green: 0xAA), + equals(SassColor.rgb(0x12, 0xAA, 0x56)), + ); + expect( + value.changeRgb(blue: 0xAA), + equals(SassColor.rgb(0x12, 0x34, 0xAA)), + ); + expect( + value.changeRgb(alpha: 0.5), + equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5)), + ); + expect( + value.changeRgb(red: 0xAA, green: 0xAA, blue: 0xAA, alpha: 0.5), + equals(SassColor.rgb(0xAA, 0xAA, 0xAA, 0.5)), + ); }); test("allows in-gamut alpha", () { @@ -179,53 +210,82 @@ void main() { }); test("changeHsl() changes HSL values", () { - expect(value.changeHsl(hue: 120), - equals(SassColor.hsl(120, 65.3846153846154, 20.392156862745097))); - expect(value.changeHsl(saturation: 42), - equals(SassColor.hsl(210, 42, 20.392156862745097))); - expect(value.changeHsl(lightness: 42), - equals(SassColor.hsl(210, 65.3846153846154, 42))); expect( - value.changeHsl(alpha: 0.5), - equals( - SassColor.hsl(210, 65.3846153846154, 20.392156862745097, 0.5))); + value.changeHsl(hue: 120), + equals(SassColor.hsl(120, 65.3846153846154, 20.392156862745097)), + ); + expect( + value.changeHsl(saturation: 42), + equals(SassColor.hsl(210, 42, 20.392156862745097)), + ); + expect( + value.changeHsl(lightness: 42), + equals(SassColor.hsl(210, 65.3846153846154, 42)), + ); + expect( + value.changeHsl(alpha: 0.5), + equals(SassColor.hsl(210, 65.3846153846154, 20.392156862745097, 0.5)), + ); expect( - value.changeHsl(hue: 120, saturation: 42, lightness: 42, alpha: 0.5), - equals(SassColor.hsl(120, 42, 42, 0.5))); + value.changeHsl(hue: 120, saturation: 42, lightness: 42, alpha: 0.5), + equals(SassColor.hsl(120, 42, 42, 0.5)), + ); }); test("changeHwb() changes HWB values", () { - expect(value.changeHwb(hue: 120), - equals(SassColor.hwb(120, 7.0588235294117645, 66.27450980392157))); - expect(value.changeHwb(whiteness: 20), - equals(SassColor.hwb(210, 20, 66.27450980392157))); - expect(value.changeHwb(blackness: 42), - equals(SassColor.hwb(210, 7.0588235294117645, 42))); expect( - value.changeHwb(alpha: 0.5), - equals( - SassColor.hwb(210, 7.0588235294117645, 66.27450980392157, 0.5))); + value.changeHwb(hue: 120), + equals(SassColor.hwb(120, 7.0588235294117645, 66.27450980392157)), + ); + expect( + value.changeHwb(whiteness: 20), + equals(SassColor.hwb(210, 20, 66.27450980392157)), + ); + expect( + value.changeHwb(blackness: 42), + equals(SassColor.hwb(210, 7.0588235294117645, 42)), + ); + expect( + value.changeHwb(alpha: 0.5), + equals(SassColor.hwb(210, 7.0588235294117645, 66.27450980392157, 0.5)), + ); expect( - value.changeHwb(hue: 120, whiteness: 42, blackness: 42, alpha: 0.5), - equals(SassColor.hwb(120, 42, 42, 0.5))); - expect(value.changeHwb(whiteness: 50), - equals(SassColor.hwb(210, 43.0016863406408, 56.9983136593592))); + value.changeHwb(hue: 120, whiteness: 42, blackness: 42, alpha: 0.5), + equals(SassColor.hwb(120, 42, 42, 0.5)), + ); + expect( + value.changeHwb(whiteness: 50), + equals(SassColor.hwb(210, 43.0016863406408, 56.9983136593592)), + ); }); group("changeChannels()", () { test("changes RGB values", () { - expect(value.changeChannels({"red": 0xAA}), - equals(SassColor.rgb(0xAA, 0x34, 0x56))); - expect(value.changeChannels({"green": 0xAA}), - equals(SassColor.rgb(0x12, 0xAA, 0x56))); - expect(value.changeChannels({"blue": 0xAA}), - equals(SassColor.rgb(0x12, 0x34, 0xAA))); - expect(value.changeChannels({"alpha": 0.5}), - equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5))); expect( - value.changeChannels( - {"red": 0xAA, "green": 0xAA, "blue": 0xAA, "alpha": 0.5}), - equals(SassColor.rgb(0xAA, 0xAA, 0xAA, 0.5))); + value.changeChannels({"red": 0xAA}), + equals(SassColor.rgb(0xAA, 0x34, 0x56)), + ); + expect( + value.changeChannels({"green": 0xAA}), + equals(SassColor.rgb(0x12, 0xAA, 0x56)), + ); + expect( + value.changeChannels({"blue": 0xAA}), + equals(SassColor.rgb(0x12, 0x34, 0xAA)), + ); + expect( + value.changeChannels({"alpha": 0.5}), + equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5)), + ); + expect( + value.changeChannels({ + "red": 0xAA, + "green": 0xAA, + "blue": 0xAA, + "alpha": 0.5, + }), + equals(SassColor.rgb(0xAA, 0xAA, 0xAA, 0.5)), + ); }); test("allows in-gamut alpha", () { @@ -246,8 +306,10 @@ void main() { group("changeAlpha()", () { test("changes the alpha value", () { - expect(value.changeAlpha(0.5), - equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5))); + expect( + value.changeAlpha(0.5), + equals(SassColor.rgb(0x12, 0x34, 0x56, 0.5)), + ); }); test("allows valid alphas", () { @@ -278,8 +340,9 @@ void main() { group("a color with a missing channel", () { late SassColor value; - setUp(() => - value = parseValue("color(display-p3 0.3 0.4 none)") as SassColor); + setUp( + () => value = parseValue("color(display-p3 0.3 0.4 none)") as SassColor, + ); test("reports present channels as present", () { expect(value.isChannelMissing("red"), isFalse); @@ -368,24 +431,45 @@ void main() { test("doesn't equal an equivalent color", () { expect( - value, - isNot(equals(SassColor.xyzD65(0.07461544022446227, - 0.12417002656711021, 0.011301590030256693)))); + value, + isNot( + equals( + SassColor.xyzD65( + 0.07461544022446227, + 0.12417002656711021, + 0.011301590030256693, + ), + ), + ), + ); }); test("changeChannels() changes LCH values", () { - expect(value.changeChannels({"lightness": 30}), - equals(SassColor.lch(30, 63, 120))); - expect(value.changeChannels({"chroma": 30}), - equals(SassColor.lch(42, 30, 120))); expect( - value.changeChannels({"hue": 80}), equals(SassColor.lch(42, 63, 80))); - expect(value.changeChannels({"alpha": 0.5}), - equals(SassColor.lch(42, 63, 120, 0.5))); + value.changeChannels({"lightness": 30}), + equals(SassColor.lch(30, 63, 120)), + ); + expect( + value.changeChannels({"chroma": 30}), + equals(SassColor.lch(42, 30, 120)), + ); + expect( + value.changeChannels({"hue": 80}), + equals(SassColor.lch(42, 63, 80)), + ); + expect( + value.changeChannels({"alpha": 0.5}), + equals(SassColor.lch(42, 63, 120, 0.5)), + ); expect( - value.changeChannels( - {"lightness": 30, "chroma": 30, "hue": 30, "alpha": 0.5}), - equals(SassColor.lch(30, 30, 30, 0.5))); + value.changeChannels({ + "lightness": 30, + "chroma": 30, + "hue": 30, + "alpha": 0.5, + }), + equals(SassColor.lch(30, 30, 30, 0.5)), + ); }); }); diff --git a/test/dart_api/value/function_test.dart b/test/dart_api/value/function_test.dart index dee752533..3820a9877 100644 --- a/test/dart_api/value/function_test.dart +++ b/test/dart_api/value/function_test.dart @@ -40,12 +40,20 @@ void main() { }); test("can return a new function", () { - var css = compileString("a {b: call(foo(), 12)}", functions: [ - Callable("foo", "", (_) { - return SassFunction(Callable("bar", r"$arg", - (arguments) => SassNumber(arguments[0].assertNumber().value + 1))); - }) - ]); + var css = compileString( + "a {b: call(foo(), 12)}", + functions: [ + Callable("foo", "", (_) { + return SassFunction( + Callable( + "bar", + r"$arg", + (arguments) => SassNumber(arguments[0].assertNumber().value + 1), + ), + ); + }), + ], + ); expect(css, equalsIgnoringWhitespace("a { b: 13; }")); }); diff --git a/test/dart_api/value/list_test.dart b/test/dart_api/value/list_test.dart index bd283680a..f4e8fa58d 100644 --- a/test/dart_api/value/list_test.dart +++ b/test/dart_api/value/list_test.dart @@ -26,50 +26,73 @@ void main() { test("returns its contents as a list", () { expect( - value.asList, - equals([ - SassString("a", quotes: false), - SassString("b", quotes: false), - SassString("c", quotes: false) - ])); + value.asList, + equals([ + SassString("a", quotes: false), + SassString("b", quotes: false), + SassString("c", quotes: false), + ]), + ); }); test("equals the same list", () { expect( - value, - equalsWithHash(SassList([ + value, + equalsWithHash( + SassList([ SassString("a", quotes: false), SassString("b", quotes: false), - SassString("c", quotes: false) - ], ListSeparator.comma))); + SassString("c", quotes: false), + ], ListSeparator.comma), + ), + ); }); test("doesn't equal a value with different metadata", () { expect( - value, - isNot(equals(SassList([ - SassString("a", quotes: false), - SassString("b", quotes: false), - SassString("c", quotes: false) - ], ListSeparator.space)))); + value, + isNot( + equals( + SassList([ + SassString("a", quotes: false), + SassString("b", quotes: false), + SassString("c", quotes: false), + ], ListSeparator.space), + ), + ), + ); expect( - value, - isNot(equals(SassList([ - SassString("a", quotes: false), - SassString("b", quotes: false), - SassString("c", quotes: false) - ], ListSeparator.comma, brackets: true)))); + value, + isNot( + equals( + SassList( + [ + SassString("a", quotes: false), + SassString("b", quotes: false), + SassString("c", quotes: false), + ], + ListSeparator.comma, + brackets: true, + ), + ), + ), + ); }); test("doesn't equal a value with different contents", () { expect( - value, - isNot(equals(SassList([ - SassString("a", quotes: false), - SassString("x", quotes: false), - SassString("c", quotes: false) - ], ListSeparator.comma)))); + value, + isNot( + equals( + SassList([ + SassString("a", quotes: false), + SassString("x", quotes: false), + SassString("c", quotes: false), + ], ListSeparator.comma), + ), + ), + ); }); group("sassIndexToListIndex()", () { @@ -87,22 +110,31 @@ void main() { test("rejects a non-number", () { expect( - () => value.sassIndexToListIndex(SassString("foo", quotes: false)), - throwsSassScriptException); + () => value.sassIndexToListIndex(SassString("foo", quotes: false)), + throwsSassScriptException, + ); }); test("rejects a non-integer", () { - expect(() => value.sassIndexToListIndex(SassNumber(1.1)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(1.1)), + throwsSassScriptException, + ); }); test("rejects invalid indices", () { - expect(() => value.sassIndexToListIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(4)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(-4)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(4)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(-4)), + throwsSassScriptException, + ); }); }); @@ -119,8 +151,10 @@ void main() { }); test("a slash-separated list is slash-separated", () { - expect(parseValue("list.slash(a, b, c)").separator, - equals(ListSeparator.slash)); + expect( + parseValue("list.slash(a, b, c)").separator, + equals(ListSeparator.slash), + ); }); test("a space-separated list is space-separated", () { @@ -188,12 +222,18 @@ void main() { }); test("sassIndexToListIndex() rejects invalid indices", () { - expect(() => value.sassIndexToListIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(1)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(-1)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(1)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(-1)), + throwsSassScriptException, + ); }); }); @@ -225,12 +265,18 @@ void main() { }); test("rejects invalid indices", () { - expect(() => value.sassIndexToListIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(2)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(-2)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(2)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(-2)), + throwsSassScriptException, + ); }); }); }); @@ -252,8 +298,9 @@ void main() { group("new SassList()", () { test("creates a list with the given contents and metadata", () { - var list = - SassList([SassString("a", quotes: false)], ListSeparator.space); + var list = SassList([ + SassString("a", quotes: false), + ], ListSeparator.space); expect(list.asList, equals([SassString("a", quotes: false)])); expect(list.separator, equals(ListSeparator.space)); expect(list.hasBrackets, isFalse); @@ -261,27 +308,37 @@ void main() { test("can create a bracketed list", () { expect( - SassList([SassString("a", quotes: false)], ListSeparator.space, - brackets: true) - .hasBrackets, - isTrue); + SassList( + [SassString("a", quotes: false)], + ListSeparator.space, + brackets: true, + ).hasBrackets, + isTrue, + ); }); test("can create a short list with an undecided separator", () { expect( - SassList([SassString("a", quotes: false)], ListSeparator.undecided) - .separator, - equals(ListSeparator.undecided)); - expect(SassList([], ListSeparator.undecided).separator, - equals(ListSeparator.undecided)); + SassList([ + SassString("a", quotes: false), + ], ListSeparator.undecided) + .separator, + equals(ListSeparator.undecided), + ); + expect( + SassList([], ListSeparator.undecided).separator, + equals(ListSeparator.undecided), + ); }); test("can't create a long list with an undecided separator", () { expect( - () => SassList( - [SassString("a", quotes: false), SassString("b", quotes: false)], - ListSeparator.undecided), - throwsArgumentError); + () => SassList([ + SassString("a", quotes: false), + SassString("b", quotes: false), + ], ListSeparator.undecided), + throwsArgumentError, + ); }); }); } diff --git a/test/dart_api/value/map_test.dart b/test/dart_api/value/map_test.dart index a6c82d442..4508fa533 100644 --- a/test/dart_api/value/map_test.dart +++ b/test/dart_api/value/map_test.dart @@ -22,26 +22,28 @@ void main() { test("returns its contents as a map", () { expect( - value.contents, - equals({ - SassString("a", quotes: false): SassString("b", quotes: false), - SassString("c", quotes: false): SassString("d", quotes: false) - })); + value.contents, + equals({ + SassString("a", quotes: false): SassString("b", quotes: false), + SassString("c", quotes: false): SassString("d", quotes: false), + }), + ); }); test("returns its contents as a list", () { expect( - value.asList, - equals([ - SassList([ - SassString("a", quotes: false), - SassString("b", quotes: false) - ], ListSeparator.space), - SassList([ - SassString("c", quotes: false), - SassString("d", quotes: false) - ], ListSeparator.space) - ])); + value.asList, + equals([ + SassList([ + SassString("a", quotes: false), + SassString("b", quotes: false), + ], ListSeparator.space), + SassList([ + SassString("c", quotes: false), + SassString("d", quotes: false), + ], ListSeparator.space), + ]), + ); }); group("sassIndexToListIndex()", () { @@ -56,74 +58,108 @@ void main() { }); test("rejects invalid indices", () { - expect(() => value.sassIndexToListIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(3)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(-3)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(3)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(-3)), + throwsSassScriptException, + ); }); }); test("equals the same map", () { expect( - value, - equalsWithHash(SassMap({ + value, + equalsWithHash( + SassMap({ SassString("a", quotes: false): SassString("b", quotes: false), - SassString("c", quotes: false): SassString("d", quotes: false) - }))); + SassString("c", quotes: false): SassString("d", quotes: false), + }), + ), + ); }); test("doesn't equal the equivalent list", () { expect( - value, - isNot(equals(SassList([ - SassList([ - SassString("a", quotes: false), - SassString("b", quotes: false) - ], ListSeparator.space), + value, + isNot( + equals( SassList([ - SassString("c", quotes: false), - SassString("d", quotes: false) - ], ListSeparator.space) - ], ListSeparator.comma)))); + SassList([ + SassString("a", quotes: false), + SassString("b", quotes: false), + ], ListSeparator.space), + SassList([ + SassString("c", quotes: false), + SassString("d", quotes: false), + ], ListSeparator.space), + ], ListSeparator.comma), + ), + ), + ); }); group("doesn't equal a map with", () { test("a different value", () { expect( - value, - isNot(equals(SassMap({ - SassString("a", quotes: false): SassString("x", quotes: false), - SassString("c", quotes: false): SassString("d", quotes: false) - })))); + value, + isNot( + equals( + SassMap({ + SassString("a", quotes: false): SassString("x", quotes: false), + SassString("c", quotes: false): SassString("d", quotes: false), + }), + ), + ), + ); }); test("a different key", () { expect( - value, - isNot(equals(SassMap({ - SassString("a", quotes: false): SassString("b", quotes: false), - SassString("x", quotes: false): SassString("d", quotes: false) - })))); + value, + isNot( + equals( + SassMap({ + SassString("a", quotes: false): SassString("b", quotes: false), + SassString("x", quotes: false): SassString("d", quotes: false), + }), + ), + ), + ); }); test("a missing pair", () { expect( - value, - isNot(equals(SassMap({ - SassString("a", quotes: false): SassString("b", quotes: false) - })))); + value, + isNot( + equals( + SassMap({ + SassString("a", quotes: false): SassString("b", quotes: false), + }), + ), + ), + ); }); test("an additional pair", () { expect( - value, - isNot(equals(SassMap({ - SassString("a", quotes: false): SassString("b", quotes: false), - SassString("c", quotes: false): SassString("d", quotes: false), - SassString("e", quotes: false): SassString("f", quotes: false) - })))); + value, + isNot( + equals( + SassMap({ + SassString("a", quotes: false): SassString("b", quotes: false), + SassString("c", quotes: false): SassString("d", quotes: false), + SassString("e", quotes: false): SassString("f", quotes: false), + }), + ), + ), + ); }); }); @@ -163,12 +199,18 @@ void main() { }); test("sassIndexToListIndex() rejects invalid indices", () { - expect(() => value.sassIndexToListIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(1)), - throwsSassScriptException); - expect(() => value.sassIndexToListIndex(SassNumber(-1)), - throwsSassScriptException); + expect( + () => value.sassIndexToListIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(1)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToListIndex(SassNumber(-1)), + throwsSassScriptException, + ); }); }); @@ -177,11 +219,12 @@ void main() { }); test("new SassMap() creates a map with the given contents", () { - var list = SassMap( - {SassString("a", quotes: false): SassString("b", quotes: false)}); + var list = SassMap({ + SassString("a", quotes: false): SassString("b", quotes: false), + }); expect( - list.contents, - equals( - {SassString("a", quotes: false): SassString("b", quotes: false)})); + list.contents, + equals({SassString("a", quotes: false): SassString("b", quotes: false)}), + ); }); } diff --git a/test/dart_api/value/number_test.dart b/test/dart_api/value/number_test.dart index 0a9c7d203..fbaa9c0cb 100644 --- a/test/dart_api/value/number_test.dart +++ b/test/dart_api/value/number_test.dart @@ -44,19 +44,29 @@ void main() { test("can be coerced to any units", () { expect( - value.coerce(["abc"], ["def"]), - equals(SassNumber.withUnits(123, - numeratorUnits: ["abc"], denominatorUnits: ["def"]))); + value.coerce(["abc"], ["def"]), + equals( + SassNumber.withUnits( + 123, + numeratorUnits: ["abc"], + denominatorUnits: ["def"], + ), + ), + ); }); test("can be converted to unitless", () { - expect(value.convertToMatch(SassNumber(456)), - equals(SassNumber.withUnits(123))); + expect( + value.convertToMatch(SassNumber(456)), + equals(SassNumber.withUnits(123)), + ); }); test("can't be converted to a unit", () { - expect(() => value.convertToMatch(SassNumber(456, "px")), - throwsSassScriptException); + expect( + () => value.convertToMatch(SassNumber(456, "px")), + throwsSassScriptException, + ); }); test("can coerce its value to unitless", () { @@ -72,8 +82,10 @@ void main() { }); test("can't convert its value to any units", () { - expect(() => value.convertValueToMatch(SassNumber(456, "px")), - throwsSassScriptException); + expect( + () => value.convertValueToMatch(SassNumber(456, "px")), + throwsSassScriptException, + ); }); test("is compatible with any unit", () { @@ -99,26 +111,34 @@ void main() { test("equals the same number within precision tolerance", () { expect( - value, - equalsWithHash( - SassNumber(123 + math.pow(10, -SassNumber.precision - 2)))); + value, + equalsWithHash( + SassNumber(123 + math.pow(10, -SassNumber.precision - 2)), + ), + ); expect( - value, - equalsWithHash( - SassNumber(123 - math.pow(10, -SassNumber.precision - 2)))); + value, + equalsWithHash( + SassNumber(123 - math.pow(10, -SassNumber.precision - 2)), + ), + ); }); test("doesn't equal a different number", () { expect(value, isNot(equals(SassNumber(124)))); expect(value, isNot(equals(SassNumber(122)))); expect( - value, - isNot(equals( - SassNumber(123 + math.pow(10, -SassNumber.precision - 1))))); + value, + isNot( + equals(SassNumber(123 + math.pow(10, -SassNumber.precision - 1))), + ), + ); expect( - value, - isNot(equals( - SassNumber(123 - math.pow(10, -SassNumber.precision - 1))))); + value, + isNot( + equals(SassNumber(123 - math.pow(10, -SassNumber.precision - 1))), + ), + ); }); test("doesn't equal a number with units", () { @@ -171,17 +191,21 @@ void main() { test("equals the same number", () { expect( - value, - equalsWithHash( - SassNumber(123 + math.pow(10, -SassNumber.precision - 2)))); + value, + equalsWithHash( + SassNumber(123 + math.pow(10, -SassNumber.precision - 2)), + ), + ); }); test("equals the same number within precision tolerance", () { expect(value, equalsWithHash(SassNumber(123))); expect( - value, - equalsWithHash( - SassNumber(123 - math.pow(10, -SassNumber.precision - 2)))); + value, + equalsWithHash( + SassNumber(123 - math.pow(10, -SassNumber.precision - 2)), + ), + ); }); group("valueInRange()", () { @@ -230,19 +254,25 @@ void main() { }); test("can't be converted to unitless", () { - expect(() => value.convertToMatch(SassNumber(456)), - throwsSassScriptException); + expect( + () => value.convertToMatch(SassNumber(456)), + throwsSassScriptException, + ); }); test("can be converted to compatible units", () { expect(value.convertToMatch(SassNumber(456, "px")), equals(value)); - expect(value.convertToMatch(SassNumber(456, "in")), - equals(SassNumber(1.28125, "in"))); + expect( + value.convertToMatch(SassNumber(456, "in")), + equals(SassNumber(1.28125, "in")), + ); }); test("can't be converted to incompatible units", () { - expect(() => value.convertToMatch(SassNumber(456, "abc")), - throwsSassScriptException); + expect( + () => value.convertToMatch(SassNumber(456, "abc")), + throwsSassScriptException, + ); }); test("can coerce its value to unitless", () { @@ -259,8 +289,10 @@ void main() { }); test("can't convert its value to unitless", () { - expect(() => value.convertValueToMatch(SassNumber(456)), - throwsSassScriptException); + expect( + () => value.convertValueToMatch(SassNumber(456)), + throwsSassScriptException, + ); }); test("can convert its value to compatible units", () { @@ -269,8 +301,10 @@ void main() { }); test("can't convert its value to incompatible units", () { - expect(() => value.convertValueToMatch(SassNumber(456, "abc")), - throwsSassScriptException); + expect( + () => value.convertValueToMatch(SassNumber(456, "abc")), + throwsSassScriptException, + ); }); test("is compatible with the same unit", () { @@ -301,15 +335,25 @@ void main() { test("doesn't equal a number with different units", () { expect(value, isNot(equals(SassNumber(123, "abc")))); expect( - value, - isNot( - equals(SassNumber.withUnits(123, numeratorUnits: ["px", "px"])))); + value, + isNot(equals(SassNumber.withUnits(123, numeratorUnits: ["px", "px"]))), + ); + expect( + value, + isNot( + equals( + SassNumber.withUnits( + 123, + numeratorUnits: ["px"], + denominatorUnits: ["abc"], + ), + ), + ), + ); expect( - value, - isNot(equals(SassNumber.withUnits(123, - numeratorUnits: ["px"], denominatorUnits: ["abc"])))); - expect(value, - isNot(equals(SassNumber.withUnits(123, denominatorUnits: ["px"])))); + value, + isNot(equals(SassNumber.withUnits(123, denominatorUnits: ["px"]))), + ); }); }); @@ -337,17 +381,34 @@ void main() { test("can be coerced to compatible units", () { expect(value.coerce(["px"], ["ms"]), equals(value)); expect( - value.coerce(["in"], ["s"]), - equals(SassNumber.withUnits(256.25, - numeratorUnits: ["in"], denominatorUnits: ["s"]))); + value.coerce(["in"], ["s"]), + equals( + SassNumber.withUnits( + 256.25, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + ); }); test("can coerce to match another number", () { expect( - value.coerceToMatch(SassNumber.withUnits(456, - numeratorUnits: ["in"], denominatorUnits: ["s"])), - equals(SassNumber.withUnits(256.25, - numeratorUnits: ["in"], denominatorUnits: ["s"]))); + value.coerceToMatch( + SassNumber.withUnits( + 456, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + equals( + SassNumber.withUnits( + 256.25, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + ); }); test("can't be coerced to incompatible units", () { @@ -355,20 +416,39 @@ void main() { }); test("can't be converted to unitless", () { - expect(() => value.convertToMatch(SassNumber(456)), - throwsSassScriptException); + expect( + () => value.convertToMatch(SassNumber(456)), + throwsSassScriptException, + ); }); test("can be converted to compatible units", () { expect( - value.convertToMatch(SassNumber.withUnits(456, - numeratorUnits: ["px"], denominatorUnits: ["ms"])), - equals(value)); + value.convertToMatch( + SassNumber.withUnits( + 456, + numeratorUnits: ["px"], + denominatorUnits: ["ms"], + ), + ), + equals(value), + ); expect( - value.convertToMatch(SassNumber.withUnits(456, - numeratorUnits: ["in"], denominatorUnits: ["s"])), - equals(SassNumber.withUnits(256.25, - numeratorUnits: ["in"], denominatorUnits: ["s"]))); + value.convertToMatch( + SassNumber.withUnits( + 456, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + equals( + SassNumber.withUnits( + 256.25, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + ); }); test("can coerce its value to unitless", () { @@ -385,24 +465,40 @@ void main() { }); test("can't convert its value to unitless", () { - expect(() => value.convertValueToMatch(SassNumber(456)), - throwsSassScriptException); + expect( + () => value.convertValueToMatch(SassNumber(456)), + throwsSassScriptException, + ); }); test("can convert its value to compatible units", () { expect( - value.convertValueToMatch(SassNumber.withUnits(456, - numeratorUnits: ["px"], denominatorUnits: ["ms"])), - equals(24.6)); + value.convertValueToMatch( + SassNumber.withUnits( + 456, + numeratorUnits: ["px"], + denominatorUnits: ["ms"], + ), + ), + equals(24.6), + ); expect( - value.convertValueToMatch(SassNumber.withUnits(456, - numeratorUnits: ["in"], denominatorUnits: ["s"])), - equals(256.25)); + value.convertValueToMatch( + SassNumber.withUnits( + 456, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + equals(256.25), + ); }); test("can't convert its value to incompatible units", () { - expect(() => value.convertValueToMatch(SassNumber(456, "abc")), - throwsSassScriptException); + expect( + () => value.convertValueToMatch(SassNumber(456, "abc")), + throwsSassScriptException, + ); }); test("is incompatible with the numerator unit", () { @@ -415,16 +511,28 @@ void main() { test("equals the same number", () { expect( - value, - equalsWithHash(SassNumber.withUnits(24.6, - numeratorUnits: ["px"], denominatorUnits: ["ms"]))); + value, + equalsWithHash( + SassNumber.withUnits( + 24.6, + numeratorUnits: ["px"], + denominatorUnits: ["ms"], + ), + ), + ); }); test("equals an equivalent number", () { expect( - value, - equalsWithHash(SassNumber.withUnits(256.25, - numeratorUnits: ["in"], denominatorUnits: ["s"]))); + value, + equalsWithHash( + SassNumber.withUnits( + 256.25, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + ); }); test("doesn't equal a unitless number", () { @@ -433,16 +541,34 @@ void main() { test("doesn't equal a number with different units", () { expect(value, isNot(equals(SassNumber(24.6, "px")))); - expect(value, - isNot(equals(SassNumber.withUnits(24.6, denominatorUnits: ["ms"])))); expect( - value, - isNot(equals(SassNumber.withUnits(24.6, - numeratorUnits: ["ms"], denominatorUnits: ["px"])))); + value, + isNot(equals(SassNumber.withUnits(24.6, denominatorUnits: ["ms"]))), + ); + expect( + value, + isNot( + equals( + SassNumber.withUnits( + 24.6, + numeratorUnits: ["ms"], + denominatorUnits: ["px"], + ), + ), + ), + ); expect( - value, - isNot(equals(SassNumber.withUnits(24.6, - numeratorUnits: ["in"], denominatorUnits: ["s"])))); + value, + isNot( + equals( + SassNumber.withUnits( + 24.6, + numeratorUnits: ["in"], + denominatorUnits: ["s"], + ), + ), + ), + ); }); }); @@ -468,8 +594,11 @@ void main() { }); test("can create a number with units", () { - var number = SassNumber.withUnits(123.456, - numeratorUnits: ["px", "em"], denominatorUnits: ["ms", "kHz"]); + var number = SassNumber.withUnits( + 123.456, + numeratorUnits: ["px", "em"], + denominatorUnits: ["ms", "kHz"], + ); expect(number.value, equals(123.456)); expect(number.numeratorUnits, equals(["px", "em"])); expect(number.denominatorUnits, equals(["ms", "kHz"])); diff --git a/test/dart_api/value/string_test.dart b/test/dart_api/value/string_test.dart index 702015e58..16558acd1 100644 --- a/test/dart_api/value/string_test.dart +++ b/test/dart_api/value/string_test.dart @@ -67,22 +67,32 @@ void main() { }); test("rejects a non-number", () { - expect(() => value.sassIndexToStringIndex(SassString("foo")), - throwsSassScriptException); + expect( + () => value.sassIndexToStringIndex(SassString("foo")), + throwsSassScriptException, + ); }); test("rejects a non-integer", () { - expect(() => value.sassIndexToStringIndex(SassNumber(1.1)), - throwsSassScriptException); + expect( + () => value.sassIndexToStringIndex(SassNumber(1.1)), + throwsSassScriptException, + ); }); test("rejects invalid indices", () { - expect(() => value.sassIndexToStringIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToStringIndex(SassNumber(7)), - throwsSassScriptException); - expect(() => value.sassIndexToStringIndex(SassNumber(-7)), - throwsSassScriptException); + expect( + () => value.sassIndexToStringIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToStringIndex(SassNumber(7)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToStringIndex(SassNumber(-7)), + throwsSassScriptException, + ); }); }); @@ -106,22 +116,32 @@ void main() { }); test("rejects a non-number", () { - expect(() => value.sassIndexToRuneIndex(SassString("foo")), - throwsSassScriptException); + expect( + () => value.sassIndexToRuneIndex(SassString("foo")), + throwsSassScriptException, + ); }); test("rejects a non-integer", () { - expect(() => value.sassIndexToRuneIndex(SassNumber(1.1)), - throwsSassScriptException); + expect( + () => value.sassIndexToRuneIndex(SassNumber(1.1)), + throwsSassScriptException, + ); }); test("rejects invalid indices", () { - expect(() => value.sassIndexToRuneIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToRuneIndex(SassNumber(7)), - throwsSassScriptException); - expect(() => value.sassIndexToRuneIndex(SassNumber(-7)), - throwsSassScriptException); + expect( + () => value.sassIndexToRuneIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToRuneIndex(SassNumber(7)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToRuneIndex(SassNumber(-7)), + throwsSassScriptException, + ); }); }); }); @@ -170,12 +190,18 @@ void main() { }); test("rejects invalid indices", () { - expect(() => value.sassIndexToStringIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToStringIndex(SassNumber(6)), - throwsSassScriptException); - expect(() => value.sassIndexToStringIndex(SassNumber(-6)), - throwsSassScriptException); + expect( + () => value.sassIndexToStringIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToStringIndex(SassNumber(6)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToStringIndex(SassNumber(-6)), + throwsSassScriptException, + ); }); }); @@ -197,12 +223,18 @@ void main() { }); test("rejects invalid indices", () { - expect(() => value.sassIndexToRuneIndex(SassNumber(0)), - throwsSassScriptException); - expect(() => value.sassIndexToRuneIndex(SassNumber(6)), - throwsSassScriptException); - expect(() => value.sassIndexToRuneIndex(SassNumber(-6)), - throwsSassScriptException); + expect( + () => value.sassIndexToRuneIndex(SassNumber(0)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToRuneIndex(SassNumber(6)), + throwsSassScriptException, + ); + expect( + () => value.sassIndexToRuneIndex(SassNumber(-6)), + throwsSassScriptException, + ); }); }); }); diff --git a/test/dart_api/value/utils.dart b/test/dart_api/value/utils.dart index 37954509e..c3bea0992 100644 --- a/test/dart_api/value/utils.dart +++ b/test/dart_api/value/utils.dart @@ -10,30 +10,41 @@ import 'package:sass/src/exception.dart'; /// Parses [source] by way of a function call. Value parseValue(String source) { late Value value; - compileString(""" - @use "sass:list"; - @use "sass:math"; + compileString( + """ + @use "sass:list"; + @use "sass:math"; - a {b: foo(($source))} - """, functions: [ - Callable("foo", r"$arg", expectAsync1((arguments) { - expect(arguments, hasLength(1)); - value = arguments.first; - return sassNull; - })) - ]); + a {b: foo(($source))} + """, + functions: [ + Callable( + "foo", + r"$arg", + expectAsync1((arguments) { + expect(arguments, hasLength(1)); + value = arguments.first; + return sassNull; + }), + ), + ], + ); return value; } /// A matcher that asserts that a function throws a [SassScriptException]. -final throwsSassScriptException = - throwsA(const TypeMatcher()); +final throwsSassScriptException = throwsA( + const TypeMatcher(), +); /// Like [equals], but asserts that the hash codes of the values are the same as /// well. Matcher equalsWithHash(Object expected) => predicate((actual) { expect(actual, equals(expected)); - expect(actual.hashCode, equals(expected.hashCode), - reason: "Expected $actual's hash code to equal $expected's."); + expect( + actual.hashCode, + equals(expected.hashCode), + reason: "Expected $actual's hash code to equal $expected's.", + ); return true; }); diff --git a/test/dart_api_test.dart b/test/dart_api_test.dart index 5337e4903..242b62ab0 100644 --- a/test/dart_api_test.dart +++ b/test/dart_api_test.dart @@ -23,22 +23,29 @@ void main() { await d.dir("subdir", [d.file("subtest.scss", "a {b: c}")]).create(); await d.file("test.scss", '@use "subtest.scss";').create(); - var css = compile(d.path("test.scss"), - importers: [FilesystemImporter(d.path('subdir'))]); + var css = compile( + d.path("test.scss"), + importers: [FilesystemImporter(d.path('subdir'))], + ); expect(css, equals("a {\n b: c;\n}")); }); test("are checked in order", () async { - await d - .dir("first", [d.file("other.scss", "a {b: from-first}")]).create(); - await d - .dir("second", [d.file("other.scss", "a {b: from-second}")]).create(); + await d.dir("first", [ + d.file("other.scss", "a {b: from-first}"), + ]).create(); + await d.dir("second", [ + d.file("other.scss", "a {b: from-second}"), + ]).create(); await d.file("test.scss", '@use "other";').create(); - var css = compile(d.path("test.scss"), importers: [ - FilesystemImporter(d.path('first')), - FilesystemImporter(d.path('second')) - ]); + var css = compile( + d.path("test.scss"), + importers: [ + FilesystemImporter(d.path('first')), + FilesystemImporter(d.path('second')), + ], + ); expect(css, equals("a {\n b: from-first;\n}")); }); }); @@ -77,14 +84,18 @@ void main() { }); test("are checked in order", () async { - await d - .dir("first", [d.file("other.scss", "a {b: from-first}")]).create(); - await d - .dir("second", [d.file("other.scss", "a {b: from-second}")]).create(); + await d.dir("first", [ + d.file("other.scss", "a {b: from-first}"), + ]).create(); + await d.dir("second", [ + d.file("other.scss", "a {b: from-second}"), + ]).create(); await d.file("test.scss", '@use "other";').create(); - var css = compile(d.path("test.scss"), - loadPaths: [d.path('first'), d.path('second')]); + var css = compile( + d.path("test.scss"), + loadPaths: [d.path('first'), d.path('second')], + ); expect(css, equals("a {\n b: from-first;\n}")); }); }); @@ -94,8 +105,9 @@ void main() { await d.dir("subdir", [d.file("test.scss", "a {b: 1 + 2}")]).create(); await d.file("test.scss", '@use "package:fake_package/test";').create(); - var config = - PackageConfig([Package('fake_package', p.toUri(d.path('subdir/')))]); + var config = PackageConfig([ + Package('fake_package', p.toUri(d.path('subdir/'))), + ]); var css = compile(d.path("test.scss"), packageConfig: config); expect(css, equals("a {\n b: 3;\n}")); @@ -108,8 +120,9 @@ void main() { ]).create(); await d.file("test.scss", '@use "package:fake_package/test";').create(); - var config = - PackageConfig([Package('fake_package', p.toUri(d.path('subdir/')))]); + var config = PackageConfig([ + Package('fake_package', p.toUri(d.path('subdir/'))), + ]); var css = compile(d.path("test.scss"), packageConfig: config); expect(css, equals("a {\n b: 3;\n}")); @@ -121,80 +134,108 @@ void main() { .create(); expect( - () => compile(d.path("test.scss"), packageConfig: PackageConfig([])), - throwsA(const TypeMatcher())); + () => compile(d.path("test.scss"), packageConfig: PackageConfig([])), + throwsA(const TypeMatcher()), + ); }); }); group("import precedence", () { test("relative imports take precedence over importers", () async { - await d.dir( - "subdir", [d.file("other.scss", "a {b: from-load-path}")]).create(); + await d.dir("subdir", [ + d.file("other.scss", "a {b: from-load-path}"), + ]).create(); await d.file("other.scss", "a {b: from-relative}").create(); await d.file("test.scss", '@use "other";').create(); - var css = compile(d.path("test.scss"), - importers: [FilesystemImporter(d.path('subdir'))]); + var css = compile( + d.path("test.scss"), + importers: [FilesystemImporter(d.path('subdir'))], + ); expect(css, equals("a {\n b: from-relative;\n}")); }); test( "the original importer takes precedence over other importers for " "relative imports", () async { - await d.dir( - "original", [d.file("other.scss", "a {b: from-original}")]).create(); - await d - .dir("other", [d.file("other.scss", "a {b: from-other}")]).create(); + await d.dir("original", [ + d.file("other.scss", "a {b: from-original}"), + ]).create(); + await d.dir("other", [ + d.file("other.scss", "a {b: from-other}"), + ]).create(); - var css = compileString('@use "other";', - importer: FilesystemImporter(d.path('original')), - url: p.toUri(d.path('original/test.scss')), - importers: [FilesystemImporter(d.path('other'))]); + var css = compileString( + '@use "other";', + importer: FilesystemImporter(d.path('original')), + url: p.toUri(d.path('original/test.scss')), + importers: [FilesystemImporter(d.path('other'))], + ); expect(css, equals("a {\n b: from-original;\n}")); }); test("importer order is preserved for absolute imports", () { - var css = compileString('@use "second:other";', importers: [ - TestImporter((url) => url.scheme == 'first' ? url : null, - (url) => ImporterResult('a {from: first}', indented: false)), - // This importer should only be invoked once, because when the - // "first:other" import is resolved it should be passed to the first - // importer first despite being in the second importer's file. - TestImporter( - expectAsync1((url) => url.scheme == 'second' ? url : null, - count: 1), - (url) => ImporterResult('@use "first:other";', indented: false)), - ]); + var css = compileString( + '@use "second:other";', + importers: [ + TestImporter( + (url) => url.scheme == 'first' ? url : null, + (url) => ImporterResult('a {from: first}', indented: false), + ), + // This importer should only be invoked once, because when the + // "first:other" import is resolved it should be passed to the first + // importer first despite being in the second importer's file. + TestImporter( + expectAsync1( + (url) => url.scheme == 'second' ? url : null, + count: 1, + ), + (url) => ImporterResult('@use "first:other";', indented: false), + ), + ], + ); expect(css, equals("a {\n from: first;\n}")); }); test("importers take precedence over load paths", () async { - await d.dir("load-path", - [d.file("other.scss", "a {b: from-load-path}")]).create(); - await d.dir( - "importer", [d.file("other.scss", "a {b: from-importer}")]).create(); + await d.dir("load-path", [ + d.file("other.scss", "a {b: from-load-path}"), + ]).create(); + await d.dir("importer", [ + d.file("other.scss", "a {b: from-importer}"), + ]).create(); await d.file("test.scss", '@use "other";').create(); - var css = compile(d.path("test.scss"), - importers: [FilesystemImporter(d.path('importer'))], - loadPaths: [d.path('load-path')]); + var css = compile( + d.path("test.scss"), + importers: [FilesystemImporter(d.path('importer'))], + loadPaths: [d.path('load-path')], + ); expect(css, equals("a {\n b: from-importer;\n}")); }); test("importers take precedence over packageConfig", () async { - await d.dir("package", - [d.file("other.scss", "a {b: from-package-config}")]).create(); - await d.dir( - "importer", [d.file("other.scss", "a {b: from-importer}")]).create(); + await d.dir("package", [ + d.file("other.scss", "a {b: from-package-config}"), + ]).create(); + await d.dir("importer", [ + d.file("other.scss", "a {b: from-importer}"), + ]).create(); await d.file("test.scss", '@use "package:fake_package/other";').create(); - var css = compile(d.path("test.scss"), - importers: [ - PackageImporter(PackageConfig( - [Package('fake_package', p.toUri(d.path('importer/')))])) - ], - packageConfig: PackageConfig( - [Package('fake_package', p.toUri(d.path('package/')))])); + var css = compile( + d.path("test.scss"), + importers: [ + PackageImporter( + PackageConfig([ + Package('fake_package', p.toUri(d.path('importer/'))), + ]), + ), + ], + packageConfig: PackageConfig([ + Package('fake_package', p.toUri(d.path('package/'))), + ]), + ); expect(css, equals("a {\n b: from-importer;\n}")); }); }); @@ -202,39 +243,57 @@ void main() { group("charset", () { group("= true", () { test("doesn't emit @charset for a pure-ASCII stylesheet", () { - expect(compileString("a {b: c}"), equals(""" -a { - b: c; -}""")); + expect( + compileString("a {b: c}"), + equalsIgnoringWhitespace(""" + a { + b: c; + } + """), + ); }); test("emits @charset with expanded output", () async { - expect(compileString("a {b: 👭}"), equals(""" -@charset "UTF-8"; -a { - b: 👭; -}""")); + expect( + compileString("a {b: 👭}"), + equalsIgnoringWhitespace(""" + @charset "UTF-8"; + a { + b: 👭; + } + """), + ); }); test("emits a BOM with compressed output", () async { - expect(compileString("a {b: 👭}", style: OutputStyle.compressed), - equals("\u{FEFF}a{b:👭}")); + expect( + compileString("a {b: 👭}", style: OutputStyle.compressed), + equals("\u{FEFF}a{b:👭}"), + ); }); }); group("= false", () { test("doesn't emit @charset with expanded output", () async { - expect(compileString("a {b: 👭}", charset: false), equals(""" -a { - b: 👭; -}""")); + expect( + compileString("a {b: 👭}", charset: false), + equalsIgnoringWhitespace(""" + a { + b: 👭; + } + """), + ); }); test("emits a BOM with compressed output", () async { expect( - compileString("a {b: 👭}", - charset: false, style: OutputStyle.compressed), - equals("a{b:👭}")); + compileString( + "a {b: 👭}", + charset: false, + style: OutputStyle.compressed, + ), + equals("a{b:👭}"), + ); }); }); }); @@ -263,8 +322,10 @@ a { test("contains a URL loaded via @import", () async { await d.file("_other.scss", "a {b: c}").create(); await d.file("input.scss", "@import 'other';").create(); - var result = compileToResult(d.path('input.scss'), - silenceDeprecations: [Deprecation.import]); + var result = compileToResult( + d.path('input.scss'), + silenceDeprecations: [Deprecation.import], + ); expect(result.loadedUrls, contains(p.toUri(d.path('_other.scss')))); }); @@ -301,27 +362,36 @@ a { @use 'sass:meta'; @include meta.load-css('venus'); """).create(); - var result = compileToResult(d.path('mercury.scss'), - silenceDeprecations: [Deprecation.import]); + var result = compileToResult( + d.path('mercury.scss'), + silenceDeprecations: [Deprecation.import], + ); expect( - result.loadedUrls, - unorderedEquals([ - p.toUri(d.path('mercury.scss')), - p.toUri(d.path('_venus.scss')), - p.toUri(d.path('_earth.scss')), - p.toUri(d.path('_mars.scss')), - p.toUri(d.path('_jupiter.scss')) - ])); + result.loadedUrls, + unorderedEquals([ + p.toUri(d.path('mercury.scss')), + p.toUri(d.path('_venus.scss')), + p.toUri(d.path('_earth.scss')), + p.toUri(d.path('_mars.scss')), + p.toUri(d.path('_jupiter.scss')), + ]), + ); }); }); // Regression test for #1318 test("meta.load-module() doesn't have a race condition", () async { await d.file("other.scss", '/**//**/').create(); - expect(compileStringAsync(""" - @use 'sass:meta'; - @include meta.load-css("other.scss"); - """, loadPaths: [d.sandbox]), completion(equals("/**/ /**/"))); + expect( + compileStringAsync( + """ + @use 'sass:meta'; + @include meta.load-css("other.scss"); + """, + loadPaths: [d.sandbox], + ), + completion(equals("/**/ /**/")), + ); // Give the race condition time to appear. await pumpEventQueue(); diff --git a/test/deprecations_test.dart b/test/deprecations_test.dart index 5841ea323..f8d352af5 100644 --- a/test/deprecations_test.dart +++ b/test/deprecations_test.dart @@ -23,7 +23,9 @@ void main() { // Deprecated in 1.7.2 test("mozDocument is violated by most @-moz-document rules", () { _expectDeprecation( - "@-moz-document url-prefix(foo) {}", Deprecation.mozDocument); + "@-moz-document url-prefix(foo) {}", + Deprecation.mozDocument, + ); }); // Deprecated in 1.17.2 @@ -49,7 +51,9 @@ void main() { test("using color.alpha for a microsoft filter", () { _expectDeprecation( - "$color.alpha(foo=bar)}", Deprecation.colorModuleCompat); + "$color.alpha(foo=bar)}", + Deprecation.colorModuleCompat, + ); }); }); @@ -92,7 +96,9 @@ void main() { test("a saturation/lightness with a non-percent unit", () { _expectDeprecation( - "a {b: hsl(10deg, 0px, 0%)}", Deprecation.functionUnits); + "a {b: hsl(10deg, 0px, 0%)}", + Deprecation.functionUnits, + ); }); test("a saturation/lightness with no unit", () { @@ -101,23 +107,30 @@ void main() { test("an alpha value with a non-percent unit", () { _expectDeprecation( - r"@use 'sass:color'; a {b: color.change(red, $alpha: 1px)}", - Deprecation.functionUnits); + r"@use 'sass:color'; a {b: color.change(red, $alpha: 1px)}", + Deprecation.functionUnits, + ); }); test("calling math.random with units", () { - _expectDeprecation("@use 'sass:math'; a {b: math.random(100px)}", - Deprecation.functionUnits); + _expectDeprecation( + "@use 'sass:math'; a {b: math.random(100px)}", + Deprecation.functionUnits, + ); }); test("calling list.nth with units", () { - _expectDeprecation("@use 'sass:list'; a {b: list.nth(1 2, 1px)}", - Deprecation.functionUnits); + _expectDeprecation( + "@use 'sass:list'; a {b: list.nth(1 2, 1px)}", + Deprecation.functionUnits, + ); }); test("calling list.set-nth with units", () { - _expectDeprecation("@use 'sass:list'; a {b: list.set-nth(1 2, 1px, 3)}", - Deprecation.functionUnits); + _expectDeprecation( + "@use 'sass:list'; a {b: list.set-nth(1 2, 1px, 3)}", + Deprecation.functionUnits, + ); }); }); } diff --git a/test/doc_comments_test.dart b/test/doc_comments_test.dart index 699ff15a2..a6ba8bd45 100644 --- a/test/doc_comments_test.dart +++ b/test/doc_comments_test.dart @@ -13,22 +13,24 @@ void main() { group('in SCSS', () { test('attach to variable declarations', () { final contents = r''' - /// Results my vary. - $vary: 5.16em;'''; + /// Results may vary. + $vary: 5.16em; + '''; final stylesheet = Stylesheet.parseScss(contents); final variable = stylesheet.children.whereType().first; - expect(variable.comment!.docComment, equals('Results my vary.')); + expect(variable.comment!.docComment, equals('Results may vary.')); }); test('attach to function rules', () { final contents = r''' - /// A fun function! - @function fun($val) { - // Not a doc comment. - @return ($val / 1000) * 1em; - }'''; + /// A fun function! + @function fun($val) { + // Not a doc comment. + @return ($val / 1000) * 1em; + } + '''; final stylesheet = Stylesheet.parseScss(contents); final function = stylesheet.children.whereType().first; @@ -37,12 +39,13 @@ void main() { test('attach to mixin rules', () { final contents = r''' - /// Mysterious mixin. - @mixin mystery { - // All black. - color: black; - background-color: black; - }'''; + /// Mysterious mixin. + @mixin mystery { + // All black. + color: black; + background-color: black; + } + '''; final stylesheet = Stylesheet.parseScss(contents); final mix = stylesheet.children.whereType().first; @@ -51,8 +54,9 @@ void main() { test('are null when there are no triple-slash comments', () { final contents = r''' - // Regular comment. - $vary: 5.16em;'''; + // Regular comment. + $vary: 5.16em; + '''; final stylesheet = Stylesheet.parseScss(contents); final variable = stylesheet.children.whereType().first; @@ -62,18 +66,19 @@ void main() { test('are not carried over across members', () { final contents = r''' - /// Mysterious mixin. - @mixin mystery { - // All black. - color: black; - background-color: black; - } - - /// A fun function! - @function fun($val) { - // Not a doc comment. - @return ($val / 1000) * 1em; - }'''; + /// Mysterious mixin. + @mixin mystery { + // All black. + color: black; + background-color: black; + } + + /// A fun function! + @function fun($val) { + // Not a doc comment. + @return ($val / 1000) * 1em; + } + '''; final stylesheet = Stylesheet.parseScss(contents); final mix = stylesheet.children.whereType().first; final function = stylesheet.children.whereType().first; @@ -84,13 +89,14 @@ void main() { test('do not include double-slash comments', () { final contents = r''' - // Not a doc comment. - /// Line 1 - /// Line 2 - // Not a doc comment. - /// Line 3 - // Not a doc comment. - $vary: 5.16em;'''; + // Not a doc comment. + /// Line 1 + /// Line 2 + // Not a doc comment. + /// Line 3 + // Not a doc comment. + $vary: 5.16em; + '''; final stylesheet = Stylesheet.parseScss(contents); final variable = stylesheet.children.whereType().first; @@ -102,13 +108,14 @@ void main() { group('in indented syntax', () { test('attach to variable declarations', () { final contents = r''' -/// Results my vary. -$vary: 5.16em'''; +/// Results may vary. +$vary: 5.16em +'''; final stylesheet = Stylesheet.parseSass(contents); final variable = stylesheet.children.whereType().first; - expect(variable.comment!.docComment, equals('Results my vary.')); + expect(variable.comment!.docComment, equals('Results may vary.')); }); test('attach to function rules', () { @@ -116,7 +123,8 @@ $vary: 5.16em'''; /// A fun function! @function fun($val) // Not a doc comment. - @return ($val / 1000) * 1em'''; + @return ($val / 1000) * 1em +'''; final stylesheet = Stylesheet.parseSass(contents); final function = stylesheet.children.whereType().first; @@ -129,7 +137,8 @@ $vary: 5.16em'''; @mixin mystery // All black. color: black - background-color: black'''; + background-color: black +'''; final stylesheet = Stylesheet.parseSass(contents); final mix = stylesheet.children.whereType().first; @@ -139,7 +148,8 @@ $vary: 5.16em'''; test('are null when there are no triple-slash comments', () { final contents = r''' // Regular comment. -$vary: 5.16em'''; +$vary: 5.16em +'''; final stylesheet = Stylesheet.parseSass(contents); final variable = stylesheet.children.whereType().first; @@ -158,7 +168,8 @@ $vary: 5.16em'''; /// A fun function! @function fun($val) // Not a doc comment. - @return ($val / 1000) * 1em'''; + @return ($val / 1000) * 1em +'''; final stylesheet = Stylesheet.parseSass(contents); final mix = stylesheet.children.whereType().first; final function = stylesheet.children.whereType().first; @@ -174,7 +185,8 @@ $vary: 5.16em'''; Line 2 // Not a doc comment. Should be ignored. -$vary: 5.16em'''; +$vary: 5.16em +'''; final stylesheet = Stylesheet.parseSass(contents); final variable = stylesheet.children.whereType().first; @@ -189,14 +201,17 @@ $vary: 5.16em'''; /// Line 2 Line 3 /// Line 4 -$vary: 5.16em'''; +$vary: 5.16em +'''; final stylesheet = Stylesheet.parseSass(contents); final variable = stylesheet.children.whereType().first; expect(stylesheet.children.length, equals(2)); - expect(variable.comment!.docComment, - equals('Line 1\nLine 2\nLine 3\nLine 4')); + expect( + variable.comment!.docComment, + equals('Line 1\nLine 2\nLine 3\nLine 4'), + ); }); }); }); diff --git a/test/double_check_test.dart b/test/double_check_test.dart index 20fef1d5a..656ed5042 100644 --- a/test/double_check_test.dart +++ b/test/double_check_test.dart @@ -21,30 +21,42 @@ import '../tool/grind/synchronize.dart' as synchronize; /// Tests that double-check that everything in the repo looks sensible. void main() { - group("up-to-date generated", () { - group("synchronized file:", () { - for (var (sourcePath, targetPath) in synchronize.sources.pairs) { - test(targetPath, () => _assertChecksumMatches(sourcePath, targetPath)); - } - }); + group( + "up-to-date generated", + () { + group("synchronized file:", () { + for (var (sourcePath, targetPath) in synchronize.sources.pairs) { + test( + targetPath, + () => _assertChecksumMatches(sourcePath, targetPath), + ); + } + }); - test( + test( "deprecations", () => _assertChecksumMatches( - deprecations.yamlPath, deprecations.dartPath)); - }, - // Windows sees different bytes than other OSes, possibly because of - // newline normalization issues. - testOn: "!windows"); + deprecations.yamlPath, + deprecations.dartPath, + ), + ); + }, + // Windows sees different bytes than other OSes, possibly because of + // newline normalization issues. + testOn: "!windows", + ); for (var package in [".", "pkg/sass_api"]) { group("in ${p.relative(package)}", () { test("pubspec version matches CHANGELOG version", () { var pubspec = Pubspec.parse( - File("$package/pubspec.yaml").readAsStringSync(), - sourceUrl: p.toUri("$package/pubspec.yaml")); - expect(pubspec.version!.toString(), - matchesChangelogVersion(_changelogVersion(package))); + File("$package/pubspec.yaml").readAsStringSync(), + sourceUrl: p.toUri("$package/pubspec.yaml"), + ); + expect( + pubspec.version!.toString(), + matchesChangelogVersion(_changelogVersion(package)), + ); }); }); } @@ -53,11 +65,14 @@ void main() { late Pubspec sassPubspec; late Pubspec pkgPubspec; setUpAll(() { - sassPubspec = Pubspec.parse(File("pubspec.yaml").readAsStringSync(), - sourceUrl: Uri.parse("pubspec.yaml")); + sassPubspec = Pubspec.parse( + File("pubspec.yaml").readAsStringSync(), + sourceUrl: Uri.parse("pubspec.yaml"), + ); pkgPubspec = Pubspec.parse( - File("pkg/sass_api/pubspec.yaml").readAsStringSync(), - sourceUrl: p.toUri("pkg/sass_api/pubspec.yaml")); + File("pkg/sass_api/pubspec.yaml").readAsStringSync(), + sourceUrl: p.toUri("pkg/sass_api/pubspec.yaml"), + ); }); test("depends on the current sass version", () { @@ -66,23 +81,33 @@ void main() { expect(pkgPubspec.dependencies, contains("sass")); var dependency = pkgPubspec.dependencies["sass"]!; expect(dependency, isA()); - expect((dependency as HostedDependency).version, - equals(sassPubspec.version)); + expect( + (dependency as HostedDependency).version, + equals(sassPubspec.version), + ); }); test( - "increments along with the sass version", - () => _checkVersionIncrementsAlong( - 'sass_api', sassPubspec, pkgPubspec.version!)); + "increments along with the sass version", + () => _checkVersionIncrementsAlong( + 'sass_api', + sassPubspec, + pkgPubspec.version!, + ), + ); test("matches SDK version", () { - expect(pkgPubspec.environment["sdk"], - equals(sassPubspec.environment["sdk"])); + expect( + pkgPubspec.environment["sdk"], + equals(sassPubspec.environment["sdk"]), + ); }); test("matches dartdoc version", () { - expect(sassPubspec.devDependencies["dartdoc"], - equals(pkgPubspec.devDependencies["dartdoc"])); + expect( + sassPubspec.devDependencies["dartdoc"], + equals(pkgPubspec.devDependencies["dartdoc"]), + ); }); }); @@ -90,22 +115,31 @@ void main() { late Pubspec sassPubspec; late Map packageJson; setUpAll(() { - sassPubspec = Pubspec.parse(File("pubspec.yaml").readAsStringSync(), - sourceUrl: Uri.parse("pubspec.yaml")); + sassPubspec = Pubspec.parse( + File("pubspec.yaml").readAsStringSync(), + sourceUrl: Uri.parse("pubspec.yaml"), + ); packageJson = json.decode(File("pkg/sass-parser/package.json").readAsStringSync()) as Map; }); test( - "package.json version matches CHANGELOG version", - () => expect(packageJson["version"].toString(), - matchesChangelogVersion(_changelogVersion("pkg/sass-parser")))); + "package.json version matches CHANGELOG version", + () => expect( + packageJson["version"].toString(), + matchesChangelogVersion(_changelogVersion("pkg/sass-parser")), + ), + ); test( - "increments along with the sass version", - () => _checkVersionIncrementsAlong('sass-parser', sassPubspec, - Version.parse(packageJson["version"] as String))); + "increments along with the sass version", + () => _checkVersionIncrementsAlong( + 'sass-parser', + sassPubspec, + Version.parse(packageJson["version"] as String), + ), + ); }); } @@ -125,26 +159,36 @@ Version _changelogVersion(String package) { /// Returns a [Matcher] that matches any valid variant of the CHANGELOG version /// [version] that the package itself can have. Matcher matchesChangelogVersion(Version version) => anyOf( - equals(version.toString()), - version.isPreRelease - ? equals("${version.nextPatch}-dev") - : equals("$version-dev")); + equals(version.toString()), + version.isPreRelease + ? equals("${version.nextPatch}-dev") + : equals("$version-dev"), + ); /// Verifies that [pkgVersion] loks like it was incremented when the version of /// the main Sass version was as well. void _checkVersionIncrementsAlong( - String pkgName, Pubspec sassPubspec, Version pkgVersion) { + String pkgName, + Pubspec sassPubspec, + Version pkgVersion, +) { var sassVersion = sassPubspec.version!; if (_isDevVersion(sassVersion)) return; - expect(_isDevVersion(pkgVersion), isFalse, - reason: "sass $sassVersion isn't a dev version but $pkgName $pkgVersion " - "is"); + expect( + _isDevVersion(pkgVersion), + isFalse, + reason: "sass $sassVersion isn't a dev version but $pkgName $pkgVersion " + "is", + ); if (sassVersion.isPreRelease) { - expect(pkgVersion.isPreRelease, isTrue, - reason: "sass $sassVersion is a pre-release version but $pkgName " - "$pkgVersion isn't"); + expect( + pkgVersion.isPreRelease, + isTrue, + reason: "sass $sassVersion is a pre-release version but $pkgName " + "$pkgVersion isn't", + ); } // If only sass's patch version was incremented, there's not a good way @@ -164,13 +208,19 @@ void _checkVersionIncrementsAlong( } if (sassVersion.minor != 0) { - expect(pkgPatch, equals(0), - reason: "sass minor version was incremented, $pkgName must increment " - "at least its minor version"); + expect( + pkgPatch, + equals(0), + reason: "sass minor version was incremented, $pkgName must increment " + "at least its minor version", + ); } else { - expect(pkgMinor, equals(0), - reason: "sass major version was incremented, $pkgName must increment " - "at its major version as well"); + expect( + pkgMinor, + equals(0), + reason: "sass major version was incremented, $pkgName must increment " + "at its major version as well", + ); } } @@ -181,7 +231,9 @@ void _assertChecksumMatches(String inputPath, String outputPath) { var outputText = File(outputPath).readAsStringSync(); var checksum = sha1.convert(utf8.encode(inputText)); if (!outputText.contains('// Checksum: $checksum')) { - fail('$outputPath is out-of-date.\n' - 'Run `dart run grinder` to update it.'); + fail( + '$outputPath is out-of-date.\n' + 'Run `dart run grinder` to update it.', + ); } } diff --git a/test/embedded/embedded_process.dart b/test/embedded/embedded_process.dart index 89740b25c..6357566b5 100644 --- a/test/embedded/embedded_process.dart +++ b/test/embedded/embedded_process.dart @@ -71,8 +71,10 @@ class EmbeddedProcess { /// Completes to [_process]'s exit code if it's exited, otherwise completes to /// `null` immediately. Future get _exitCodeOrNull async { - var exitCode = - await this.exitCode.timeout(Duration.zero, onTimeout: () => -1); + var exitCode = await this.exitCode.timeout( + Duration.zero, + onTimeout: () => -1, + ); return exitCode == -1 ? null : exitCode; } @@ -85,18 +87,21 @@ class EmbeddedProcess { /// If [forwardOutput] is `true`, the process's [outbound] messages and /// [stderr] will be printed to the console as they appear. This is only /// intended to be set temporarily to help when debugging test failures. - static Future start( - {String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - bool forwardOutput = false}) async { - var process = await Process.start(pkg.executableRunner("sass"), - [...pkg.executableArgs("sass"), "--embedded"], - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell); + static Future start({ + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + bool forwardOutput = false, + }) async { + var process = await Process.start( + pkg.executableRunner("sass"), + [...pkg.executableArgs("sass"), "--embedded"], + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + ); return EmbeddedProcess._(process, forwardOutput: forwardOutput); } @@ -107,23 +112,33 @@ class EmbeddedProcess { EmbeddedProcess._(Process process, {bool forwardOutput = false}) : _process = process, _outboundSplitter = StreamSplitter( - process.stdout.transform(lengthDelimitedDecoder).map((packet) { - var (compilationId, buffer) = parsePacket(packet); - return (compilationId, OutboundMessage.fromBuffer(buffer)); - })), - _stderrSplitter = StreamSplitter(process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter())), + process.stdout.transform(lengthDelimitedDecoder).map((packet) { + var (compilationId, buffer) = parsePacket(packet); + return (compilationId, OutboundMessage.fromBuffer(buffer)); + }), + ), + _stderrSplitter = StreamSplitter( + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()), + ), inbound = StreamSinkTransformer<(int, InboundMessage), - List>.fromHandlers(handleData: (pair, sink) { - var (compilationId, message) = pair; - sink.add(serializePacket(compilationId, message)); - }).bind( - StreamSinkTransformer.fromStreamTransformer(lengthDelimitedEncoder) - .bind(process.stdin)) { + List>.fromHandlers( + handleData: (pair, sink) { + var (compilationId, message) = pair; + sink.add(serializePacket(compilationId, message)); + }, + ).bind( + StreamSinkTransformer.fromStreamTransformer( + lengthDelimitedEncoder, + ).bind(process.stdin), + ) { addTearDown(_tearDown); - expect(_process.exitCode.then((_) => _logOutput()), completes, - reason: "Process `sass --embedded` never exited."); + expect( + _process.exitCode.then((_) => _logOutput()), + completes, + reason: "Process `sass --embedded` never exited.", + ); _outbound = StreamQueue(_outboundSplitter.split()); _stderr = StreamQueue(_stderrSplitter.split()); @@ -185,8 +200,11 @@ class EmbeddedProcess { /// default compilation ID. Future receive() async { var (actualCompilationId, message) = await outbound.next; - expect(actualCompilationId, equals(defaultCompilationId), - reason: "Expected default compilation ID"); + expect( + actualCompilationId, + equals(defaultCompilationId), + reason: "Expected default compilation ID", + ); return message; } @@ -213,7 +231,10 @@ class EmbeddedProcess { Future shouldExit([int? expectedExitCode]) async { var exitCode = await this.exitCode; if (expectedExitCode == null) return; - expect(exitCode, expectedExitCode, - reason: "Process `dart_sass_embedded` had an unexpected exit code."); + expect( + exitCode, + expectedExitCode, + reason: "Process `dart_sass_embedded` had an unexpected exit code.", + ); } } diff --git a/test/embedded/file_importer_test.dart b/test/embedded/file_importer_test.dart index 61c8e1a42..6d8e99079 100644 --- a/test/embedded/file_importer_test.dart +++ b/test/embedded/file_importer_test.dart @@ -25,37 +25,48 @@ void main() { late OutboundMessage_FileImportRequest request; setUp(() async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..fileImporterId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..fileImporterId = 1, + ], + ), + ); request = await getFileImportRequest(process); }); test("for a response without a corresponding request ID", () async { - process.send(InboundMessage() - ..fileImportResponse = - (InboundMessage_FileImportResponse()..id = request.id + 1)); + process.send( + InboundMessage() + ..fileImportResponse = + (InboundMessage_FileImportResponse()..id = request.id + 1), + ); await expectParamsError( - process, - errorId, - "Response ID ${request.id + 1} doesn't match any outstanding " - "requests in compilation $defaultCompilationId."); + process, + errorId, + "Response ID ${request.id + 1} doesn't match any outstanding " + "requests in compilation $defaultCompilationId.", + ); await process.shouldExit(76); }); test("for a response that doesn't match the request type", () async { - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id), + ); await expectParamsError( - process, - errorId, - "Request ID ${request.id} doesn't match response type " - "InboundMessage_CanonicalizeResponse in compilation " - "$defaultCompilationId."); + process, + errorId, + "Request ID ${request.id} doesn't match response type " + "InboundMessage_CanonicalizeResponse in compilation " + "$defaultCompilationId.", + ); await process.shouldExit(76); }); }); @@ -64,44 +75,61 @@ void main() { late OutboundMessage_FileImportRequest request; setUp(() async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..fileImporterId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..fileImporterId = 1, + ], + ), + ); request = await getFileImportRequest(process); }); group("for a FileImportResponse with a URL", () { test("that's empty", () async { - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..fileUrl = "")); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..fileUrl = ""), + ); await _expectUseError( - process, 'The file importer must return an absolute URL, was ""'); + process, + 'The file importer must return an absolute URL, was ""', + ); await process.close(); }); test("that's relative", () async { - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..fileUrl = "foo")); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..fileUrl = "foo"), + ); - await _expectUseError(process, - 'The file importer must return an absolute URL, was "foo"'); + await _expectUseError( + process, + 'The file importer must return an absolute URL, was "foo"', + ); await process.close(); }); test("that's not file:", () async { - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..fileUrl = "other:foo")); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..fileUrl = "other:foo"), + ); - await _expectUseError(process, - 'The file importer must return a file: URL, was "other:foo"'); + await _expectUseError( + process, + 'The file importer must return a file: URL, was "other:foo"', + ); await process.close(); }); }); @@ -112,9 +140,16 @@ void main() { var importerId = 5679; late OutboundMessage_FileImportRequest request; setUp(() async { - process.send(compileString("@use 'other'", id: compilationId, importers: [ - InboundMessage_CompileRequest_Importer()..fileImporterId = importerId - ])); + process.send( + compileString( + "@use 'other'", + id: compilationId, + importers: [ + InboundMessage_CompileRequest_Importer() + ..fileImporterId = importerId, + ], + ), + ); request = await getFileImportRequest(process); }); @@ -135,15 +170,22 @@ void main() { }); test("errors cause compilation to fail", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..fileImporterId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..fileImporterId = 1, + ], + ), + ); var request = await getFileImportRequest(process); - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..error = "oh no")); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..error = "oh no"), + ); var failure = await getCompileFailure(process); expect(failure.message, equals('oh no')); @@ -153,14 +195,21 @@ void main() { }); test("null results count as not found", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..fileImporterId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..fileImporterId = 1, + ], + ), + ); var request = await getFileImportRequest(process); - process.send(InboundMessage() - ..fileImportResponse = - (InboundMessage_FileImportResponse()..id = request.id)); + process.send( + InboundMessage() + ..fileImportResponse = + (InboundMessage_FileImportResponse()..id = request.id), + ); var failure = await getCompileFailure(process); expect(failure.message, equals("Can't find stylesheet to import.")); @@ -170,44 +219,60 @@ void main() { group("attempts importers in order", () { test("with multiple file importers", () async { - process.send(compileString("@use 'other'", importers: [ - for (var i = 0; i < 10; i++) - InboundMessage_CompileRequest_Importer()..fileImporterId = i - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + for (var i = 0; i < 10; i++) + InboundMessage_CompileRequest_Importer()..fileImporterId = i, + ], + ), + ); for (var i = 0; i < 10; i++) { var request = await getFileImportRequest(process); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..fileImportResponse = - (InboundMessage_FileImportResponse()..id = request.id)); + process.send( + InboundMessage() + ..fileImportResponse = + (InboundMessage_FileImportResponse()..id = request.id), + ); } await process.kill(); }); test("with a mixture of file and normal importers", () async { - process.send(compileString("@use 'other'", importers: [ - for (var i = 0; i < 10; i++) - if (i % 2 == 0) - InboundMessage_CompileRequest_Importer()..fileImporterId = i - else - InboundMessage_CompileRequest_Importer()..importerId = i - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + for (var i = 0; i < 10; i++) + if (i % 2 == 0) + InboundMessage_CompileRequest_Importer()..fileImporterId = i + else + InboundMessage_CompileRequest_Importer()..importerId = i, + ], + ), + ); for (var i = 0; i < 10; i++) { if (i % 2 == 0) { var request = await getFileImportRequest(process); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..fileImportResponse = - (InboundMessage_FileImportResponse()..id = request.id)); + process.send( + InboundMessage() + ..fileImportResponse = + (InboundMessage_FileImportResponse()..id = request.id), + ); } else { var request = await getCanonicalizeRequest(process); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id), + ); } } @@ -219,26 +284,35 @@ void main() { await d.file("upstream.scss", "a {b: c}").create(); await d.file("midstream.scss", "@use 'upstream';").create(); - process.send(compileString("@use 'midstream'", importers: [ - for (var i = 0; i < 10; i++) - InboundMessage_CompileRequest_Importer()..fileImporterId = i - ])); + process.send( + compileString( + "@use 'midstream'", + importers: [ + for (var i = 0; i < 10; i++) + InboundMessage_CompileRequest_Importer()..fileImporterId = i, + ], + ), + ); for (var i = 0; i < 5; i++) { var request = await getFileImportRequest(process); expect(request.url, equals("midstream")); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..fileImportResponse = - (InboundMessage_FileImportResponse()..id = request.id)); + process.send( + InboundMessage() + ..fileImportResponse = + (InboundMessage_FileImportResponse()..id = request.id), + ); } var request = await getFileImportRequest(process); expect(request.importerId, equals(5)); - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..fileUrl = p.toUri(d.path("midstream")).toString())); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..fileUrl = p.toUri(d.path("midstream")).toString()), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); @@ -250,27 +324,37 @@ void main() { }); test("without a base URL", () async { - process.send(compileString("@use 'other'", + process.send( + compileString( + "@use 'other'", importer: InboundMessage_CompileRequest_Importer() - ..fileImporterId = 1)); + ..fileImporterId = 1, + ), + ); var request = await getFileImportRequest(process); expect(request.url, equals("other")); - process.send(InboundMessage() - ..fileImportResponse = (InboundMessage_FileImportResponse() - ..id = request.id - ..fileUrl = p.toUri(d.path("other")).toString())); + process.send( + InboundMessage() + ..fileImportResponse = (InboundMessage_FileImportResponse() + ..id = request.id + ..fileUrl = p.toUri(d.path("other")).toString()), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); }); test("with a base URL", () async { - process.send(compileString("@use 'other'", + process.send( + compileString( + "@use 'other'", url: p.toUri(d.path("input")).toString(), importer: InboundMessage_CompileRequest_Importer() - ..fileImporterId = 1)); + ..fileImporterId = 1, + ), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); diff --git a/test/embedded/function_test.dart b/test/embedded/function_test.dart index 584151e79..bd0761cc6 100644 --- a/test/embedded/function_test.dart +++ b/test/embedded/function_test.dart @@ -28,35 +28,45 @@ void main() { test("that's empty", () async { _process.send(compileString("a {b: c}", functions: [r""])); await _expectFunctionError( - _process, r'Invalid signature "": Expected identifier.'); + _process, + r'Invalid signature "": Expected identifier.', + ); await _process.close(); }); test("that's just a name", () async { _process.send(compileString("a {b: c}", functions: [r"foo"])); await _expectFunctionError( - _process, r'Invalid signature "foo": expected "(".'); + _process, + r'Invalid signature "foo": expected "(".', + ); await _process.close(); }); test("without a closing paren", () async { _process.send(compileString("a {b: c}", functions: [r"foo($bar"])); await _expectFunctionError( - _process, r'Invalid signature "foo($bar": expected ")".'); + _process, + r'Invalid signature "foo($bar": expected ")".', + ); await _process.close(); }); test("with text after the closing paren", () async { _process.send(compileString("a {b: c}", functions: [r"foo() "])); await _expectFunctionError( - _process, r'Invalid signature "foo() ": expected no more input.'); + _process, + r'Invalid signature "foo() ": expected no more input.', + ); await _process.close(); }); test("with invalid arguments", () async { _process.send(compileString("a {b: c}", functions: [r"foo($)"])); await _expectFunctionError( - _process, r'Invalid signature "foo($)": Expected identifier.'); + _process, + r'Invalid signature "foo($)": Expected identifier.', + ); await _process.close(); }); }); @@ -78,91 +88,122 @@ void main() { }); test("by position", () async { - _process.send(compileString("a {b: foo(true, null, false)}", - functions: [r"foo($arg1, $arg2, $arg3)"])); + _process.send( + compileString( + "a {b: foo(true, null, false)}", + functions: [r"foo($arg1, $arg2, $arg3)"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, equals([_true, _null, _false])); await _process.kill(); }); test("by name", () async { - _process.send(compileString( + _process.send( + compileString( r"a {b: foo($arg3: true, $arg1: null, $arg2: false)}", - functions: [r"foo($arg1, $arg2, $arg3)"])); + functions: [r"foo($arg1, $arg2, $arg3)"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, equals([_null, _false, _true])); await _process.kill(); }); test("by position and name", () async { - _process.send(compileString( + _process.send( + compileString( r"a {b: foo(true, $arg3: null, $arg2: false)}", - functions: [r"foo($arg1, $arg2, $arg3)"])); + functions: [r"foo($arg1, $arg2, $arg3)"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, equals([_true, _false, _null])); await _process.kill(); }); test("from defaults", () async { - _process.send(compileString(r"a {b: foo(1, $arg3: 2)}", - functions: [r"foo($arg1: null, $arg2: true, $arg3: false)"])); + _process.send( + compileString( + r"a {b: foo(1, $arg3: 2)}", + functions: [r"foo($arg1: null, $arg2: true, $arg3: false)"], + ), + ); var request = await getFunctionCallRequest(_process); expect( - request.arguments, - equals([ - Value()..number = (Value_Number()..value = 1.0), - _true, - Value()..number = (Value_Number()..value = 2.0) - ])); + request.arguments, + equals([ + Value()..number = (Value_Number()..value = 1.0), + _true, + Value()..number = (Value_Number()..value = 2.0), + ]), + ); await _process.kill(); }); group("from argument lists", () { test("with no named arguments", () async { - _process.send(compileString("a {b: foo(true, false, null)}", - functions: [r"foo($arg, $args...)"])); + _process.send( + compileString( + "a {b: foo(true, false, null)}", + functions: [r"foo($arg, $args...)"], + ), + ); var request = await getFunctionCallRequest(_process); expect( - request.arguments, - equals([ - _true, - Value() - ..argumentList = (Value_ArgumentList() - ..id = 1 - ..separator = ListSeparator.COMMA - ..contents.addAll([_false, _null])) - ])); + request.arguments, + equals([ + _true, + Value() + ..argumentList = (Value_ArgumentList() + ..id = 1 + ..separator = ListSeparator.COMMA + ..contents.addAll([_false, _null])), + ]), + ); await _process.kill(); }); test("with named arguments", () async { - _process.send(compileString(r"a {b: foo(true, $arg: false)}", - functions: [r"foo($args...)"])); + _process.send( + compileString( + r"a {b: foo(true, $arg: false)}", + functions: [r"foo($args...)"], + ), + ); var request = await getFunctionCallRequest(_process); expect( - request.arguments, - equals([ - Value() - ..argumentList = (Value_ArgumentList() - ..id = 1 - ..separator = ListSeparator.COMMA - ..contents.addAll([_true]) - ..keywords.addAll({"arg": _false})) - ])); + request.arguments, + equals([ + Value() + ..argumentList = (Value_ArgumentList() + ..id = 1 + ..separator = ListSeparator.COMMA + ..contents.addAll([_true]) + ..keywords.addAll({"arg": _false})), + ]), + ); await _process.kill(); }); test("throws if named arguments are unused", () async { - _process.send(compileString(r"a {b: foo($arg: false)}", - functions: [r"foo($args...)"])); + _process.send( + compileString( + r"a {b: foo($arg: false)}", + functions: [r"foo($args...)"], + ), + ); var request = await getFunctionCallRequest(_process); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = _true)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = _true), + ); var failure = await getCompileFailure(_process); expect(failure.message, equals(r"No parameter named $arg.")); @@ -170,16 +211,23 @@ void main() { }); test("doesn't throw if named arguments are used", () async { - _process.send(compileString(r"a {b: foo($arg: false)}", - functions: [r"foo($args...)"])); + _process.send( + compileString( + r"a {b: foo($arg: false)}", + functions: [r"foo($args...)"], + ), + ); var request = await getFunctionCallRequest(_process); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..accessedArgumentLists - .add(request.arguments.first.argumentList.id) - ..success = _true)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..accessedArgumentLists.add( + request.arguments.first.argumentList.id, + ) + ..success = _true), + ); await expectSuccess(_process, equals("a {\n b: true;\n}")); await _process.close(); @@ -192,13 +240,15 @@ void main() { _process.send(compileString("a {b: foo() + 2px}", functions: [r"foo()"])); var request = await getFunctionCallRequest(_process); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = (Value() - ..number = (Value_Number() - ..value = 1 - ..numerators.add("px"))))); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = (Value() + ..number = (Value_Number() + ..value = 1 + ..numerators.add("px")))), + ); await expectSuccess(_process, equals("a {\n b: 3px;\n}")); await _process.close(); @@ -206,92 +256,119 @@ void main() { group("calls a first-class function", () { test("defined in the compiler and passed to and from the host", () async { - _process.send(compileString(r""" - @use "sass:math"; - @use "sass:meta"; + _process.send( + compileString( + r""" + @use "sass:math"; + @use "sass:meta"; - a {b: meta.call(foo(meta.get-function("abs", $module: "math")), -1)} - """, functions: [r"foo($arg)"])); + a {b: meta.call(foo(meta.get-function("abs", $module: "math")), -1)} + """, + functions: [r"foo($arg)"], + ), + ); var request = await getFunctionCallRequest(_process); var value = request.arguments.single; expect(value.hasCompilerFunction(), isTrue); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = value)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = value), + ); await expectSuccess(_process, equals("a {\n b: 1;\n}")); await _process.close(); }); test("defined in the host", () async { - _process.send(compileString(""" - @use "sass:meta"; + _process.send( + compileString( + """ + @use "sass:meta"; - a {b: meta.call(foo(), true)} - """, functions: [r"foo()"])); + a {b: meta.call(foo(), true)} + """, + functions: [r"foo()"], + ), + ); var hostFunctionId = 5678; var request = await getFunctionCallRequest(_process); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = (Value() - ..hostFunction = (Value_HostFunction() - ..id = hostFunctionId - ..signature = r"bar($arg)")))); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = (Value() + ..hostFunction = (Value_HostFunction() + ..id = hostFunctionId + ..signature = r"bar($arg)"))), + ); request = await getFunctionCallRequest(_process); expect(request.functionId, equals(hostFunctionId)); expect(request.arguments, equals([_true])); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = _false)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = _false), + ); await expectSuccess(_process, equals("a {\n b: false;\n}")); await _process.close(); }); test("defined in the host and passed to and from the host", () async { - _process.send(compileString(r""" + _process.send( + compileString( + r""" @use "sass:meta"; $function: get-host-function(); $function: round-trip($function); a {b: meta.call($function, true)} - """, functions: [r"get-host-function()", r"round-trip($function)"])); + """, + functions: [r"get-host-function()", r"round-trip($function)"], + ), + ); var hostFunctionId = 5678; var request = await getFunctionCallRequest(_process); expect(request.name, equals("get-host-function")); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = (Value() - ..hostFunction = (Value_HostFunction() - ..id = hostFunctionId - ..signature = r"bar($arg)")))); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = (Value() + ..hostFunction = (Value_HostFunction() + ..id = hostFunctionId + ..signature = r"bar($arg)"))), + ); request = await getFunctionCallRequest(_process); expect(request.name, equals("round-trip")); var value = request.arguments.single; expect(value.hasCompilerFunction(), isTrue); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = value)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = value), + ); request = await getFunctionCallRequest(_process); expect(request.functionId, equals(hostFunctionId)); expect(request.arguments, equals([_true])); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = _false)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = _false), + ); await expectSuccess(_process, equals("a {\n b: false;\n}")); await _process.close(); @@ -368,9 +445,10 @@ void main() { }); test("with multiple denominators", () async { - var value = - (await _protofy('math.div(math.div(math.div(1, 1em), 1px), 1foo)')) - .number; + var value = (await _protofy( + 'math.div(math.div(math.div(1, 1em), 1px), 1foo)', + )) + .number; expect(value.value, equals(1.0)); expect(value.numerators, isEmpty); expect(value.denominators, unorderedEquals(["em", "px", "foo"])); @@ -403,18 +481,24 @@ void main() { group("with alpha", () { test("0", () async { - expect(await _protofy('rgb(10, 20, 30, 0)'), - equals(_rgb(10, 20, 30, 0.0))); + expect( + await _protofy('rgb(10, 20, 30, 0)'), + equals(_rgb(10, 20, 30, 0.0)), + ); }); test("1", () async { - expect(await _protofy('rgb(10, 20, 30, 1)'), - equals(_rgb(10, 20, 30, 1.0))); + expect( + await _protofy('rgb(10, 20, 30, 1)'), + equals(_rgb(10, 20, 30, 1.0)), + ); }); test("between 0 and 1", () async { - expect(await _protofy('rgb(10, 20, 30, 0.123)'), - equals(_rgb(10, 20, 30, 0.123))); + expect( + await _protofy('rgb(10, 20, 30, 0.123)'), + equals(_rgb(10, 20, 30, 0.123)), + ); }); }); }); @@ -428,22 +512,30 @@ void main() { test("360", () async { expect( - await _protofy('hsl(360, 50%, 50%)'), _hsl(0, 50, 50, 1.0)); + await _protofy('hsl(360, 50%, 50%)'), + _hsl(0, 50, 50, 1.0), + ); }); test("below 0", () async { - expect(await _protofy('hsl(-100, 50%, 50%)'), - _hsl(260, 50, 50, 1.0)); + expect( + await _protofy('hsl(-100, 50%, 50%)'), + _hsl(260, 50, 50, 1.0), + ); }); test("between 0 and 360", () async { expect( - await _protofy('hsl(100, 50%, 50%)'), _hsl(100, 50, 50, 1.0)); + await _protofy('hsl(100, 50%, 50%)'), + _hsl(100, 50, 50, 1.0), + ); }); test("above 360", () async { expect( - await _protofy('hsl(560, 50%, 50%)'), _hsl(200, 50, 50, 1.0)); + await _protofy('hsl(560, 50%, 50%)'), + _hsl(200, 50, 50, 1.0), + ); }); }); @@ -454,7 +546,9 @@ void main() { test("100", () async { expect( - await _protofy('hsl(0, 100%, 50%)'), _hsl(0, 100, 50, 1.0)); + await _protofy('hsl(0, 100%, 50%)'), + _hsl(0, 100, 50, 1.0), + ); }); test("in the middle", () async { @@ -469,7 +563,9 @@ void main() { test("100", () async { expect( - await _protofy('hsl(0, 50%, 100%)'), _hsl(0, 50, 100, 1.0)); + await _protofy('hsl(0, 50%, 100%)'), + _hsl(0, 50, 100, 1.0), + ); }); test("in the middle", () async { @@ -480,18 +576,24 @@ void main() { group("with alpha", () { test("0", () async { - expect(await _protofy('hsl(10, 20%, 30%, 0)'), - equals(_hsl(10, 20, 30, 0.0))); + expect( + await _protofy('hsl(10, 20%, 30%, 0)'), + equals(_hsl(10, 20, 30, 0.0)), + ); }); test("1", () async { - expect(await _protofy('hsl(10, 20%, 30%, 1)'), - equals(_hsl(10, 20, 30, 1.0))); + expect( + await _protofy('hsl(10, 20%, 30%, 1)'), + equals(_hsl(10, 20, 30, 1.0)), + ); }); test("between 0 and 1", () async { - expect(await _protofy('hsl(10, 20%, 30%, 0.123)'), - equals(_hsl(10, 20, 30, 0.123))); + expect( + await _protofy('hsl(10, 20%, 30%, 0.123)'), + equals(_hsl(10, 20, 30, 0.123)), + ); }); }); }); @@ -583,18 +685,20 @@ void main() { }); test("with a space separator", () async { - var list = - (await _protofy(r"list.join([true], [], $separator: space)")) - .list; + var list = (await _protofy( + r"list.join([true], [], $separator: space)", + )) + .list; expect(list.contents, equals([_true])); expect(list.hasBrackets, isTrue); expect(list.separator, equals(ListSeparator.SPACE)); }); test("with a slash separator", () async { - var list = - (await _protofy(r"list.join([true], [], $separator: slash)")) - .list; + var list = (await _protofy( + r"list.join([true], [], $separator: slash)", + )) + .list; expect(list.contents, equals([_true])); expect(list.hasBrackets, isTrue); expect(list.separator, equals(ListSeparator.SLASH)); @@ -610,18 +714,20 @@ void main() { }); test("with a space separator", () async { - var list = - (await _protofy(r"list.join(true, (), $separator: space)")) - .list; + var list = (await _protofy( + r"list.join(true, (), $separator: space)", + )) + .list; expect(list.contents, equals([_true])); expect(list.hasBrackets, isFalse); expect(list.separator, equals(ListSeparator.SPACE)); }); test("with a slash separator", () async { - var list = - (await _protofy(r"list.join(true, (), $separator: slash)")) - .list; + var list = (await _protofy( + r"list.join(true, (), $separator: slash)", + )) + .list; expect(list.contents, equals([_true])); expect(list.hasBrackets, isFalse); expect(list.separator, equals(ListSeparator.SLASH)); @@ -696,16 +802,19 @@ void main() { }); test("with a slash separator", () async { - var list = - (await _protofy(r"capture-args(list.slash(true, null, false)...)")) - .argumentList; + var list = (await _protofy( + r"capture-args(list.slash(true, null, false)...)", + )) + .argumentList; expect(list.contents, [_true, _null, _false]); expect(list.keywords, isEmpty); expect(list.separator, equals(ListSeparator.SLASH)); }); test("with keywords", () async { - var list = (await _protofy(r"capture-args($arg1: true, $arg2: false)")) + var list = (await _protofy( + r"capture-args($arg1: true, $arg2: false)", + )) .argumentList; expect(list.contents, isEmpty); expect(list.keywords, equals({"arg1": _true, "arg2": _false})); @@ -720,113 +829,148 @@ void main() { test("with one element", () async { expect( - (await _protofy("(true: false)")).map.entries, - equals([ - Value_Map_Entry() - ..key = _true - ..value = _false - ])); + (await _protofy("(true: false)")).map.entries, + equals([ + Value_Map_Entry() + ..key = _true + ..value = _false, + ]), + ); }); test("with multiple elements", () async { expect( - (await _protofy("(true: false, 1: 2, a: b)")).map.entries, - equals([ - Value_Map_Entry() - ..key = _true - ..value = _false, - Value_Map_Entry() - ..key = (Value()..number = (Value_Number()..value = 1.0)) - ..value = (Value()..number = (Value_Number()..value = 2.0)), - Value_Map_Entry() - ..key = (Value() - ..string = (Value_String() - ..text = "a" - ..quoted = false)) - ..value = (Value() - ..string = (Value_String() - ..text = "b" - ..quoted = false)) - ])); + (await _protofy("(true: false, 1: 2, a: b)")).map.entries, + equals([ + Value_Map_Entry() + ..key = _true + ..value = _false, + Value_Map_Entry() + ..key = (Value()..number = (Value_Number()..value = 1.0)) + ..value = (Value()..number = (Value_Number()..value = 2.0)), + Value_Map_Entry() + ..key = (Value() + ..string = (Value_String() + ..text = "a" + ..quoted = false)) + ..value = (Value() + ..string = (Value_String() + ..text = "b" + ..quoted = false)), + ]), + ); }); }); group("a calculation", () { test("with a string argument", () async { expect( - (await _protofy("calc(var(--foo))")).calculation, - equals(Value_Calculation() + (await _protofy("calc(var(--foo))")).calculation, + equals( + Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..string = "var(--foo)"))); + ..arguments.add( + Value_Calculation_CalculationValue()..string = "var(--foo)", + ), + ), + ); }); test("with an interpolation argument", () async { expect( - (await _protofy("calc(#{var(--foo)})")).calculation, - equals(Value_Calculation() + (await _protofy("calc(#{var(--foo)})")).calculation, + equals( + Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..string = "var(--foo)"))); + ..arguments.add( + Value_Calculation_CalculationValue()..string = "var(--foo)", + ), + ), + ); }); test("with number arguments", () async { expect( - (await _protofy("clamp(1%, 2px, 3em)")).calculation, - equals(Value_Calculation() + (await _protofy("clamp(1%, 2px, 3em)")).calculation, + equals( + Value_Calculation() ..name = "clamp" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 3.0 - ..numerators.add("em"))))); + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 3.0 + ..numerators.add("em")), + ), + ), + ); }); test("with a calculation argument", () async { expect( - (await _protofy("min(max(1%, 2px), 3em)")).calculation, - equals(Value_Calculation() + (await _protofy("min(max(1%, 2px), 3em)")).calculation, + equals( + Value_Calculation() ..name = "min" - ..arguments.add(Value_Calculation_CalculationValue() - ..calculation = (Value_Calculation() - ..name = "max" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 3.0 - ..numerators.add("em"))))); + ..arguments.add( + Value_Calculation_CalculationValue() + ..calculation = (Value_Calculation() + ..name = "max" + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")), + )), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 3.0 + ..numerators.add("em")), + ), + ), + ); }); test("with an operation", () async { expect( - (await _protofy("calc(1% + 2px)")).calculation, - equals(Value_Calculation() + (await _protofy("calc(1% + 2px)")).calculation, + equals( + Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..operation = (Value_Calculation_CalculationOperation() - ..operator = CalculationOperator.PLUS - ..left = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..right = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))))))); + ..arguments.add( + Value_Calculation_CalculationValue() + ..operation = (Value_Calculation_CalculationOperation() + ..operator = CalculationOperator.PLUS + ..left = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%"))) + ..right = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")))), + ), + ), + ); }); }); @@ -848,20 +992,26 @@ void main() { group("quoted", () { test("and empty", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..string = (Value_String() ..text = "" - ..quoted = true)), - '""'); + ..quoted = true), + ), + '""', + ); }); test("and non-empty", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..string = (Value_String() ..text = "foo bar" - ..quoted = true)), - '"foo bar"'); + ..quoted = true), + ), + '"foo bar"', + ); }); }); @@ -869,19 +1019,24 @@ void main() { test("and empty", () async { // We can't use [_deprotofy] here because a property with an empty // value won't render at all. - await _assertRoundTrips(Value() - ..string = (Value_String() - ..text = "" - ..quoted = false)); + await _assertRoundTrips( + Value() + ..string = (Value_String() + ..text = "" + ..quoted = false), + ); }); test("and non-empty", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..string = (Value_String() ..text = "foo bar" - ..quoted = false)), - "foo bar"); + ..quoted = false), + ), + "foo bar", + ); }); }); }); @@ -890,69 +1045,82 @@ void main() { group("that's unitless", () { test("and an integer", () async { expect( - await _deprotofy(Value()..number = (Value_Number()..value = 1.0)), - "1"); + await _deprotofy(Value()..number = (Value_Number()..value = 1.0)), + "1", + ); }); test("and a float", () async { expect( - await _deprotofy(Value()..number = (Value_Number()..value = 1.5)), - "1.5"); + await _deprotofy(Value()..number = (Value_Number()..value = 1.5)), + "1.5", + ); }); }); test("with one numerator", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..number = (Value_Number() ..value = 1 - ..numerators.add("em"))), - "1em"); + ..numerators.add("em")), + ), + "1em", + ); }); test("with multiple numerators", () async { expect( - await _deprotofy( - Value() - ..number = (Value_Number() - ..value = 1 - ..numerators.addAll(["em", "px", "foo"])), - inspect: true), - "calc(1em * 1px * 1foo)"); + await _deprotofy( + Value() + ..number = (Value_Number() + ..value = 1 + ..numerators.addAll(["em", "px", "foo"])), + inspect: true, + ), + "calc(1em * 1px * 1foo)", + ); }); test("with one denominator", () async { expect( - await _deprotofy( - Value() - ..number = (Value_Number() - ..value = 1 - ..denominators.add("em")), - inspect: true), - "calc(1 / 1em)"); + await _deprotofy( + Value() + ..number = (Value_Number() + ..value = 1 + ..denominators.add("em")), + inspect: true, + ), + "calc(1 / 1em)", + ); }); test("with multiple denominators", () async { expect( - await _deprotofy( - Value() - ..number = (Value_Number() - ..value = 1 - ..denominators.addAll(["em", "px", "foo"])), - inspect: true), - "calc(1 / 1em / 1px / 1foo)"); + await _deprotofy( + Value() + ..number = (Value_Number() + ..value = 1 + ..denominators.addAll(["em", "px", "foo"])), + inspect: true, + ), + "calc(1 / 1em / 1px / 1foo)", + ); }); test("with numerators and denominators", () async { expect( - await _deprotofy( - Value() - ..number = (Value_Number() - ..value = 1 - ..numerators.addAll(["em", "px"]) - ..denominators.addAll(["s", "foo"])), - inspect: true), - "calc(1em * 1px / 1s / 1foo)"); + await _deprotofy( + Value() + ..number = (Value_Number() + ..value = 1 + ..numerators.addAll(["em", "px"]) + ..denominators.addAll(["s", "foo"])), + inspect: true, + ), + "calc(1em * 1px / 1s / 1foo)", + ); }); }); @@ -968,35 +1136,47 @@ void main() { }); test("in the middle", () async { - expect(await _deprotofy(_rgb(0xaa, 0xbb, 0xcc, 1.0)), - equals('#aabbcc')); + expect( + await _deprotofy(_rgb(0xaa, 0xbb, 0xcc, 1.0)), + equals('#aabbcc'), + ); }); test("with red above 255", () async { - expect(await _deprotofy(_rgb(256, 0, 0, 1.0)), - equals('hsl(0, 100.7874015748%, 50.1960784314%)')); + expect( + await _deprotofy(_rgb(256, 0, 0, 1.0)), + equals('hsl(0, 100.7874015748%, 50.1960784314%)'), + ); }); test("with green above 255", () async { - expect(await _deprotofy(_rgb(0, 256, 0, 1.0)), - equals('hsl(120, 100.7874015748%, 50.1960784314%)')); + expect( + await _deprotofy(_rgb(0, 256, 0, 1.0)), + equals('hsl(120, 100.7874015748%, 50.1960784314%)'), + ); }); test("with blue above 255", () async { - expect(await _deprotofy(_rgb(0, 0, 256, 1.0)), - equals('hsl(240, 100.7874015748%, 50.1960784314%)')); + expect( + await _deprotofy(_rgb(0, 0, 256, 1.0)), + equals('hsl(240, 100.7874015748%, 50.1960784314%)'), + ); }); }); group("with alpha", () { test("0", () async { - expect(await _deprotofy(_rgb(10, 20, 30, 0.0)), - equals('rgba(10, 20, 30, 0)')); + expect( + await _deprotofy(_rgb(10, 20, 30, 0.0)), + equals('rgba(10, 20, 30, 0)'), + ); }); test("between 0 and 1", () async { - expect(await _deprotofy(_rgb(10, 20, 30, 0.123)), - equals('rgba(10, 20, 30, 0.123)')); + expect( + await _deprotofy(_rgb(10, 20, 30, 0.123)), + equals('rgba(10, 20, 30, 0.123)'), + ); }); }); }); @@ -1006,27 +1186,37 @@ void main() { group("hue", () { test("0", () async { expect( - await _deprotofy(_hsl(0, 50, 50, 1.0)), "hsl(0, 50%, 50%)"); + await _deprotofy(_hsl(0, 50, 50, 1.0)), + "hsl(0, 50%, 50%)", + ); }); test("360", () async { expect( - await _deprotofy(_hsl(360, 50, 50, 1.0)), "hsl(0, 50%, 50%)"); + await _deprotofy(_hsl(360, 50, 50, 1.0)), + "hsl(0, 50%, 50%)", + ); }); test("below 0", () async { - expect(await _deprotofy(_hsl(-100, 50, 50, 1.0)), - "hsl(260, 50%, 50%)"); + expect( + await _deprotofy(_hsl(-100, 50, 50, 1.0)), + "hsl(260, 50%, 50%)", + ); }); test("between 0 and 360", () async { - expect(await _deprotofy(_hsl(100, 50, 50, 1.0)), - "hsl(100, 50%, 50%)"); + expect( + await _deprotofy(_hsl(100, 50, 50, 1.0)), + "hsl(100, 50%, 50%)", + ); }); test("above 360", () async { - expect(await _deprotofy(_hsl(560, 50, 50, 1.0)), - "hsl(200, 50%, 50%)"); + expect( + await _deprotofy(_hsl(560, 50, 50, 1.0)), + "hsl(200, 50%, 50%)", + ); }); }); @@ -1037,12 +1227,16 @@ void main() { test("100", () async { expect( - await _deprotofy(_hsl(0, 100, 50, 1.0)), "hsl(0, 100%, 50%)"); + await _deprotofy(_hsl(0, 100, 50, 1.0)), + "hsl(0, 100%, 50%)", + ); }); test("in the middle", () async { expect( - await _deprotofy(_hsl(0, 42, 50, 1.0)), "hsl(0, 42%, 50%)"); + await _deprotofy(_hsl(0, 42, 50, 1.0)), + "hsl(0, 42%, 50%)", + ); }); }); @@ -1053,25 +1247,33 @@ void main() { test("100", () async { expect( - await _deprotofy(_hsl(0, 50, 100, 1.0)), "hsl(0, 50%, 100%)"); + await _deprotofy(_hsl(0, 50, 100, 1.0)), + "hsl(0, 50%, 100%)", + ); }); test("in the middle", () async { expect( - await _deprotofy(_hsl(0, 50, 42, 1.0)), "hsl(0, 50%, 42%)"); + await _deprotofy(_hsl(0, 50, 42, 1.0)), + "hsl(0, 50%, 42%)", + ); }); }); }); group("with alpha", () { test("0", () async { - expect(await _deprotofy(_hsl(10, 20, 30, 0.0)), - "hsla(10, 20%, 30%, 0)"); + expect( + await _deprotofy(_hsl(10, 20, 30, 0.0)), + "hsla(10, 20%, 30%, 0)", + ); }); test("between 0 and 1", () async { - expect(await _deprotofy(_hsl(10, 20, 30, 0.123)), - "hsla(10, 20%, 30%, 0.123)"); + expect( + await _deprotofy(_hsl(10, 20, 30, 0.123)), + "hsla(10, 20%, 30%, 0.123)", + ); }); }); }); @@ -1082,80 +1284,88 @@ void main() { group("with brackets", () { group("with unknown separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = true - ..separator = ListSeparator.UNDECIDED), - "[]"); + Value() + ..list = (Value_List() + ..hasBrackets = true + ..separator = ListSeparator.UNDECIDED), + "[]", + ); }); group("with a comma separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = true - ..separator = ListSeparator.COMMA), - "[]"); + Value() + ..list = (Value_List() + ..hasBrackets = true + ..separator = ListSeparator.COMMA), + "[]", + ); }); group("with a space separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = true - ..separator = ListSeparator.SPACE), - "[]"); + Value() + ..list = (Value_List() + ..hasBrackets = true + ..separator = ListSeparator.SPACE), + "[]", + ); }); group("with a slash separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = true - ..separator = ListSeparator.SLASH), - "[]"); + Value() + ..list = (Value_List() + ..hasBrackets = true + ..separator = ListSeparator.SLASH), + "[]", + ); }); }); group("without brackets", () { group("with unknown separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = false - ..separator = ListSeparator.UNDECIDED), - "()", - inspect: true); + Value() + ..list = (Value_List() + ..hasBrackets = false + ..separator = ListSeparator.UNDECIDED), + "()", + inspect: true, + ); }); group("with a comma separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = false - ..separator = ListSeparator.COMMA), - "()", - inspect: true); + Value() + ..list = (Value_List() + ..hasBrackets = false + ..separator = ListSeparator.COMMA), + "()", + inspect: true, + ); }); group("with a space separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = false - ..separator = ListSeparator.SPACE), - "()", - inspect: true); + Value() + ..list = (Value_List() + ..hasBrackets = false + ..separator = ListSeparator.SPACE), + "()", + inspect: true, + ); }); group("with a slash separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..hasBrackets = false - ..separator = ListSeparator.SLASH), - "()", - inspect: true); + Value() + ..list = (Value_List() + ..hasBrackets = false + ..separator = ListSeparator.SLASH), + "()", + inspect: true, + ); }); }); }); @@ -1164,88 +1374,98 @@ void main() { group("with brackets", () { group("with unknown separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = true - ..separator = ListSeparator.UNDECIDED), - "[true]"); + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = true + ..separator = ListSeparator.UNDECIDED), + "[true]", + ); }); test("with a comma separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = true - ..separator = ListSeparator.COMMA), - inspect: true), - "[true,]"); - }); - - group("with a space separator", () { - _testSerializationAndRoundTrip( + await _deprotofy( Value() ..list = (Value_List() ..contents.add(_true) ..hasBrackets = true - ..separator = ListSeparator.SPACE), - "[true]"); + ..separator = ListSeparator.COMMA), + inspect: true, + ), + "[true,]", + ); + }); + + group("with a space separator", () { + _testSerializationAndRoundTrip( + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = true + ..separator = ListSeparator.SPACE), + "[true]", + ); }); group("with a slash separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = true - ..separator = ListSeparator.SLASH), - "[true]"); + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = true + ..separator = ListSeparator.SLASH), + "[true]", + ); }); }); group("without brackets", () { group("with unknown separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = false - ..separator = ListSeparator.UNDECIDED), - "true"); + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = false + ..separator = ListSeparator.UNDECIDED), + "true", + ); }); test("with a comma separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = false - ..separator = ListSeparator.COMMA), - inspect: true), - "(true,)"); - }); - - group("with a space separator", () { - _testSerializationAndRoundTrip( + await _deprotofy( Value() ..list = (Value_List() ..contents.add(_true) ..hasBrackets = false - ..separator = ListSeparator.SPACE), - "true"); + ..separator = ListSeparator.COMMA), + inspect: true, + ), + "(true,)", + ); + }); + + group("with a space separator", () { + _testSerializationAndRoundTrip( + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = false + ..separator = ListSeparator.SPACE), + "true", + ); }); group("with a slash separator", () { _testSerializationAndRoundTrip( - Value() - ..list = (Value_List() - ..contents.add(_true) - ..hasBrackets = false - ..separator = ListSeparator.SLASH), - "true"); + Value() + ..list = (Value_List() + ..contents.add(_true) + ..hasBrackets = false + ..separator = ListSeparator.SLASH), + "true", + ); }); }); }); @@ -1254,76 +1474,88 @@ void main() { group("with brackets", () { test("with a comma separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = true - ..separator = ListSeparator.COMMA), - inspect: true), - "[true, null, false]"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = true + ..separator = ListSeparator.COMMA), + inspect: true, + ), + "[true, null, false]", + ); }); test("with a space separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = true - ..separator = ListSeparator.SPACE), - inspect: true), - "[true null false]"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = true + ..separator = ListSeparator.SPACE), + inspect: true, + ), + "[true null false]", + ); }); test("with a slash separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = true - ..separator = ListSeparator.SLASH), - inspect: true), - "[true / null / false]"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = true + ..separator = ListSeparator.SLASH), + inspect: true, + ), + "[true / null / false]", + ); }); }); group("without brackets", () { test("with a comma separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = false - ..separator = ListSeparator.COMMA), - inspect: true), - "true, null, false"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = false + ..separator = ListSeparator.COMMA), + inspect: true, + ), + "true, null, false", + ); }); test("with a space separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = false - ..separator = ListSeparator.SPACE), - inspect: true), - "true null false"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = false + ..separator = ListSeparator.SPACE), + inspect: true, + ), + "true null false", + ); }); test("with a slash separator", () async { expect( - await _deprotofy( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _null, _false]) - ..hasBrackets = false - ..separator = ListSeparator.SLASH), - inspect: true), - "true / null / false"); + await _deprotofy( + Value() + ..list = (Value_List() + ..contents.addAll([_true, _null, _false]) + ..hasBrackets = false + ..separator = ListSeparator.SLASH), + inspect: true, + ), + "true / null / false", + ); }); }); }); @@ -1332,280 +1564,386 @@ void main() { group("an argument list", () { test("with no elements", () async { expect( - await _roundTrip(Value() + await _roundTrip( + Value() ..argumentList = - (Value_ArgumentList()..separator = ListSeparator.UNDECIDED)), - equals(Value() + (Value_ArgumentList()..separator = ListSeparator.UNDECIDED), + ), + equals( + Value() ..argumentList = (Value_ArgumentList() ..id = 1 - ..separator = ListSeparator.UNDECIDED))); + ..separator = ListSeparator.UNDECIDED), + ), + ); }); test("with comma separator", () async { expect( - await _roundTrip(Value() + await _roundTrip( + Value() ..argumentList = (Value_ArgumentList() ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.COMMA)), - equals(Value() + ..separator = ListSeparator.COMMA), + ), + equals( + Value() ..argumentList = (Value_ArgumentList() ..id = 1 ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.COMMA))); + ..separator = ListSeparator.COMMA), + ), + ); }); test("with space separator", () async { expect( - await _roundTrip(Value() + await _roundTrip( + Value() ..argumentList = (Value_ArgumentList() ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.SPACE)), - equals(Value() + ..separator = ListSeparator.SPACE), + ), + equals( + Value() ..argumentList = (Value_ArgumentList() ..id = 1 ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.SPACE))); + ..separator = ListSeparator.SPACE), + ), + ); }); test("with slash separator", () async { expect( - await _roundTrip(Value() + await _roundTrip( + Value() ..argumentList = (Value_ArgumentList() ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.SLASH)), - equals(Value() + ..separator = ListSeparator.SLASH), + ), + equals( + Value() ..argumentList = (Value_ArgumentList() ..id = 1 ..contents.addAll([_true, _false, _null]) - ..separator = ListSeparator.SLASH))); + ..separator = ListSeparator.SLASH), + ), + ); }); test("with keywords", () async { expect( - await _roundTrip(Value() + await _roundTrip( + Value() ..argumentList = (Value_ArgumentList() ..keywords.addAll({"arg1": _true, "arg2": _false}) - ..separator = ListSeparator.COMMA)), - equals(Value() + ..separator = ListSeparator.COMMA), + ), + equals( + Value() ..argumentList = (Value_ArgumentList() ..id = 1 ..keywords.addAll({"arg1": _true, "arg2": _false}) - ..separator = ListSeparator.COMMA))); + ..separator = ListSeparator.COMMA), + ), + ); }); }); group("a map", () { group("with no elements", () { - _testSerializationAndRoundTrip(Value()..map = Value_Map(), "()", - inspect: true); + _testSerializationAndRoundTrip( + Value()..map = Value_Map(), + "()", + inspect: true, + ); }); test("with one element", () async { expect( - await _deprotofy( - Value() - ..map = (Value_Map() - ..entries.add(Value_Map_Entry() - ..key = _true - ..value = _false)), - inspect: true), - "(true: false)"); + await _deprotofy( + Value() + ..map = (Value_Map() + ..entries.add( + Value_Map_Entry() + ..key = _true + ..value = _false, + )), + inspect: true, + ), + "(true: false)", + ); }); test("with multiple elements", () async { expect( - await _deprotofy( - Value() - ..map = (Value_Map() - ..entries.addAll([ - Value_Map_Entry() - ..key = _true - ..value = _false, - Value_Map_Entry() - ..key = - (Value()..number = (Value_Number()..value = 1.0)) - ..value = - (Value()..number = (Value_Number()..value = 2.0)), - Value_Map_Entry() - ..key = (Value() - ..string = (Value_String() - ..text = "a" - ..quoted = false)) - ..value = (Value() - ..string = (Value_String() - ..text = "b" - ..quoted = false)) - ])), - inspect: true), - "(true: false, 1: 2, a: b)"); + await _deprotofy( + Value() + ..map = (Value_Map() + ..entries.addAll([ + Value_Map_Entry() + ..key = _true + ..value = _false, + Value_Map_Entry() + ..key = (Value()..number = (Value_Number()..value = 1.0)) + ..value = (Value()..number = (Value_Number()..value = 2.0)), + Value_Map_Entry() + ..key = (Value() + ..string = (Value_String() + ..text = "a" + ..quoted = false)) + ..value = (Value() + ..string = (Value_String() + ..text = "b" + ..quoted = false)), + ])), + inspect: true, + ), + "(true: false, 1: 2, a: b)", + ); }); }); group("a calculation", () { test("with a string argument", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..string = "var(--foo)"))), - "calc(var(--foo))"); + ..arguments.add( + Value_Calculation_CalculationValue()..string = "var(--foo)", + )), + ), + "calc(var(--foo))", + ); }); test("with an interpolation argument", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..interpolation = "var(--foo)"))), - "calc((var(--foo)))"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..interpolation = "var(--foo)", + )), + ), + "calc((var(--foo)))", + ); }); test("with number arguments", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "clamp" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 3.0 - ..numerators.add("em"))))), - "clamp(1%, 2px, 3em)"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 3.0 + ..numerators.add("em")), + )), + ), + "clamp(1%, 2px, 3em)", + ); }); test("with a calculation argument", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "min" - ..arguments.add(Value_Calculation_CalculationValue() - ..calculation = (Value_Calculation() - ..name = "max" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 3.0 - ..numerators.add("em"))))), - "min(max(1%, 2px), 3em)"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..calculation = (Value_Calculation() + ..name = "max" + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")), + )), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 3.0 + ..numerators.add("em")), + )), + ), + "min(max(1%, 2px), 3em)", + ); }); test("with an operation", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..operation = (Value_Calculation_CalculationOperation() - ..operator = CalculationOperator.PLUS - ..left = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..right = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))))))), - "calc(1% + 2px)"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..operation = (Value_Calculation_CalculationOperation() + ..operator = CalculationOperator.PLUS + ..left = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%"))) + ..right = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px")))), + )), + ), + "calc(1% + 2px)", + ); }); group("simplifies", () { test("an operation", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..operation = (Value_Calculation_CalculationOperation() - ..operator = CalculationOperator.PLUS - ..left = (Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..right = (Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0)))))), - "3"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..operation = (Value_Calculation_CalculationOperation() + ..operator = CalculationOperator.PLUS + ..left = (Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0)) + ..right = (Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0))), + )), + ), + "3", + ); }); test("a nested operation", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..operation = (Value_Calculation_CalculationOperation() - ..operator = CalculationOperator.PLUS - ..left = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("%"))) - ..right = (Value_Calculation_CalculationValue() - ..operation = (Value_Calculation_CalculationOperation() - ..operator = CalculationOperator.PLUS - ..left = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("px"))) - ..right = (Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 3.0 - ..numerators.add("px"))))))))), - "calc(1% + 5px)"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..operation = (Value_Calculation_CalculationOperation() + ..operator = CalculationOperator.PLUS + ..left = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("%"))) + ..right = (Value_Calculation_CalculationValue() + ..operation = + (Value_Calculation_CalculationOperation() + ..operator = CalculationOperator.PLUS + ..left = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("px"))) + ..right = (Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 3.0 + ..numerators.add( + "px", + )))))), + )), + ), + "calc(1% + 5px)", + ); }); test("min", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "min" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 3.0)))), - "1"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 3.0), + )), + ), + "1", + ); }); test("max", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "max" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 3.0)))), - "3"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 3.0), + )), + ), + "3", + ); }); test("clamp", () async { expect( - await _deprotofy(Value() + await _deprotofy( + Value() ..calculation = (Value_Calculation() ..name = "clamp" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 3.0)))), - "2"); + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 3.0), + )), + ), + "2", + ); }); }); }); @@ -1625,149 +1963,207 @@ void main() { group("and rejects", () { group("a color", () { test("with RGB alpha below 0", () async { - await _expectDeprotofyError(_rgb(0, 0, 0, -0.1), - "Color.alpha must be between 0 and 1, was -0.1"); + await _expectDeprotofyError( + _rgb(0, 0, 0, -0.1), + "Color.alpha must be between 0 and 1, was -0.1", + ); }); test("with RGB alpha above 1", () async { - await _expectDeprotofyError(_rgb(0, 0, 0, 1.1), - "Color.alpha must be between 0 and 1, was 1.1"); + await _expectDeprotofyError( + _rgb(0, 0, 0, 1.1), + "Color.alpha must be between 0 and 1, was 1.1", + ); }); test("with HSL alpha below 0", () async { - await _expectDeprotofyError(_hsl(0, 0, 0, -0.1), - "Color.alpha must be between 0 and 1, was -0.1"); + await _expectDeprotofyError( + _hsl(0, 0, 0, -0.1), + "Color.alpha must be between 0 and 1, was -0.1", + ); }); test("with HSL alpha above 1", () async { - await _expectDeprotofyError(_hsl(0, 0, 0, 1.1), - "Color.alpha must be between 0 and 1, was 1.1"); + await _expectDeprotofyError( + _hsl(0, 0, 0, 1.1), + "Color.alpha must be between 0 and 1, was 1.1", + ); }); }); test("a list with multiple elements and an unknown separator", () async { await _expectDeprotofyError( - Value() - ..list = (Value_List() - ..contents.addAll([_true, _false]) - ..separator = ListSeparator.UNDECIDED), - endsWith("can't have an undecided separator because it has 2 " - "elements")); + Value() + ..list = (Value_List() + ..contents.addAll([_true, _false]) + ..separator = ListSeparator.UNDECIDED), + endsWith( + "can't have an undecided separator because it has 2 " + "elements", + ), + ); }); test("an arglist with an unknown id", () async { await _expectDeprotofyError( - Value()..argumentList = (Value_ArgumentList()..id = 1), - equals( - "Value.ArgumentList.id 1 doesn't match any known argument lists")); + Value()..argumentList = (Value_ArgumentList()..id = 1), + equals( + "Value.ArgumentList.id 1 doesn't match any known argument lists", + ), + ); }); group("a calculation", () { group("with too many arguments", () { test("for calc", () async { await _expectDeprotofyError( - Value() - ..calculation = (Value_Calculation() - ..name = "calc" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0))), - equals("Value.Calculation.arguments must have exactly one " - "argument for calc().")); + Value() + ..calculation = (Value_Calculation() + ..name = "calc" + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0), + )), + equals( + "Value.Calculation.arguments must have exactly one " + "argument for calc().", + ), + ); }); test("for clamp", () async { await _expectDeprotofyError( - Value() - ..calculation = (Value_Calculation() - ..name = "clamp" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 1.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 2.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 3.0)) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number()..value = 4.0))), - equals("Value.Calculation.arguments must have 1 to 3 " - "arguments for clamp().")); + Value() + ..calculation = (Value_Calculation() + ..name = "clamp" + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 1.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 2.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 3.0), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number()..value = 4.0), + )), + equals( + "Value.Calculation.arguments must have 1 to 3 " + "arguments for clamp().", + ), + ); }); }); group("with too few arguments", () { test("for calc", () async { await _expectDeprotofyError( - Value()..calculation = (Value_Calculation()..name = "calc"), - equals("Value.Calculation.arguments must have exactly one " - "argument for calc().")); + Value()..calculation = (Value_Calculation()..name = "calc"), + equals( + "Value.Calculation.arguments must have exactly one " + "argument for calc().", + ), + ); }); test("for clamp", () async { await _expectDeprotofyError( - Value()..calculation = (Value_Calculation()..name = "clamp"), - equals("Value.Calculation.arguments must have 1 to 3 " - "arguments for clamp().")); + Value()..calculation = (Value_Calculation()..name = "clamp"), + equals( + "Value.Calculation.arguments must have 1 to 3 " + "arguments for clamp().", + ), + ); }); test("for min", () async { await _expectDeprotofyError( - Value()..calculation = (Value_Calculation()..name = "min"), - equals("Value.Calculation.arguments must have at least 1 " - "argument for min().")); + Value()..calculation = (Value_Calculation()..name = "min"), + equals( + "Value.Calculation.arguments must have at least 1 " + "argument for min().", + ), + ); }); test("for max", () async { await _expectDeprotofyError( - Value()..calculation = (Value_Calculation()..name = "max"), - equals("Value.Calculation.arguments must have at least 1 " - "argument for max().")); + Value()..calculation = (Value_Calculation()..name = "max"), + equals( + "Value.Calculation.arguments must have at least 1 " + "argument for max().", + ), + ); }); }); - test("reports a compilation failure when simplification fails", - () async { - _process.send(compileString("a {b: foo()}", functions: [r"foo()"])); - - var request = await getFunctionCallRequest(_process); - expect(request.arguments, isEmpty); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = (Value() - ..calculation = (Value_Calculation() - ..name = "min" - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 1.0 - ..numerators.add("px"))) - ..arguments.add(Value_Calculation_CalculationValue() - ..number = (Value_Number() - ..value = 2.0 - ..numerators.add("s"))))))); - - var failure = await getCompileFailure(_process); - expect(failure.message, equals("1px and 2s are incompatible.")); - expect(_process.close(), completes); - }); + test( + "reports a compilation failure when simplification fails", + () async { + _process.send(compileString("a {b: foo()}", functions: [r"foo()"])); + + var request = await getFunctionCallRequest(_process); + expect(request.arguments, isEmpty); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = (Value() + ..calculation = (Value_Calculation() + ..name = "min" + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 1.0 + ..numerators.add("px")), + ) + ..arguments.add( + Value_Calculation_CalculationValue() + ..number = (Value_Number() + ..value = 2.0 + ..numerators.add("s")), + )))), + ); + + var failure = await getCompileFailure(_process); + expect(failure.message, equals("1px and 2s are incompatible.")); + expect(_process.close(), completes); + }, + ); }); group("reports a compilation error for a function with a signature", () { Future expectSignatureError( - String signature, Object message) async { - _process.send(compileString( + String signature, + Object message, + ) async { + _process.send( + compileString( "@use 'sass:meta';\na {b: meta.inspect(foo())}", - functions: [r"foo()"])); + functions: [r"foo()"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, isEmpty); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = (Value() - ..hostFunction = (Value_HostFunction() - ..id = 1234 - ..signature = signature)))); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = (Value() + ..hostFunction = (Value_HostFunction() + ..id = 1234 + ..signature = signature))), + ); var failure = await getCompileFailure(_process); expect(failure.message, message); @@ -1776,27 +2172,37 @@ void main() { test("that's empty", () async { await expectSignatureError( - "", r'Invalid signature "": Expected identifier.'); + "", + r'Invalid signature "": Expected identifier.', + ); }); test("that's just a name", () async { await expectSignatureError( - "foo", r'Invalid signature "foo": expected "(".'); + "foo", + r'Invalid signature "foo": expected "(".', + ); }); test("without a closing paren", () async { await expectSignatureError( - r"foo($bar", r'Invalid signature "foo($bar": expected ")".'); + r"foo($bar", + r'Invalid signature "foo($bar": expected ")".', + ); }); test("with text after the closing paren", () async { - await expectSignatureError(r"foo() ", - r'Invalid signature "foo() ": expected no more input.'); + await expectSignatureError( + r"foo() ", + r'Invalid signature "foo() ": expected no more input.', + ); }); test("with invalid arguments", () async { await expectSignatureError( - r"foo($)", r'Invalid signature "foo($)": Expected identifier.'); + r"foo($)", + r'Invalid signature "foo($)": Expected identifier.', + ); }); }); }); @@ -1806,20 +2212,25 @@ void main() { /// Evaluates [sassScript] in the compiler, passes it to a custom function, and /// returns the protocol buffer result. Future _protofy(String sassScript) async { - _process.send(compileString(""" -@use 'sass:list'; -@use 'sass:map'; -@use 'sass:math'; -@use 'sass:meta'; -@use 'sass:string'; - -@function capture-args(\$args...) { - \$_: meta.keywords(\$args); - @return \$args; -} + _process.send( + compileString( + """ + @use 'sass:list'; + @use 'sass:map'; + @use 'sass:math'; + @use 'sass:meta'; + @use 'sass:string'; + + @function capture-args(\$args...) { + \$_: meta.keywords(\$args); + @return \$args; + } -\$_: foo(($sassScript)); -""", functions: [r"foo($arg)"])); + \$_: foo(($sassScript)); + """, + functions: [r"foo($arg)"], + ), + ); var request = await getFunctionCallRequest(_process); expect(_process.kill(), completes); return request.arguments.single; @@ -1831,10 +2242,15 @@ Future _protofy(String sassScript) async { /// /// This is necessary for values that can be serialized but also have metadata /// that's not visible in the serialized form. -void _testSerializationAndRoundTrip(Value value, String expected, - {bool inspect = false}) { - test("is serialized correctly", - () async => expect(await _deprotofy(value, inspect: inspect), expected)); +void _testSerializationAndRoundTrip( + Value value, + String expected, { + bool inspect = false, +}) { + test( + "is serialized correctly", + () async => expect(await _deprotofy(value, inspect: inspect), expected), + ); test("preserves metadata", () => _assertRoundTrips(value)); } @@ -1844,18 +2260,23 @@ void _testSerializationAndRoundTrip(Value value, String expected, /// If [inspect] is true, this returns the value as serialized by the /// `meta.inspect()` function. Future _deprotofy(Value value, {bool inspect = false}) async { - _process.send(compileString( + _process.send( + compileString( inspect ? "@use 'sass:meta';\na {b: meta.inspect(foo())}" : "a {b: foo()}", - functions: [r"foo()"])); + functions: [r"foo()"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, isEmpty); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = value)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = value), + ); var success = await getCompileSuccess(_process); expect(_process.close(), completes); @@ -1869,10 +2290,12 @@ Future _expectDeprotofyError(Value value, Object message) async { var request = await getFunctionCallRequest(_process); expect(request.arguments, isEmpty); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = value)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = value), + ); await expectParamsError(_process, errorId, message); await _process.kill(); @@ -1891,16 +2314,21 @@ Future _assertRoundTrips(Value value) async => /// Sends [value] to the compiler to convert to a native Sass value, then sends /// it back out to the host as a protocol buffer and returns the result. Future _roundTrip(Value value) async { - _process.send(compileString(""" -\$_: outbound(inbound()); -""", functions: ["inbound()", r"outbound($arg)"])); + _process.send( + compileString( + "\$_: outbound(inbound());", + functions: ["inbound()", r"outbound($arg)"], + ), + ); var request = await getFunctionCallRequest(_process); expect(request.arguments, isEmpty); - _process.send(InboundMessage() - ..functionCallResponse = (InboundMessage_FunctionCallResponse() - ..id = request.id - ..success = value)); + _process.send( + InboundMessage() + ..functionCallResponse = (InboundMessage_FunctionCallResponse() + ..id = request.id + ..success = value), + ); request = await getFunctionCallRequest(_process); expect(_process.kill(), completes); @@ -1928,7 +2356,9 @@ Value _hsl(num hue, num saturation, num lightness, double alpha) => Value() /// Asserts that [process] emits a [CompileFailure] result with the given /// [message] on its protobuf stream and causes the compilation to fail. Future _expectFunctionError( - EmbeddedProcess process, Object message) async { + EmbeddedProcess process, + Object message, +) async { var failure = await getCompileFailure(process); expect(failure.message, equals(message)); } diff --git a/test/embedded/importer_test.dart b/test/embedded/importer_test.dart index ee4d90c0f..de036c6d4 100644 --- a/test/embedded/importer_test.dart +++ b/test/embedded/importer_test.dart @@ -23,85 +23,126 @@ void main() { group("emits a protocol error", () { test("for a response without a corresponding request ID", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id + 1)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id + 1), + ); await expectParamsError( - process, - errorId, - "Response ID ${request.id + 1} doesn't match any outstanding " - "requests in compilation $defaultCompilationId."); + process, + errorId, + "Response ID ${request.id + 1} doesn't match any outstanding " + "requests in compilation $defaultCompilationId.", + ); await process.shouldExit(76); }); test("for a response that doesn't match the request type", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse()..id = request.id)); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse()..id = request.id), + ); await expectParamsError( - process, - errorId, - "Request ID ${request.id} doesn't match response type " - "InboundMessage_ImportResponse in compilation " - "$defaultCompilationId."); + process, + errorId, + "Request ID ${request.id} doesn't match response type " + "InboundMessage_ImportResponse in compilation " + "$defaultCompilationId.", + ); await process.shouldExit(76); }); test("for an unset importer", () async { - process.send(compileString("a {b: c}", - importers: [InboundMessage_CompileRequest_Importer()])); + process.send( + compileString( + "a {b: c}", + importers: [InboundMessage_CompileRequest_Importer()], + ), + ); await expectParamsError( - process, errorId, "Missing mandatory field Importer.importer"); + process, + errorId, + "Missing mandatory field Importer.importer", + ); await process.shouldExit(76); }); group("for an importer with nonCanonicalScheme set:", () { test("path", () async { - process.send(compileString("a {b: c}", importers: [ - InboundMessage_CompileRequest_Importer( - path: "somewhere", nonCanonicalScheme: ["u"]) - ])); + process.send( + compileString( + "a {b: c}", + importers: [ + InboundMessage_CompileRequest_Importer( + path: "somewhere", + nonCanonicalScheme: ["u"], + ), + ], + ), + ); await expectParamsError( - process, - errorId, - "Importer.non_canonical_scheme may only be set along with " - "Importer.importer.importer_id"); + process, + errorId, + "Importer.non_canonical_scheme may only be set along with " + "Importer.importer.importer_id", + ); await process.shouldExit(76); }); test("file importer", () async { - process.send(compileString("a {b: c}", importers: [ - InboundMessage_CompileRequest_Importer( - fileImporterId: 1, nonCanonicalScheme: ["u"]) - ])); + process.send( + compileString( + "a {b: c}", + importers: [ + InboundMessage_CompileRequest_Importer( + fileImporterId: 1, + nonCanonicalScheme: ["u"], + ), + ], + ), + ); await expectParamsError( - process, - errorId, - "Importer.non_canonical_scheme may only be set along with " - "Importer.importer.importer_id"); + process, + errorId, + "Importer.non_canonical_scheme may only be set along with " + "Importer.importer.importer_id", + ); await process.shouldExit(76); }); test("unset", () async { - process.send(compileString("a {b: c}", + process.send( + compileString( + "a {b: c}", importer: InboundMessage_CompileRequest_Importer( - nonCanonicalScheme: ["u"]))); + nonCanonicalScheme: ["u"], + ), + ), + ); await expectParamsError( - process, - errorId, - "Importer.non_canonical_scheme may only be set along with " - "Importer.importer.importer_id"); + process, + errorId, + "Importer.non_canonical_scheme may only be set along with " + "Importer.importer.importer_id", + ); await process.shouldExit(76); }); }); @@ -110,34 +151,52 @@ void main() { group("canonicalization", () { group("emits a compile failure", () { test("for a canonicalize response with an empty URL", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = 1, + ], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = request.id - ..url = "")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = request.id + ..url = ""), + ); await _expectImportError( - process, 'The importer must return an absolute URL, was ""'); + process, + 'The importer must return an absolute URL, was ""', + ); await process.close(); }); test("for a canonicalize response with a relative URL", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = 1, + ], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = request.id - ..url = "relative")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = request.id + ..url = "relative"), + ); - await _expectImportError(process, - 'The importer must return an absolute URL, was "relative"'); + await _expectImportError( + process, + 'The importer must return an absolute URL, was "relative"', + ); await process.close(); }); }); @@ -146,9 +205,14 @@ void main() { var importerId = 5679; late OutboundMessage_CanonicalizeRequest request; setUp(() async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = importerId - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = importerId, + ], + ), + ); request = await getCanonicalizeRequest(process); }); @@ -164,15 +228,20 @@ void main() { }); test("errors cause compilation to fail", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = request.id - ..error = "oh no")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = request.id + ..error = "oh no"), + ); var failure = await getCompileFailure(process); expect(failure.message, equals('oh no')); @@ -182,14 +251,19 @@ void main() { }); test("null results count as not found", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id), + ); var failure = await getCompileFailure(process); expect(failure.message, equals("Can't find stylesheet to import.")); @@ -199,9 +273,12 @@ void main() { group("the containing URL", () { test("is unset for a potentially canonical scheme", () async { - process.send(compileString('@use "u:orange"', importers: [ - InboundMessage_CompileRequest_Importer(importerId: 1) - ])); + process.send( + compileString( + '@use "u:orange"', + importers: [InboundMessage_CompileRequest_Importer(importerId: 1)], + ), + ); var request = await getCanonicalizeRequest(process); expect(request.hasContainingUrl(), isFalse); @@ -210,12 +287,18 @@ void main() { group("for a non-canonical scheme", () { test("is set to the original URL", () async { - process.send(compileString('@use "u:orange"', + process.send( + compileString( + '@use "u:orange"', importers: [ InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: ["u"]) + importerId: 1, + nonCanonicalScheme: ["u"], + ), ], - url: "x:original.scss")); + url: "x:original.scss", + ), + ); var request = await getCanonicalizeRequest(process); expect(request.containingUrl, equals("x:original.scss")); @@ -223,10 +306,17 @@ void main() { }); test("is unset to the original URL is unset", () async { - process.send(compileString('@use "u:orange"', importers: [ - InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: ["u"]) - ])); + process.send( + compileString( + '@use "u:orange"', + importers: [ + InboundMessage_CompileRequest_Importer( + importerId: 1, + nonCanonicalScheme: ["u"], + ), + ], + ), + ); var request = await getCanonicalizeRequest(process); expect(request.hasContainingUrl(), isFalse); @@ -236,11 +326,15 @@ void main() { group("for a schemeless load", () { test("is set to the original URL", () async { - process.send(compileString('@use "orange"', + process.send( + compileString( + '@use "orange"', importers: [ - InboundMessage_CompileRequest_Importer(importerId: 1) + InboundMessage_CompileRequest_Importer(importerId: 1), ], - url: "x:original.scss")); + url: "x:original.scss", + ), + ); var request = await getCanonicalizeRequest(process); expect(request.containingUrl, equals("x:original.scss")); @@ -248,9 +342,14 @@ void main() { }); test("is unset to the original URL is unset", () async { - process.send(compileString('@use "u:orange"', importers: [ - InboundMessage_CompileRequest_Importer(importerId: 1) - ])); + process.send( + compileString( + '@use "u:orange"', + importers: [ + InboundMessage_CompileRequest_Importer(importerId: 1), + ], + ), + ); var request = await getCanonicalizeRequest(process); expect(request.hasContainingUrl(), isFalse); @@ -262,66 +361,98 @@ void main() { test( "fails if the importer returns a canonical URL with a non-canonical " "scheme", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: ["u"]) - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer( + importerId: 1, + nonCanonicalScheme: ["u"], + ), + ], + ), + ); var request = await getCanonicalizeRequest(process); - process.send(InboundMessage( + process.send( + InboundMessage( canonicalizeResponse: InboundMessage_CanonicalizeResponse( - id: request.id, url: "u:other"))); + id: request.id, + url: "u:other", + ), + ), + ); await _expectImportError( - process, contains('a scheme declared as non-canonical')); + process, + contains('a scheme declared as non-canonical'), + ); await process.close(); }); test("attempts importers in order", () async { - process.send(compileString("@use 'other'", importers: [ - for (var i = 0; i < 10; i++) - InboundMessage_CompileRequest_Importer()..importerId = i - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + for (var i = 0; i < 10; i++) + InboundMessage_CompileRequest_Importer()..importerId = i, + ], + ), + ); for (var i = 0; i < 10; i++) { var request = await getCanonicalizeRequest(process); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id), + ); } await process.close(); }); test("tries resolved URL using the original importer first", () async { - process.send(compileString("@use 'midstream'", importers: [ - for (var i = 0; i < 10; i++) - InboundMessage_CompileRequest_Importer()..importerId = i - ])); + process.send( + compileString( + "@use 'midstream'", + importers: [ + for (var i = 0; i < 10; i++) + InboundMessage_CompileRequest_Importer()..importerId = i, + ], + ), + ); for (var i = 0; i < 5; i++) { var request = await getCanonicalizeRequest(process); expect(request.url, equals("midstream")); expect(request.importerId, equals(i)); - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = request.id)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = request.id), + ); } var canonicalize = await getCanonicalizeRequest(process); expect(canonicalize.importerId, equals(5)); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = canonicalize.id - ..url = "custom:foo/bar")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = canonicalize.id + ..url = "custom:foo/bar"), + ); var import = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = import.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "@use 'upstream'"))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = import.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "@use 'upstream'")), + ); canonicalize = await getCanonicalizeRequest(process); expect(canonicalize.importerId, equals(5)); @@ -334,20 +465,29 @@ void main() { group("importing", () { group("emits a compile failure", () { test("for an import result with a relative sourceMapUrl", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = 1, + ], + ), + ); await _canonicalize(process); var import = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = import.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..sourceMapUrl = "relative"))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = import.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..sourceMapUrl = "relative")), + ); - await _expectImportError(process, - 'The importer must return an absolute URL, was "relative"'); + await _expectImportError( + process, + 'The importer must return an absolute URL, was "relative"', + ); await process.close(); }); }); @@ -356,15 +496,22 @@ void main() { var importerId = 5678; late OutboundMessage_ImportRequest request; setUp(() async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = importerId - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = importerId, + ], + ), + ); var canonicalize = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = canonicalize.id - ..url = "custom:foo")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = canonicalize.id + ..url = "custom:foo"), + ); request = await getImportRequest(process); }); @@ -381,20 +528,27 @@ void main() { }); test("null results count as not found", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); var canonicalizeRequest = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = canonicalizeRequest.id - ..url = "o:other")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = canonicalizeRequest.id + ..url = "o:other"), + ); var importRequest = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = - (InboundMessage_ImportResponse()..id = importRequest.id)); + process.send( + InboundMessage() + ..importResponse = + (InboundMessage_ImportResponse()..id = importRequest.id), + ); var failure = await getCompileFailure(process); expect(failure.message, equals("Can't find stylesheet to import.")); @@ -403,16 +557,21 @@ void main() { }); test("errors cause compilation to fail", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..error = "oh no")); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..error = "oh no"), + ); var failure = await getCompileFailure(process); expect(failure.message, equals('oh no')); @@ -422,112 +581,153 @@ void main() { }); test("can return an SCSS file", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a {b: 1px + 2px}"))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a {b: 1px + 2px}")), + ); await expectSuccess(process, "a { b: 3px; }"); await process.close(); }); test("can return an indented syntax file", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a\n b: 1px + 2px" - ..syntax = Syntax.INDENTED))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a\n b: 1px + 2px" + ..syntax = Syntax.INDENTED)), + ); await expectSuccess(process, "a { b: 3px; }"); await process.close(); }); test("can return a plain CSS file", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a {b: c}" - ..syntax = Syntax.CSS))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a {b: c}" + ..syntax = Syntax.CSS)), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); }); test("uses a data: URL rather than an empty source map URL", () async { - process.send(compileString("@use 'other'", sourceMap: true, importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + sourceMap: true, + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a {b: c}" - ..sourceMapUrl = ""))); - - await expectSuccess(process, "a { b: c; }", sourceMap: (String map) { - var mapping = source_maps.parse(map) as source_maps.SingleMapping; - expect(mapping.urls, [startsWith("data:")]); - }); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a {b: c}" + ..sourceMapUrl = "")), + ); + + await expectSuccess( + process, + "a { b: c; }", + sourceMap: (String map) { + var mapping = source_maps.parse(map) as source_maps.SingleMapping; + expect(mapping.urls, [startsWith("data:")]); + }, + ); await process.close(); }); test("uses a non-empty source map URL", () async { - process.send(compileString("@use 'other'", sourceMap: true, importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + sourceMap: true, + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a {b: c}" - ..sourceMapUrl = "file:///asdf"))); - - await expectSuccess(process, "a { b: c; }", sourceMap: (String map) { - var mapping = source_maps.parse(map) as source_maps.SingleMapping; - expect(mapping.urls, equals(["file:///asdf"])); - }); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a {b: c}" + ..sourceMapUrl = "file:///asdf")), + ); + + await expectSuccess( + process, + "a { b: c; }", + sourceMap: (String map) { + var mapping = source_maps.parse(map) as source_maps.SingleMapping; + expect(mapping.urls, equals(["file:///asdf"])); + }, + ); await process.close(); }); }); test("handles an importer for a string compile request", () async { - process.send(compileString("@use 'other'", - importer: InboundMessage_CompileRequest_Importer()..importerId = 1)); + process.send( + compileString( + "@use 'other'", + importer: InboundMessage_CompileRequest_Importer()..importerId = 1, + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "a {b: 1px + 2px}"))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "a {b: 1px + 2px}")), + ); await expectSuccess(process, "a { b: 3px; }"); await process.close(); @@ -537,9 +737,14 @@ void main() { test("are used to load imports", () async { await d.dir("dir", [d.file("other.scss", "a {b: c}")]).create(); - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..path = d.path("dir") - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..path = d.path("dir"), + ], + ), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); @@ -550,10 +755,15 @@ void main() { await d.dir("dir$i", [d.file("other$i.scss", "a {b: $i}")]).create(); } - process.send(compileString("@use 'other2'", importers: [ - for (var i = 0; i < 3; i++) - InboundMessage_CompileRequest_Importer()..path = d.path("dir$i") - ])); + process.send( + compileString( + "@use 'other2'", + importers: [ + for (var i = 0; i < 3; i++) + InboundMessage_CompileRequest_Importer()..path = d.path("dir$i"), + ], + ), + ); await expectSuccess(process, "a { b: 2; }"); await process.close(); @@ -562,10 +772,15 @@ void main() { test("take precedence over later importers", () async { await d.dir("dir", [d.file("other.scss", "a {b: c}")]).create(); - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..path = d.path("dir"), - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..path = d.path("dir"), + InboundMessage_CompileRequest_Importer()..importerId = 1, + ], + ), + ); await expectSuccess(process, "a { b: c; }"); await process.close(); @@ -574,18 +789,25 @@ void main() { test("yield precedence to earlier importers", () async { await d.dir("dir", [d.file("other.scss", "a {b: c}")]).create(); - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1, - InboundMessage_CompileRequest_Importer()..path = d.path("dir") - ])); + process.send( + compileString( + "@use 'other'", + importers: [ + InboundMessage_CompileRequest_Importer()..importerId = 1, + InboundMessage_CompileRequest_Importer()..path = d.path("dir"), + ], + ), + ); await _canonicalize(process); var request = await getImportRequest(process); - process.send(InboundMessage() - ..importResponse = (InboundMessage_ImportResponse() - ..id = request.id - ..success = (InboundMessage_ImportResponse_ImportSuccess() - ..contents = "x {y: z}"))); + process.send( + InboundMessage() + ..importResponse = (InboundMessage_ImportResponse() + ..id = request.id + ..success = (InboundMessage_ImportResponse_ImportSuccess() + ..contents = "x {y: z}")), + ); await expectSuccess(process, "x { y: z; }"); await process.close(); @@ -594,38 +816,65 @@ void main() { group("fails compilation for an invalid scheme:", () { test("empty", () async { - process.send(compileString("a {b: c}", importers: [ - InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: [""]) - ])); + process.send( + compileString( + "a {b: c}", + importers: [ + InboundMessage_CompileRequest_Importer( + importerId: 1, + nonCanonicalScheme: [""], + ), + ], + ), + ); var failure = await getCompileFailure(process); - expect(failure.message, - equals('"" isn\'t a valid URL scheme (for example "file").')); + expect( + failure.message, + equals('"" isn\'t a valid URL scheme (for example "file").'), + ); await process.close(); }); test("uppercase", () async { - process.send(compileString("a {b: c}", importers: [ - InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: ["U"]) - ])); + process.send( + compileString( + "a {b: c}", + importers: [ + InboundMessage_CompileRequest_Importer( + importerId: 1, + nonCanonicalScheme: ["U"], + ), + ], + ), + ); var failure = await getCompileFailure(process); - expect(failure.message, - equals('"U" isn\'t a valid URL scheme (for example "file").')); + expect( + failure.message, + equals('"U" isn\'t a valid URL scheme (for example "file").'), + ); await process.close(); }); test("colon", () async { - process.send(compileString("a {b: c}", importers: [ - InboundMessage_CompileRequest_Importer( - importerId: 1, nonCanonicalScheme: ["u:"]) - ])); + process.send( + compileString( + "a {b: c}", + importers: [ + InboundMessage_CompileRequest_Importer( + importerId: 1, + nonCanonicalScheme: ["u:"], + ), + ], + ), + ); var failure = await getCompileFailure(process); - expect(failure.message, - equals('"u:" isn\'t a valid URL scheme (for example "file").')); + expect( + failure.message, + equals('"u:" isn\'t a valid URL scheme (for example "file").'), + ); await process.close(); }); }); @@ -639,10 +888,12 @@ void main() { /// canonicalization itself. Future _canonicalize(EmbeddedProcess process) async { var request = await getCanonicalizeRequest(process); - process.send(InboundMessage() - ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() - ..id = request.id - ..url = "custom:other")); + process.send( + InboundMessage() + ..canonicalizeResponse = (InboundMessage_CanonicalizeResponse() + ..id = request.id + ..url = "custom:other"), + ); } /// Asserts that [process] emits a [CompileFailure] result with the given diff --git a/test/embedded/length_delimited_test.dart b/test/embedded/length_delimited_test.dart index 1d4c6fb62..a26d35954 100644 --- a/test/embedded/length_delimited_test.dart +++ b/test/embedded/length_delimited_test.dart @@ -40,8 +40,10 @@ void main() { test("encodes a message of length greater than 256", () { sink.add(List.filled(300, 1)); sink.close(); - expect(collectBytes(stream), - completion(equals([172, 2, ...List.filled(300, 1)]))); + expect( + collectBytes(stream), + completion(equals([172, 2, ...List.filled(300, 1)])), + ); }); test("encodes multiple messages", () { @@ -49,8 +51,10 @@ void main() { sink.add([20, 30]); sink.add([40, 50, 60]); sink.close(); - expect(collectBytes(stream), - completion(equals([1, 10, 2, 20, 30, 3, 40, 50, 60]))); + expect( + collectBytes(stream), + completion(equals([1, 10, 2, 20, 30, 3, 40, 50, 60])), + ); }); }); diff --git a/test/embedded/protocol_test.dart b/test/embedded/protocol_test.dart index c7473ac69..5a0a41a12 100644 --- a/test/embedded/protocol_test.dart +++ b/test/embedded/protocol_test.dart @@ -33,8 +33,10 @@ void main() { test("caused by an unterminated compilation ID varint", () async { process.stdin.add([1, 0x81]); await expectParseError( - process, "Invalid compilation ID: continuation bit always set.", - compilationId: errorId); + process, + "Invalid compilation ID: continuation bit always set.", + compilationId: errorId, + ); await process.shouldExit(76); }); @@ -42,43 +44,54 @@ void main() { var varint = serializeVarint(0x100000000); process.stdin.add([...serializeVarint(varint.length), ...varint]); await expectParseError( - process, "Varint compilation ID was longer than 32 bits.", - compilationId: errorId); + process, + "Varint compilation ID was longer than 32 bits.", + compilationId: errorId, + ); await process.shouldExit(76); }); test("caused by an invalid protobuf", () async { process.stdin.add([2, 1, 0]); await expectParseError( - process, "Protocol message contained an invalid tag (zero).", - compilationId: 1); + process, + "Protocol message contained an invalid tag (zero).", + compilationId: 1, + ); await process.shouldExit(76); }); test("caused by a response to an inactive compilation", () async { - process.send(InboundMessage() - ..canonicalizeResponse = - (InboundMessage_CanonicalizeResponse()..id = 1)); + process.send( + InboundMessage() + ..canonicalizeResponse = + (InboundMessage_CanonicalizeResponse()..id = 1), + ); await expectParamsError( - process, - errorId, - "Response ID 1 doesn't match any outstanding requests in " - "compilation $defaultCompilationId."); + process, + errorId, + "Response ID 1 doesn't match any outstanding requests in " + "compilation $defaultCompilationId.", + ); await process.shouldExit(76); }); test("caused by duplicate compilation IDs", () async { - process.send(compileString("@use 'other'", importers: [ - InboundMessage_CompileRequest_Importer()..importerId = 1 - ])); + process.send( + compileString( + "@use 'other'", + importers: [InboundMessage_CompileRequest_Importer()..importerId = 1], + ), + ); await getCanonicalizeRequest(process); process.send(compileString("a {b: c}")); await expectParamsError( - process, - errorId, - "A CompileRequest with compilation ID $defaultCompilationId is " - "already active."); + process, + errorId, + "A CompileRequest with compilation ID $defaultCompilationId is " + "already active.", + ); await process.shouldExit(76); }); }); @@ -87,7 +100,7 @@ void main() { process.inbound.add(( 0, InboundMessage() - ..versionRequest = (InboundMessage_VersionRequest()..id = 123) + ..versionRequest = (InboundMessage_VersionRequest()..id = 123), )); var (compilationId, OutboundMessage(versionResponse: response)) = await process.outbound.next; @@ -129,9 +142,11 @@ void main() { test("an absolute path", () async { await d.file("test.scss", "a {b: 1px + 2px}").create(); - process.send(InboundMessage() - ..compileRequest = (InboundMessage_CompileRequest() - ..path = p.absolute(d.path("test.scss")))); + process.send( + InboundMessage() + ..compileRequest = (InboundMessage_CompileRequest() + ..path = p.absolute(d.path("test.scss"))), + ); await expectSuccess(process, "a { b: 3px; }"); await process.close(); }); @@ -139,9 +154,11 @@ void main() { test("a relative path", () async { await d.file("test.scss", "a {b: 1px + 2px}").create(); - process.send(InboundMessage() - ..compileRequest = (InboundMessage_CompileRequest() - ..path = p.relative(d.path("test.scss")))); + process.send( + InboundMessage() + ..compileRequest = (InboundMessage_CompileRequest() + ..path = p.relative(d.path("test.scss"))), + ); await expectSuccess(process, "a { b: 3px; }"); await process.close(); }); @@ -149,15 +166,17 @@ void main() { group("compiles CSS in", () { test("expanded mode", () async { - process - .send(compileString("a {b: 1px + 2px}", style: OutputStyle.EXPANDED)); + process.send( + compileString("a {b: 1px + 2px}", style: OutputStyle.EXPANDED), + ); await expectSuccess(process, equals("a {\n b: 3px;\n}")); await process.close(); }); test("compressed mode", () async { process.send( - compileString("a {b: 1px + 2px}", style: OutputStyle.COMPRESSED)); + compileString("a {b: 1px + 2px}", style: OutputStyle.COMPRESSED), + ); await expectSuccess(process, equals("a{b:3px}")); await process.close(); }); @@ -187,15 +206,19 @@ void main() { test("handles many concurrent compilation requests", () async { var totalRequests = 1000; for (var i = 1; i <= totalRequests; i++) { - process.inbound - .add((i, compileString("a {b: foo() + 2px}", functions: [r"foo()"]))); + process.inbound.add(( + i, + compileString("a {b: foo() + 2px}", functions: [r"foo()"]), + )); } var successes = 0; process.outbound.rest.listen((pair) { var (compilationId, message) = pair; - expect(compilationId, - allOf(greaterThan(0), lessThanOrEqualTo(totalRequests))); + expect( + compilationId, + allOf(greaterThan(0), lessThanOrEqualTo(totalRequests)), + ); if (message.hasFunctionCallRequest()) { process.inbound.add(( @@ -206,7 +229,7 @@ void main() { ..success = (Value() ..number = (Value_Number() ..value = 1 - ..numerators.add("px")))) + ..numerators.add("px")))), )); } else if (message.hasCompileResponse()) { var response = message.compileResponse; @@ -241,8 +264,10 @@ void main() { // compilations as we can realistically have anyway. var totalRequests = 15; for (var i = 1; i <= totalRequests; i++) { - process.inbound - .add((i, compileString("a {b: foo() + 2px}", functions: [r"foo()"]))); + process.inbound.add(( + i, + compileString("a {b: foo() + 2px}", functions: [r"foo()"]), + )); } await process.close(); @@ -262,54 +287,78 @@ void main() { test("includes a source map if source_map is true", () async { process.send(compileString("a {b: 1px + 2px}", sourceMap: true)); - await expectSuccess(process, "a { b: 3px; }", sourceMap: (String map) { - var mapping = source_maps.parse(map); - var span = mapping.spanFor(2, 5)!; - expect(span.start.line, equals(0)); - expect(span.start.column, equals(3)); - expect(span.end, equals(span.start)); - expect(mapping, isA()); - expect((mapping as source_maps.SingleMapping).files[0], isNull); - return true; - }); + await expectSuccess( + process, + "a { b: 3px; }", + sourceMap: (String map) { + var mapping = source_maps.parse(map); + var span = mapping.spanFor(2, 5)!; + expect(span.start.line, equals(0)); + expect(span.start.column, equals(3)); + expect(span.end, equals(span.start)); + expect(mapping, isA()); + expect((mapping as source_maps.SingleMapping).files[0], isNull); + return true; + }, + ); await process.close(); }); test( - "includes a source map without content if source_map is true and source_map_include_sources is false", - () async { - process.send(compileString("a {b: 1px + 2px}", - sourceMap: true, sourceMapIncludeSources: false)); - await expectSuccess(process, "a { b: 3px; }", sourceMap: (String map) { - var mapping = source_maps.parse(map); - var span = mapping.spanFor(2, 5)!; - expect(span.start.line, equals(0)); - expect(span.start.column, equals(3)); - expect(span.end, equals(span.start)); - expect(mapping, isA()); - expect((mapping as source_maps.SingleMapping).files[0], isNull); - return true; - }); - await process.close(); - }); + "includes a source map without content if source_map is true and source_map_include_sources is false", + () async { + process.send( + compileString( + "a {b: 1px + 2px}", + sourceMap: true, + sourceMapIncludeSources: false, + ), + ); + await expectSuccess( + process, + "a { b: 3px; }", + sourceMap: (String map) { + var mapping = source_maps.parse(map); + var span = mapping.spanFor(2, 5)!; + expect(span.start.line, equals(0)); + expect(span.start.column, equals(3)); + expect(span.end, equals(span.start)); + expect(mapping, isA()); + expect((mapping as source_maps.SingleMapping).files[0], isNull); + return true; + }, + ); + await process.close(); + }, + ); test( - "includes a source map with content if source_map is true and source_map_include_sources is true", - () async { - process.send(compileString("a {b: 1px + 2px}", - sourceMap: true, sourceMapIncludeSources: true)); - await expectSuccess(process, "a { b: 3px; }", sourceMap: (String map) { - var mapping = source_maps.parse(map); - var span = mapping.spanFor(2, 5)!; - expect(span.start.line, equals(0)); - expect(span.start.column, equals(3)); - expect(span.end, equals(span.start)); - expect(mapping, isA()); - expect((mapping as source_maps.SingleMapping).files[0], isNotNull); - return true; - }); - await process.close(); - }); + "includes a source map with content if source_map is true and source_map_include_sources is true", + () async { + process.send( + compileString( + "a {b: 1px + 2px}", + sourceMap: true, + sourceMapIncludeSources: true, + ), + ); + await expectSuccess( + process, + "a { b: 3px; }", + sourceMap: (String map) { + var mapping = source_maps.parse(map); + var span = mapping.spanFor(2, 5)!; + expect(span.start.line, equals(0)); + expect(span.start.column, equals(3)); + expect(span.end, equals(span.start)); + expect(mapping, isA()); + expect((mapping as source_maps.SingleMapping).files[0], isNotNull); + return true; + }, + ); + await process.close(); + }, + ); group("emits a log event", () { group("for a @debug rule", () { @@ -332,7 +381,9 @@ void main() { process.send(compileString("a {@debug hello}", alertColor: true)); var logEvent = await getLogEvent(process); expect( - logEvent.formatted, equals('-:1 \u001b[1mDebug\u001b[0m: hello\n')); + logEvent.formatted, + equals('-:1 \u001b[1mDebug\u001b[0m: hello\n'), + ); await process.kill(); }); }); @@ -347,9 +398,12 @@ void main() { expect(logEvent.span, equals(SourceSpan())); expect(logEvent.stackTrace, equals("- 1:4 root stylesheet\n")); expect( - logEvent.formatted, - equals('WARNING: hello\n' - ' - 1:4 root stylesheet\n')); + logEvent.formatted, + equals( + 'WARNING: hello\n' + ' - 1:4 root stylesheet\n', + ), + ); await process.kill(); }); @@ -357,9 +411,12 @@ void main() { process.send(compileString("a {@warn hello}", alertColor: true)); var logEvent = await getLogEvent(process); expect( - logEvent.formatted, - equals('\x1B[33m\x1B[1mWarning\x1B[0m: hello\n' - ' - 1:4 root stylesheet\n')); + logEvent.formatted, + equals( + '\x1B[33m\x1B[1mWarning\x1B[0m: hello\n' + ' - 1:4 root stylesheet\n', + ), + ); await process.kill(); }); @@ -367,14 +424,17 @@ void main() { process.send(compileString("a {@debug a && b}", alertAscii: true)); var logEvent = await getLogEvent(process); expect( - logEvent.formatted, - equals('WARNING: In Sass, "&&" means two copies of the parent ' - 'selector. You probably want to use "and" instead.\n\n' - ' ,\n' - '1 | a {@debug a && b}\n' - ' | ^^\n' - ' \'\n' - ' - 1:13 root stylesheet\n')); + logEvent.formatted, + equals( + 'WARNING: In Sass, "&&" means two copies of the parent ' + 'selector. You probably want to use "and" instead.\n\n' + ' ,\n' + '1 | a {@debug a && b}\n' + ' | ^^\n' + ' \'\n' + ' - 1:13 root stylesheet\n', + ), + ); await process.kill(); }); }); @@ -385,12 +445,14 @@ void main() { var logEvent = await getLogEvent(process); expect(logEvent.type, equals(LogEventType.DEPRECATION_WARNING)); expect( - logEvent.message, - equals( - '@elseif is deprecated and will not be supported in future Sass ' - 'versions.\n' - '\n' - 'Recommendation: @else if')); + logEvent.message, + equals( + '@elseif is deprecated and will not be supported in future Sass ' + 'versions.\n' + '\n' + 'Recommendation: @else if', + ), + ); expect(logEvent.span.text, equals("@elseif")); expect(logEvent.span.start, equals(location(12, 0, 12))); expect(logEvent.span.end, equals(location(19, 0, 19))); @@ -405,11 +467,14 @@ void main() { var logEvent = await getLogEvent(process); expect(logEvent.type, equals(LogEventType.DEPRECATION_WARNING)); expect( - logEvent.message, - equals("As of Dart Sass 2.0.0, !global assignments won't be able to " - "declare new variables.\n" - "\n" - "Recommendation: add `\$var: null` at the stylesheet root.")); + logEvent.message, + equals( + "As of Dart Sass 2.0.0, !global assignments won't be able to " + "declare new variables.\n" + "\n" + "Recommendation: add `\$var: null` at the stylesheet root.", + ), + ); expect(logEvent.span.text, equals("\$var: value !global")); expect(logEvent.span.start, equals(location(3, 0, 3))); expect(logEvent.span.end, equals(location(22, 0, 22))); @@ -449,14 +514,18 @@ void main() { }); test("from a missing file", () async { - process.send(InboundMessage() - ..compileRequest = - (InboundMessage_CompileRequest()..path = d.path("test.scss"))); + process.send( + InboundMessage() + ..compileRequest = + (InboundMessage_CompileRequest()..path = d.path("test.scss")), + ); var failure = await getCompileFailure(process); expect(failure.message, startsWith("Cannot open file: ")); - expect(failure.message.replaceFirst("Cannot open file: ", "").trim(), - equalsPath(d.path('test.scss'))); + expect( + failure.message.replaceFirst("Cannot open file: ", "").trim(), + equalsPath(d.path('test.scss')), + ); expect(failure.span.text, equals('')); expect(failure.span.context, equals('')); expect(failure.span.start, equals(SourceSpan_SourceLocation())); @@ -467,39 +536,47 @@ void main() { }); test("with a multi-line source span", () async { - process.send(compileString(""" -a { - b: 1px + - 1em; -} -""")); + process.send( + compileString(""" + a { + b: 1px + + 1em; + } + """), + ); var failure = await getCompileFailure(process); - expect(failure.span.text, "1px +\n 1em"); - expect(failure.span.start, equals(location(9, 1, 5))); - expect(failure.span.end, equals(location(23, 2, 8))); + expect(failure.span.text, "1px +\n 1em"); + expect(failure.span.start, equals(location(29, 1, 15))); + expect(failure.span.end, equals(location(54, 2, 19))); expect(failure.span.url, isEmpty); - expect(failure.span.context, equals(" b: 1px +\n 1em;\n")); - expect(failure.stackTrace, equals("- 2:6 root stylesheet\n")); + expect(failure.span.context, + equals(" b: 1px +\n 1em;\n")); + expect(failure.stackTrace, equals("- 2:16 root stylesheet\n")); await process.close(); }); test("with multiple stack trace entries", () async { - process.send(compileString(""" -@function fail() { - @return 1px + 1em; -} + process.send( + compileString(""" + @function fail() { + @return 1px + 1em; + } -a { - b: fail(); -} -""")); + a { + b: fail(); + } + """), + ); var failure = await getCompileFailure(process); expect( - failure.stackTrace, - equals("- 2:11 fail()\n" - "- 6:6 root stylesheet\n")); + failure.stackTrace, + equals( + "- 2:21 fail()\n" + "- 6:16 root stylesheet\n", + ), + ); await process.close(); }); @@ -510,15 +587,19 @@ a { var failure = await getCompileFailure(process); expect(failure.span.url, equals("foo://bar/baz")); expect( - failure.stackTrace, equals("foo://bar/baz 1:7 root stylesheet\n")); + failure.stackTrace, + equals("foo://bar/baz 1:7 root stylesheet\n"), + ); await process.close(); }); test("a path input", () async { await d.file("test.scss", "a {b: 1px + 1em}").create(); var path = d.path("test.scss"); - process.send(InboundMessage() - ..compileRequest = (InboundMessage_CompileRequest()..path = path)); + process.send( + InboundMessage() + ..compileRequest = (InboundMessage_CompileRequest()..path = path), + ); var failure = await getCompileFailure(process); expect(p.fromUri(failure.span.url), equalsPath(path)); @@ -548,13 +629,16 @@ a { var failure = await getCompileFailure(process); expect( - failure.formatted, - equals('Error: 1px and 1em have incompatible units.\n' - ' ╷\n' - '1 │ a {b: 1px + 1em}\n' - ' │ ^^^^^^^^^\n' - ' ╵\n' - ' - 1:7 root stylesheet')); + failure.formatted, + equals( + 'Error: 1px and 1em have incompatible units.\n' + ' ╷\n' + '1 │ a {b: 1px + 1em}\n' + ' │ ^^^^^^^^^\n' + ' ╵\n' + ' - 1:7 root stylesheet', + ), + ); await process.close(); }); @@ -563,13 +647,16 @@ a { var failure = await getCompileFailure(process); expect( - failure.formatted, - equals('Error: 1px and 1em have incompatible units.\n' - '\x1B[34m ╷\x1B[0m\n' - '\x1B[34m1 │\x1B[0m a {b: \x1B[31m1px + 1em\x1B[0m}\n' - '\x1B[34m │\x1B[0m \x1B[31m ^^^^^^^^^\x1B[0m\n' - '\x1B[34m ╵\x1B[0m\n' - ' - 1:7 root stylesheet')); + failure.formatted, + equals( + 'Error: 1px and 1em have incompatible units.\n' + '\x1B[34m ╷\x1B[0m\n' + '\x1B[34m1 │\x1B[0m a {b: \x1B[31m1px + 1em\x1B[0m}\n' + '\x1B[34m │\x1B[0m \x1B[31m ^^^^^^^^^\x1B[0m\n' + '\x1B[34m ╵\x1B[0m\n' + ' - 1:7 root stylesheet', + ), + ); await process.close(); }); @@ -578,13 +665,16 @@ a { var failure = await getCompileFailure(process); expect( - failure.formatted, - equals('Error: 1px and 1em have incompatible units.\n' - ' ,\n' - '1 | a {b: 1px + 1em}\n' - ' | ^^^^^^^^^\n' - ' \'\n' - ' - 1:7 root stylesheet')); + failure.formatted, + equals( + 'Error: 1px and 1em have incompatible units.\n' + ' ,\n' + '1 | a {b: 1px + 1em}\n' + ' | ^^^^^^^^^\n' + ' \'\n' + ' - 1:7 root stylesheet', + ), + ); await process.close(); }); }); diff --git a/test/embedded/utils.dart b/test/embedded/utils.dart index 7741706d6..6de2611b6 100644 --- a/test/embedded/utils.dart +++ b/test/embedded/utils.dart @@ -17,21 +17,23 @@ const defaultCompilationId = 4321; /// Returns a (compilation ID, [InboundMessage]) pair that compiles the given /// plain CSS string. -InboundMessage compileString(String css, - {int? id, - bool? alertColor, - bool? alertAscii, - Syntax? syntax, - OutputStyle? style, - String? url, - bool? sourceMap, - bool? sourceMapIncludeSources, - Iterable? importers, - InboundMessage_CompileRequest_Importer? importer, - Iterable? functions, - Iterable? fatalDeprecations, - Iterable? futureDeprecations, - Iterable? silenceDeprecations}) { +InboundMessage compileString( + String css, { + int? id, + bool? alertColor, + bool? alertAscii, + Syntax? syntax, + OutputStyle? style, + String? url, + bool? sourceMap, + bool? sourceMapIncludeSources, + Iterable? importers, + InboundMessage_CompileRequest_Importer? importer, + Iterable? functions, + Iterable? fatalDeprecations, + Iterable? futureDeprecations, + Iterable? silenceDeprecations, +}) { var input = InboundMessage_CompileRequest_StringInput()..source = css; if (syntax != null) input.syntax = syntax; if (url != null) input.url = url; @@ -55,25 +57,35 @@ InboundMessage compileString(String css, /// Asserts that [process] emits a [ProtocolError] parse error with the given /// [message] on its protobuf stream and prints a notice on stderr. -Future expectParseError(EmbeddedProcess process, Object message, - {int compilationId = defaultCompilationId}) async { +Future expectParseError( + EmbeddedProcess process, + Object message, { + int compilationId = defaultCompilationId, +}) async { var (actualCompilationId, actualMessage) = await process.outbound.next; expect(actualCompilationId, equals(compilationId)); - expect(actualMessage, - isProtocolError(errorId, ProtocolErrorType.PARSE, message)); + expect( + actualMessage, + isProtocolError(errorId, ProtocolErrorType.PARSE, message), + ); var stderrPrefix = "Host caused parse error: "; await expectLater( - process.stderr, - message is String - ? emitsInOrder("$stderrPrefix$message".split("\n")) - : emits(startsWith(stderrPrefix))); + process.stderr, + message is String + ? emitsInOrder("$stderrPrefix$message".split("\n")) + : emits(startsWith(stderrPrefix)), + ); } /// Asserts that [process] emits a [ProtocolError] params error with the given /// [message] on its protobuf stream and prints a notice on stderr. -Future expectParamsError(EmbeddedProcess process, int id, Object message, - {int compilationId = defaultCompilationId}) async { +Future expectParamsError( + EmbeddedProcess process, + int id, + Object message, { + int compilationId = defaultCompilationId, +}) async { var (actualCompilationId, actualMessage) = await process.outbound.next; expect(actualCompilationId, equals(compilationId)); expect(actualMessage, isProtocolError(id, ProtocolErrorType.PARAMS, message)); @@ -81,10 +93,11 @@ Future expectParamsError(EmbeddedProcess process, int id, Object message, var stderrPrefix = "Host caused params error" "${id == errorId ? '' : " with request $id"}: "; await expectLater( - process.stderr, - message is String - ? emitsInOrder("$stderrPrefix$message".split("\n")) - : emits(startsWith(stderrPrefix))); + process.stderr, + message is String + ? emitsInOrder("$stderrPrefix$message".split("\n")) + : emits(startsWith(stderrPrefix)), + ); } /// Asserts that an [OutboundMessage] is a [ProtocolError] with the given [id], @@ -93,8 +106,11 @@ Matcher isProtocolError(int id, ProtocolErrorType type, [Object? message]) => predicate((value) { expect(value, isA()); var outboundMessage = value as OutboundMessage; - expect(outboundMessage.hasError(), isTrue, - reason: "Expected $outboundMessage to be a ProtocolError"); + expect( + outboundMessage.hasError(), + isTrue, + reason: "Expected $outboundMessage to be a ProtocolError", + ); expect(outboundMessage.error.id, equals(id)); expect(outboundMessage.error.type, equals(type)); if (message != null) expect(outboundMessage.error.message, message); @@ -104,70 +120,98 @@ Matcher isProtocolError(int id, ProtocolErrorType type, [Object? message]) => /// Asserts [process] emits a `CanonicalizeRequest` with the default compilation /// ID and returns it. Future getCanonicalizeRequest( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var message = await process.receive(); - expect(message.hasCanonicalizeRequest(), isTrue, - reason: "Expected $message to have a CanonicalizeRequest"); + expect( + message.hasCanonicalizeRequest(), + isTrue, + reason: "Expected $message to have a CanonicalizeRequest", + ); return message.canonicalizeRequest; } /// Asserts [process] emits an `ImportRequest` with the default compilation ID /// and returns it. Future getImportRequest( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var message = await process.receive(); - expect(message.hasImportRequest(), isTrue, - reason: "Expected $message to have a ImportRequest"); + expect( + message.hasImportRequest(), + isTrue, + reason: "Expected $message to have a ImportRequest", + ); return message.importRequest; } /// Asserts that [process] emits a `FileImportRequest` with the default /// compilation ID and returns it. Future getFileImportRequest( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var message = await process.receive(); - expect(message.hasFileImportRequest(), isTrue, - reason: "Expected $message to have a FileImportRequest"); + expect( + message.hasFileImportRequest(), + isTrue, + reason: "Expected $message to have a FileImportRequest", + ); return message.fileImportRequest; } /// Asserts that [process] emits a `FunctionCallRequest` with the default /// compilation ID and returns it. Future getFunctionCallRequest( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var message = await process.receive(); - expect(message.hasFunctionCallRequest(), isTrue, - reason: "Expected $message to have a FunctionCallRequest"); + expect( + message.hasFunctionCallRequest(), + isTrue, + reason: "Expected $message to have a FunctionCallRequest", + ); return message.functionCallRequest; } /// Asserts that [process] emits a with the default compilation ID /// `CompileResponse.Failure` and returns it. Future getCompileFailure( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var response = await getCompileResponse(process); - expect(response.hasFailure(), isTrue, - reason: "Expected $response to be a failure"); + expect( + response.hasFailure(), + isTrue, + reason: "Expected $response to be a failure", + ); return response.failure; } /// Asserts that [process] emits a with the default compilation ID /// `CompileResponse.Success` and returns it. Future getCompileSuccess( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var response = await getCompileResponse(process); - expect(response.hasSuccess(), isTrue, - reason: "Expected $response to be a success"); + expect( + response.hasSuccess(), + isTrue, + reason: "Expected $response to be a success", + ); return response.success; } /// Asserts that [process] emits a `CompileResponse` and with the default /// compilation ID returns it. Future getCompileResponse( - EmbeddedProcess process) async { + EmbeddedProcess process, +) async { var message = await process.receive(); - expect(message.hasCompileResponse(), isTrue, - reason: "Expected $message to have a CompileResponse"); + expect( + message.hasCompileResponse(), + isTrue, + reason: "Expected $message to have a CompileResponse", + ); return message.compileResponse; } @@ -175,8 +219,11 @@ Future getCompileResponse( /// compilation ID it. Future getLogEvent(EmbeddedProcess process) async { var message = await process.receive(); - expect(message.hasLogEvent(), isTrue, - reason: "Expected $message to have a LogEvent"); + expect( + message.hasLogEvent(), + isTrue, + reason: "Expected $message to have a LogEvent", + ); return message.logEvent; } @@ -188,8 +235,11 @@ Future getLogEvent(EmbeddedProcess process) async { /// /// If [sourceMap] is a function, `response.success.sourceMap` is passed to it. /// Otherwise, it's treated as a matcher for `response.success.sourceMap`. -Future expectSuccess(EmbeddedProcess process, Object css, - {Object? sourceMap}) async { +Future expectSuccess( + EmbeddedProcess process, + Object css, { + Object? sourceMap, +}) async { var success = await getCompileSuccess(process); expect(success.css, css is String ? equalsIgnoringWhitespace(css) : css); if (sourceMap is void Function(String)) { @@ -210,4 +260,6 @@ SourceSpan_SourceLocation location(int offset, int line, int column) => /// Returns a matcher that verifies whether the given value refers to the same /// path as [expected]. Matcher equalsPath(String expected) => predicate( - (actual) => p.equals(actual, expected), "equals $expected"); + (actual) => p.equals(actual, expected), + "equals $expected", + ); diff --git a/test/hybrid.dart b/test/hybrid.dart index d4ec47941..47d53a689 100644 --- a/test/hybrid.dart +++ b/test/hybrid.dart @@ -8,11 +8,14 @@ import 'package:test/test.dart'; /// Creates a directory in the system temp directory and returns its path. Future createTempDir() async => (await runHybridExpression( - '(await Directory.systemTemp.createTemp("dart_sass_")).path')) as String; + '(await Directory.systemTemp.createTemp("dart_sass_")).path', + )) as String; /// Writes [text] to [path]. Future writeTextFile(String path, String text) => runHybridExpression( - 'File(message[0]).writeAsString(message[1])', [path, text]); + 'File(message[0]).writeAsString(message[1])', + [path, text], + ); /// Creates a directory at [path]. Future createDirectory(String path) => @@ -25,8 +28,10 @@ Future deleteDirectory(String path) => /// Runs [expression], which may be asynchronous, in a hybrid isolate. /// /// Returns the result of [expression] if it's JSON-serializable. -Future runHybridExpression(String expression, - [Object? message]) async { +Future runHybridExpression( + String expression, [ + Object? message, +]) async { var channel = spawnHybridCode(''' import 'dart:async'; import 'dart:convert'; diff --git a/test/output_test.dart b/test/output_test.dart index 4a5f40cff..30d4eba0f 100644 --- a/test/output_test.dart +++ b/test/output_test.dart @@ -17,8 +17,10 @@ void main() { group("emits private-use area characters as escapes in expanded mode", () { testCharacter(String escape) { test(escape, () { - expect(compileString("a {b: $escape}"), - equalsIgnoringWhitespace("a { b: $escape; }")); + expect( + compileString("a {b: $escape}"), + equalsIgnoringWhitespace("a { b: $escape; }"), + ); }); } @@ -49,13 +51,17 @@ void main() { group("adds a space", () { test("if followed by a hex character", () { - expect(compileString(r"a {b: '\e000 a'}"), - equalsIgnoringWhitespace(r'a { b: "\e000 a"; }')); + expect( + compileString(r"a {b: '\e000 a'}"), + equalsIgnoringWhitespace(r'a { b: "\e000 a"; }'), + ); }); test("if followed by a space", () { - expect(compileString(r"a {b: '\e000 '}"), - equalsIgnoringWhitespace(r'a { b: "\e000 "; }')); + expect( + compileString(r"a {b: '\e000 '}"), + equalsIgnoringWhitespace(r'a { b: "\e000 "; }'), + ); }); }); }); @@ -68,8 +74,10 @@ void main() { }); test("in Sass", () { - expect(compileString("/*\r\n foo\r\n bar", syntax: Syntax.sass), - equals("/* foo\n * bar */")); + expect( + compileString("/*\r\n foo\r\n bar", syntax: Syntax.sass), + equals("/* foo\n * bar */"), + ); }); }); @@ -78,8 +86,10 @@ void main() { group("removes exponential notation", () { group("for integers", () { test(">= 1e21", () { - expect(compileString("a {b: 1e21}"), - equalsIgnoringWhitespace("a { b: 1${'0' * 21}; }")); + expect( + compileString("a {b: 1e21}"), + equalsIgnoringWhitespace("a { b: 1${'0' * 21}; }"), + ); }); // At time of writing, numbers that are 20 digits or fewer are not printed @@ -87,20 +97,26 @@ void main() { // determine when to get rid of the exponent. This test ensures that if that // ever changes, we know about it. test("< 1e21", () { - expect(compileString("a {b: 1e20}"), - equalsIgnoringWhitespace("a { b: 1${'0' * 20}; }")); + expect( + compileString("a {b: 1e20}"), + equalsIgnoringWhitespace("a { b: 1${'0' * 20}; }"), + ); }); }); group("for floating-point numbers", () { test("Infinity", () { - expect(compileString("a {b: 1e999}"), - equalsIgnoringWhitespace("a { b: calc(infinity); }")); + expect( + compileString("a {b: 1e999}"), + equalsIgnoringWhitespace("a { b: calc(infinity); }"), + ); }); test(">= 1e21", () { - expect(compileString("a {b: 1.01e21}"), - equalsIgnoringWhitespace("a { b: 101${'0' * 19}; }")); + expect( + compileString("a {b: 1.01e21}"), + equalsIgnoringWhitespace("a { b: 101${'0' * 19}; }"), + ); }); // At time of writing, numbers that are 20 digits or fewer are not printed @@ -108,8 +124,10 @@ void main() { // determine when to get rid of the exponent. This test ensures that if that // ever changes, we know about it. test("< 1e21", () { - expect(compileString("a {b: 1.01e20}"), - equalsIgnoringWhitespace("a { b: 101${'0' * 18}; }")); + expect( + compileString("a {b: 1.01e20}"), + equalsIgnoringWhitespace("a { b: 101${'0' * 18}; }"), + ); }); }); }); @@ -121,40 +139,55 @@ void main() { group("uses a nice format to inspect numbers with complex units", () { group("finite", () { test("top-level", () { - expect(compileString(""" - @use 'sass:meta'; - a {b: meta.inspect(1px * 1em)}; - """), equalsIgnoringWhitespace('a { b: calc(1px * 1em); }')); + expect( + compileString(""" + @use 'sass:meta'; + a {b: meta.inspect(1px * 1em)}; + """), + equalsIgnoringWhitespace('a { b: calc(1px * 1em); }'), + ); }); test("in calc", () { - expect(compileString(""" - @use 'sass:meta'; - a {b: meta.inspect(calc(1px * 1em))}; - """), equalsIgnoringWhitespace('a { b: calc(1px * 1em); }')); + expect( + compileString(""" + @use 'sass:meta'; + a {b: meta.inspect(calc(1px * 1em))}; + """), + equalsIgnoringWhitespace('a { b: calc(1px * 1em); }'), + ); }); test("nested in calc", () { - expect(compileString(""" - @use 'sass:meta'; - a {b: meta.inspect(calc(c / (1px * 1em)))}; - """), equalsIgnoringWhitespace('a { b: calc(c / (1px * 1em)); }')); + expect( + compileString(""" + @use 'sass:meta'; + a {b: meta.inspect(calc(c / (1px * 1em)))}; + """), + equalsIgnoringWhitespace('a { b: calc(c / (1px * 1em)); }'), + ); }); test("numerator and denominator", () { - expect(compileString(""" - @use 'sass:math'; - @use 'sass:meta'; - a {b: meta.inspect(1px * math.div(math.div(1em, 1s), 1x))}; - """), equalsIgnoringWhitespace('a { b: calc(1px * 1em / 1s / 1x); }')); + expect( + compileString(""" + @use 'sass:math'; + @use 'sass:meta'; + a {b: meta.inspect(1px * math.div(math.div(1em, 1s), 1x))}; + """), + equalsIgnoringWhitespace('a { b: calc(1px * 1em / 1s / 1x); }'), + ); }); test("denominator only", () { - expect(compileString(""" - @use 'sass:math'; - @use 'sass:meta'; - a {b: meta.inspect(math.div(math.div(1, 1s), 1x))}; - """), equalsIgnoringWhitespace('a { b: calc(1 / 1s / 1x); }')); + expect( + compileString(""" + @use 'sass:math'; + @use 'sass:meta'; + a {b: meta.inspect(math.div(math.div(1, 1s), 1x))}; + """), + equalsIgnoringWhitespace('a { b: calc(1 / 1s / 1x); }'), + ); }); }); }); @@ -165,102 +198,136 @@ void main() { // trailing loud comments in the Sass syntax. group("preserve trailing loud comments in SCSS", () { test("after open block", () { - expect(compileString(""" -selector { /* please don't move me */ - name: value; -}"""), equals(""" + expect( + compileString(""" + selector { /* please don't move me */ + name: value; + } + """), + equals(""" selector { /* please don't move me */ name: value; -}""")); +}"""), + ); }); test("after open block (multi-line selector)", () { - expect(compileString(""" -selector1, -selector2 { /* please don't move me */ - name: value; -}"""), equals(""" + expect( + compileString(""" + selector1, + selector2 { /* please don't move me */ + name: value; + } + """), + equals(""" selector1, selector2 { /* please don't move me */ name: value; -}""")); +}"""), + ); }); test("after close block", () { - expect(compileString(""" -selector { - name: value; -} /* please don't move me */"""), equals(""" + expect( + compileString(""" + selector { + name: value; + } /* please don't move me */ + """), + equals(""" selector { name: value; -} /* please don't move me */""")); +} /* please don't move me */"""), + ); }); test("only content in block", () { - expect(compileString(""" -selector { - /* please don't move me */ -}"""), equals(""" + expect( + compileString(""" + selector { + /* please don't move me */ + } + """), + equals(""" selector { /* please don't move me */ -}""")); +}"""), + ); }); test("only content in block (no newlines)", () { - expect(compileString(""" -selector { /* please don't move me */ }"""), equals(""" -selector { /* please don't move me */ }""")); + expect( + compileString("selector { /* please don't move me */ }"), + equals("selector { /* please don't move me */ }"), + ); }); test("double trailing empty block", () { - expect(compileString(""" -selector { /* please don't move me */ /* please don't move me */ }"""), - equals(""" + expect( + compileString( + "selector { /* please don't move me */ /* please don't move me */ " + "}"), + equals(""" selector { /* please don't move me */ /* please don't move me */ -}""")); +}"""), + ); }); test("double trailing style rule", () { - expect(compileString(""" -selector { - margin: 1px; /* please don't move me */ /* please don't move me */ -}"""), equals(""" + expect( + compileString(""" + selector { + margin: 1px; /* please don't move me */ /* please don't move me */ + } + """), + equals(""" selector { margin: 1px; /* please don't move me */ /* please don't move me */ -}""")); +}"""), + ); }); test("after property in block", () { - expect(compileString(""" -selector { - name1: value1; /* please don't move me 1 */ - name2: value2; /* please don't move me 2 */ - name3: value3; /* please don't move me 3 */ -}"""), equals(""" + expect( + compileString(""" + selector { + name1: value1; /* please don't move me 1 */ + name2: value2; /* please don't move me 2 */ + name3: value3; /* please don't move me 3 */ + } + """), + equals(""" selector { name1: value1; /* please don't move me 1 */ name2: value2; /* please don't move me 2 */ name3: value3; /* please don't move me 3 */ -}""")); +}"""), + ); }); test("after rule in block", () { - expect(compileString(""" -selector { - @rule1; /* please don't move me 1 */ - @rule2; /* please don't move me 2 */ - @rule3; /* please don't move me 3 */ -}"""), equals(""" + expect( + compileString(""" + selector { + @rule1; /* please don't move me 1 */ + @rule2; /* please don't move me 2 */ + @rule3; /* please don't move me 3 */ + } + """), + equals(""" selector { @rule1; /* please don't move me 1 */ @rule2; /* please don't move me 2 */ @rule3; /* please don't move me 3 */ -}""")); +}"""), + ); }); test("after top-level statement", () { - expect(compileString("@rule; /* please don't move me */"), - equals("@rule; /* please don't move me */")); + expect( + compileString("@rule; /* please don't move me */"), + equals("@rule; /* please don't move me */"), + ); }); // The trailing comment detection logic looks for left braces to detect @@ -268,56 +335,74 @@ selector { // checks to make sure it isn't confused by syntax that uses braces for // things other than starting child blocks. test("selector contains left brace", () { - expect(compileString("""@rule1; -@rule2; -selector[href*="{"] -{ /* please don't move me */ } - -@rule3;"""), equals("""@rule1; + expect( + compileString(""" + @rule1; + @rule2; + selector[href*="{"] + { /* please don't move me */ } + + @rule3; + """), + equals(""" +@rule1; @rule2; selector[href*="{"] { /* please don't move me */ } -@rule3;""")); +@rule3;"""), + ); }); group("loud comments in mixin", () { test("empty with spacing", () { - expect(compileString(""" -@mixin loudComment { - /* ... */ -} - -selector { - @include loudComment; -}"""), """ + expect( + compileString(""" + @mixin loudComment { + /* ... */ + } + + selector { + @include loudComment; + } + """), + equals(""" selector { /* ... */ -}"""); +}"""), + ); }); test("empty no spacing", () { - expect(compileString(""" -@mixin loudComment{/* ... */} -selector {@include loudComment;}"""), """ + expect( + compileString(""" + @mixin loudComment{/* ... */} + selector {@include loudComment;} + """), + equals(""" selector { /* ... */ -}"""); +}"""), + ); }); test("with style rule", () { - expect(compileString(""" -@mixin loudComment { - margin: 1px; /* mixin */ -} /* mixin-out */ - -selector { - @include loudComment; /* selector */ -}"""), """ + expect( + compileString(""" + @mixin loudComment { + margin: 1px; /* mixin */ + } /* mixin-out */ + + selector { + @include loudComment; /* selector */ + } + """), + """ /* mixin-out */ selector { margin: 1px; /* mixin */ /* selector */ -}"""); +}""", + ); }); }); @@ -325,24 +410,24 @@ selector { test("with styles", () { expect( compileString(""" -foo { /* foo */ - padding: 1px; /* foo padding */ - bar { /* bar */ - padding: 2px; /* bar padding */ - baz { /* baz */ - padding: 3px; /* baz padding */ - margin: 3px; /* baz margin */ - } /* baz end */ - biz { /* biz */ - padding: 3px; /* biz padding */ - margin: 3px; /* biz margin */ - } /* biz end */ - margin: 2px; /* bar margin */ - } /* bar end */ - margin: 1px; /* foo margin */ -} /* foo end */ -"""), - """ + foo { /* foo */ + padding: 1px; /* foo padding */ + bar { /* bar */ + padding: 2px; /* bar padding */ + baz { /* baz */ + padding: 3px; /* baz padding */ + margin: 3px; /* baz margin */ + } /* baz end */ + biz { /* biz */ + padding: 3px; /* biz padding */ + margin: 3px; /* biz margin */ + } /* biz end */ + margin: 2px; /* bar margin */ + } /* bar end */ + margin: 1px; /* foo margin */ + } /* foo end */ + """), + equals(""" foo { /* foo */ padding: 1px; /* foo padding */ /* bar end */ @@ -363,23 +448,23 @@ foo bar biz { /* biz */ margin: 3px; /* biz margin */ } -/* foo end */""", +/* foo end */"""), ); }); test("empty", () { expect( compileString(""" -foo { /* foo */ - bar { /* bar */ - baz { /* baz */ - } /* baz end */ - biz { /* biz */ - } /* biz end */ - } /* bar end */ -} /* foo end */ -"""), - """ + foo { /* foo */ + bar { /* bar */ + baz { /* baz */ + } /* baz end */ + biz { /* biz */ + } /* biz end */ + } /* bar end */ + } /* foo end */ + """), + equals(""" foo { /* foo */ /* bar end */ } @@ -390,7 +475,7 @@ foo bar { /* bar */ foo bar baz { /* baz */ } foo bar biz { /* biz */ } -/* foo end */""", +/* foo end */"""), ); }); }); diff --git a/test/repo_test.dart b/test/repo_test.dart index 637899e7f..df6eb777c 100644 --- a/test/repo_test.dart +++ b/test/repo_test.dart @@ -15,7 +15,7 @@ void main() { group("YAML files are valid:", () { for (var entry in [ ...Directory.current.listSync(), - ...Directory("pkg").listSync(recursive: true) + ...Directory("pkg").listSync(recursive: true), ]) { if (entry is File && (entry.path.endsWith(".yaml") || entry.path.endsWith(".yml"))) { diff --git a/test/source_map_test.dart b/test/source_map_test.dart index df4d0e4af..25e49c539 100644 --- a/test/source_map_test.dart +++ b/test/source_map_test.dart @@ -20,623 +20,743 @@ void main() { group("maps source to target for", () { group("a style rule", () { test("that's basic", () { - _expectSourceMap(""" - {{1}}foo - {{2}}bar: baz - """, """ - {{1}}foo { - {{2}}bar: baz; - } - """, """ - {{1}}foo { - {{2}}bar: baz; - } - """); + _expectSourceMap( + """ + {{1}}foo + {{2}}bar: baz + """, + """ + {{1}}foo { + {{2}}bar: baz; + } + """, + """ + {{1}}foo { + {{2}}bar: baz; + } + """, + ); }); test("with a multiline selector", () { - _expectSourceMap(""" - {{1}}foo, - bar - {{2}}bar: baz - """, """ - {{1}}foo, - bar { - {{2}}bar: baz; - } - """, """ - {{1}}foo, - {{1}}bar { - {{2}}bar: baz; - } - """); + _expectSourceMap( + """ + {{1}}foo, + bar + {{2}}bar: baz + """, + """ + {{1}}foo, + bar { + {{2}}bar: baz; + } + """, + """ + {{1}}foo, + {{1}}bar { + {{2}}bar: baz; + } + """, + ); }); test("with a property on a different line", () { - _expectScssSourceMap(""" - {{1}}foo { - {{2}}bar: - {{3}}baz; - } - """, """ - {{1}}foo { - {{2}}bar: {{3}}baz; - } - """); + _expectScssSourceMap( + """ + {{1}}foo { + {{2}}bar: + {{3}}baz; + } + """, + """ + {{1}}foo { + {{2}}bar: {{3}}baz; + } + """, + ); }); test("with a multiline property", () { - _expectScssSourceMap(""" - {{1}}foo { - {{2}}bar: baz - bang; - } - """, """ - {{1}}foo { - {{2}}bar: baz bang; - } - """); + _expectScssSourceMap( + """ + {{1}}foo { + {{2}}bar: baz + bang; + } + """, + """ + {{1}}foo { + {{2}}bar: baz bang; + } + """, + ); }); test("that's nested", () { - _expectSourceMap(""" - foo - {{1}}bar - {{2}}baz: bang - """, """ - foo { - {{1}}bar { + _expectSourceMap( + """ + foo + {{1}}bar + {{2}}baz: bang + """, + """ + foo { + {{1}}bar { + {{2}}baz: bang; + } + } + """, + """ + {{1}}foo bar { {{2}}baz: bang; } - } - """, """ - {{1}}foo bar { - {{2}}baz: bang; - } - """); + """, + ); }); test("with a nested rule and declaration", () { - _expectSourceMap(""" - {{1}}foo - {{2}}a: b + _expectSourceMap( + """ + {{1}}foo + {{2}}a: b - {{3}}bar - {{4}}x: y - """, """ - {{1}}foo { - {{2}}a: b; + {{3}}bar + {{4}}x: y + """, + """ + {{1}}foo { + {{2}}a: b; - {{3}}bar { + {{3}}bar { + {{4}}x: y; + } + } + """, + """ + {{1}}foo { + {{2}}a: b; + } + {{3}}foo bar { {{4}}x: y; } - } - """, """ - {{1}}foo { - {{2}}a: b; - } - {{3}}foo bar { - {{4}}x: y; - } - """); + """, + ); }); test("with a nested declaration", () { - _expectSourceMap(""" - {{1}}foo - {{2}}a: b - {{3}}c: d - """, """ - {{1}}foo { - {{2}}a: b { - {{3}}c: d; + _expectSourceMap( + """ + {{1}}foo + {{2}}a: b + {{3}}c: d + """, + """ + {{1}}foo { + {{2}}a: b { + {{3}}c: d; + } } - } - """, """ - {{1}}foo { - {{2}}a: b; - {{3}}a-c: d; - } - """); + """, + """ + {{1}}foo { + {{2}}a: b; + {{3}}a-c: d; + } + """, + ); }); }); group("an unknown at-rule", () { test("without children", () { - _expectSourceMap(""" - {{1}}@foo (fblthp) - """, """ - {{1}}@foo (fblthp); - """, """ - {{1}}@foo (fblthp); - """); + _expectSourceMap( + "{{1}}@foo (fblthp)", + "{{1}}@foo (fblthp);", + "{{1}}@foo (fblthp);", + ); }); group("that contains", () { test("declarations", () { - _expectSourceMap(""" - {{1}}@foo (fblthp) - {{2}}bar: baz - """, """ - {{1}}@foo (fblthp) { - {{2}}bar: baz; - } - """, """ - {{1}}@foo (fblthp) { - {{2}}bar: baz; - } - """); + _expectSourceMap( + """ + {{1}}@foo (fblthp) + {{2}}bar: baz + """, + """ + {{1}}@foo (fblthp) { + {{2}}bar: baz; + } + """, + """ + {{1}}@foo (fblthp) { + {{2}}bar: baz; + } + """, + ); }); test("style rules", () { - _expectSourceMap(""" - {{1}}@foo (fblthp) - {{2}}bar - {{3}}baz: bang - """, """ - {{1}}@foo (fblthp) { - {{2}}bar { - {{3}}baz: bang; + _expectSourceMap( + """ + {{1}}@foo (fblthp) + {{2}}bar + {{3}}baz: bang + """, + """ + {{1}}@foo (fblthp) { + {{2}}bar { + {{3}}baz: bang; + } } - } - """, """ - {{1}}@foo (fblthp) { - {{2}}bar { - {{3}}baz: bang; + """, + """ + {{1}}@foo (fblthp) { + {{2}}bar { + {{3}}baz: bang; + } } - } - """); + """, + ); }); test("at-rules", () { - _expectSourceMap(""" - {{1}}@foo (fblthp) - {{2}}@bar baz - """, """ - {{1}}@foo (fblthp) { - {{2}}@bar baz; - } - """, """ - {{1}}@foo (fblthp) { - {{2}}@bar baz; - } - """); + _expectSourceMap( + """ + {{1}}@foo (fblthp) + {{2}}@bar baz + """, + """ + {{1}}@foo (fblthp) { + {{2}}@bar baz; + } + """, + """ + {{1}}@foo (fblthp) { + {{2}}@bar baz; + } + """, + ); }); }); }); group("a comment", () { test("that covers a single line", () { - _expectSourceMap(""" - {{1}}/* foo bar - {{2}}/* baz bang - """, """ - {{1}}/* foo bar */ - {{2}}/* baz bang */ - """, """ - {{1}}/* foo bar */ - {{2}}/* baz bang */ - """); + _expectSourceMap( + """ + {{1}}/* foo bar + {{2}}/* baz bang + """, + """ + {{1}}/* foo bar */ + {{2}}/* baz bang */ + """, + """ + {{1}}/* foo bar */ + {{2}}/* baz bang */ + """, + ); }); test("that covers multiple lines", () { - _expectSourceMap(""" - {{1}}/* foo bar - baz bang - """, """ - {{1}}/* foo bar - * baz bang */ - """, """ - {{1}}/* foo bar - {{1}} * baz bang */ - """); + _expectSourceMap( + """ + {{1}}/* foo bar + baz bang + """, + """ + {{1}}/* foo bar + * baz bang */ + """, + """ + {{1}}/* foo bar + {{1}} * baz bang */ + """, + ); }); }); group("@import", () { test("with a single URL", () { - _expectSourceMap(""" - @import {{1}}url(foo) - """, """ - @import {{1}}url(foo); - """, """ - {{1}}@import url(foo); - """); + _expectSourceMap( + """ + @import {{1}}url(foo) + """, + """ + @import {{1}}url(foo); + """, + """ + {{1}}@import url(foo); + """, + ); }); test("with multiple URLs", () { - _expectSourceMap(""" - @import {{1}}url(foo), {{2}}"bar.css" - """, """ - @import {{1}}url(foo), - {{2}}"bar.css"; - """, """ - {{1}}@import url(foo); - {{2}}@import "bar.css"; - """); + _expectSourceMap( + """ + @import {{1}}url(foo), {{2}}"bar.css" + """, + """ + @import {{1}}url(foo), + {{2}}"bar.css"; + """, + """ + {{1}}@import url(foo); + {{2}}@import "bar.css"; + """, + ); }); }); test("@keyframes", () { - _expectSourceMap(""" - {{1}}@keyframes name - {{2}}from - {{3}}top: 0px - - {{4}}10% - {{5}}top: 10px - """, """ - {{1}}@keyframes name { - {{2}}from { - {{3}}top: 0px; - } + _expectSourceMap( + """ + {{1}}@keyframes name + {{2}}from + {{3}}top: 0px + + {{4}}10% + {{5}}top: 10px + """, + """ + {{1}}@keyframes name { + {{2}}from { + {{3}}top: 0px; + } - {{4}}10% { - {{5}}top: 10px; - } - } - """, """ - {{1}}@keyframes name { - {{2}}from { - {{3}}top: 0px; + {{4}}10% { + {{5}}top: 10px; + } } - {{4}}10% { - {{5}}top: 10px; + """, + """ + {{1}}@keyframes name { + {{2}}from { + {{3}}top: 0px; + } + {{4}}10% { + {{5}}top: 10px; + } } - } - """); + """, + ); }); group("@media", () { test("at the root", () { - _expectSourceMap(""" - {{1}}@media screen - {{2}}foo - {{3}}bar: baz - """, """ - {{1}}@media screen { - {{2}}foo { - {{3}}bar: baz; + _expectSourceMap( + """ + {{1}}@media screen + {{2}}foo + {{3}}bar: baz + """, + """ + {{1}}@media screen { + {{2}}foo { + {{3}}bar: baz; + } } - } - """, """ - {{1}}@media screen { - {{2}}foo { - {{3}}bar: baz; + """, + """ + {{1}}@media screen { + {{2}}foo { + {{3}}bar: baz; + } } - } - """); + """, + ); }); test("within a style rule", () { - _expectSourceMap(""" - {{1}}foo - {{2}}@media screen - {{3}}bar: baz - """, """ - {{1}}foo { - {{2}}@media screen { - {{3}}bar: baz; - } - } - """, """ - {{2}}@media screen { + _expectSourceMap( + """ + {{1}}foo + {{2}}@media screen + {{3}}bar: baz + """, + """ {{1}}foo { - {{3}}bar: baz; + {{2}}@media screen { + {{3}}bar: baz; + } } - } - """); + """, + """ + {{2}}@media screen { + {{1}}foo { + {{3}}bar: baz; + } + } + """, + ); }); }); group("@supports", () { test("at the root", () { - _expectSourceMap(""" - {{1}}@supports (display: grid) - {{2}}foo - {{3}}bar: baz - """, """ - {{1}}@supports (display: grid) { - {{2}}foo { - {{3}}bar: baz; + _expectSourceMap( + """ + {{1}}@supports (display: grid) + {{2}}foo + {{3}}bar: baz + """, + """ + {{1}}@supports (display: grid) { + {{2}}foo { + {{3}}bar: baz; + } } - } - """, """ - {{1}}@supports (display: grid) { - {{2}}foo { - {{3}}bar: baz; + """, + """ + {{1}}@supports (display: grid) { + {{2}}foo { + {{3}}bar: baz; + } } - } - """); + """, + ); }); test("within a style rule", () { - _expectSourceMap(""" - {{1}}foo - {{2}}@supports (display: grid) - {{3}}bar: baz - """, """ - {{1}}foo { - {{2}}@supports (display: grid) { - {{3}}bar: baz; - } - } - """, """ - {{2}}@supports (display: grid) { + _expectSourceMap( + """ + {{1}}foo + {{2}}@supports (display: grid) + {{3}}bar: baz + """, + """ {{1}}foo { - {{3}}bar: baz; + {{2}}@supports (display: grid) { + {{3}}bar: baz; + } } - } - """); + """, + """ + {{2}}@supports (display: grid) { + {{1}}foo { + {{3}}bar: baz; + } + } + """, + ); }); }); group("a value from a variable defined", () { group("in", () { test("a variable declaration", () { - _expectScssSourceMap(r""" - $var: {{1}}value; + _expectScssSourceMap( + r""" + $var: {{1}}value; - {{2}}a { - {{3}}b: $var; - } - """, """ - {{2}}a { - {{3}}b: {{1}}value; - } - """); + {{2}}a { + {{3}}b: $var; + } + """, + """ + {{2}}a { + {{3}}b: {{1}}value; + } + """, + ); }); test("an @each rule", () { - _expectScssSourceMap(r""" - @each $var in {{1}}1 2 { + _expectScssSourceMap( + r""" + @each $var in {{1}}1 2 { + {{2}}a { + {{3}}b: $var; + } + } + """, + """ {{2}}a { - {{3}}b: $var; + {{3}}b: {{1}}1; } - } - """, """ - {{2}}a { - {{3}}b: {{1}}1; - } - {{2}}a { - {{3}}b: {{1}}2; - } - """); + {{2}}a { + {{3}}b: {{1}}2; + } + """, + ); }); test("a @for rule", () { - _expectScssSourceMap(r""" - @for $var from {{1}}1 through 2 { + _expectScssSourceMap( + r""" + @for $var from {{1}}1 through 2 { + {{2}}a { + {{3}}b: $var; + } + } + """, + """ {{2}}a { - {{3}}b: $var; + {{3}}b: {{1}}1; } - } - """, """ - {{2}}a { - {{3}}b: {{1}}1; - } - {{2}}a { - {{3}}b: {{1}}2; - } - """); + {{2}}a { + {{3}}b: {{1}}2; + } + """, + ); }); group("a mixin argument that is", () { test("the default value", () { - _expectScssSourceMap(r""" - @mixin foo($var: {{1}}1) { - {{2}}b: $var; - } - - {{3}}a { - @include foo(); - } - """, """ - {{3}}a { - {{2}}b: {{1}}1; - } - """); + _expectScssSourceMap( + r""" + @mixin foo($var: {{1}}1) { + {{2}}b: $var; + } + + {{3}}a { + @include foo(); + } + """, + """ + {{3}}a { + {{2}}b: {{1}}1; + } + """, + ); }); test("passed by position", () { - _expectScssSourceMap(r""" - @mixin foo($var) { - {{1}}b: $var; - } - - {{2}}a { - @include foo({{3}}1); - } - """, """ - {{2}}a { - {{1}}b: {{3}}1; - } - """); + _expectScssSourceMap( + r""" + @mixin foo($var) { + {{1}}b: $var; + } + + {{2}}a { + @include foo({{3}}1); + } + """, + """ + {{2}}a { + {{1}}b: {{3}}1; + } + """, + ); }); test("passed by name", () { - _expectScssSourceMap(r""" - @mixin foo($var) { - {{1}}b: $var; - } - - {{2}}a { - @include foo($var: {{3}}1); - } - """, """ - {{2}}a { - {{1}}b: {{3}}1; - } - """); + _expectScssSourceMap( + r""" + @mixin foo($var) { + {{1}}b: $var; + } + + {{2}}a { + @include foo($var: {{3}}1); + } + """, + """ + {{2}}a { + {{1}}b: {{3}}1; + } + """, + ); }); test("passed by arglist", () { - _expectScssSourceMap(r""" - @mixin foo($var) { - {{1}}b: $var; - } - - {{2}}a { - @include foo({{3}}(1,)...); - } - """, """ - {{2}}a { - {{1}}b: {{3}}1; - } - """); + _expectScssSourceMap( + r""" + @mixin foo($var) { + {{1}}b: $var; + } + + {{2}}a { + @include foo({{3}}(1,)...); + } + """, + """ + {{2}}a { + {{1}}b: {{3}}1; + } + """, + ); }); }); }); group("in a variable which is referenced by", () { test("a variable rename", () { - _expectScssSourceMap(r""" - $var1: {{1}}value; - $var2: $var1; + _expectScssSourceMap( + r""" + $var1: {{1}}value; + $var2: $var1; - {{2}}a { - {{3}}b: $var2; - } - """, """ - {{2}}a { - {{3}}b: {{1}}value; - } - """); + {{2}}a { + {{3}}b: $var2; + } + """, + """ + {{2}}a { + {{3}}b: {{1}}value; + } + """, + ); }); test("an @each rule from a variable", () { - _expectScssSourceMap(r""" - $list: {{1}}1 2; - - @each $var in $list { + _expectScssSourceMap( + r""" + $list: {{1}}1 2; + + @each $var in $list { + {{2}}a { + {{3}}b: $var; + } + } + """, + """ {{2}}a { - {{3}}b: $var; + {{3}}b: {{1}}1; } - } - """, """ - {{2}}a { - {{3}}b: {{1}}1; - } - {{2}}a { - {{3}}b: {{1}}2; - } - """); + {{2}}a { + {{3}}b: {{1}}2; + } + """, + ); }); test("a @for rule from a variable", () { - _expectScssSourceMap(r""" - $start: {{1}}1; - $end: 2; - - @for $var from $start through $end { + _expectScssSourceMap( + r""" + $start: {{1}}1; + $end: 2; + + @for $var from $start through $end { + {{2}}a { + {{3}}b: $var; + } + } + """, + """ {{2}}a { - {{3}}b: $var; + {{3}}b: {{1}}1; } - } - """, """ - {{2}}a { - {{3}}b: {{1}}1; - } - {{2}}a { - {{3}}b: {{1}}2; - } - """); + {{2}}a { + {{3}}b: {{1}}2; + } + """, + ); }); test("a @use rule with a with clause", () { - _expectScssSourceMap(r""" - $var1: {{1}}new value; - @use 'other' with ($var2: $var1); + _expectScssSourceMap( + r""" + $var1: {{1}}new value; + @use 'other' with ($var2: $var1); - {{2}}a { - {{3}}b: other.$var2; - } - """, """ - {{2}}a { - {{3}}b: {{1}}new value; - } - """, - importer: TestImporter( - (url) => Uri.parse("u:$url"), - (_) => ImporterResult(r"$var2: default value !default;", - syntax: Syntax.scss))); + {{2}}a { + {{3}}b: other.$var2; + } + """, + """ + {{2}}a { + {{3}}b: {{1}}new value; + } + """, + importer: TestImporter( + (url) => Uri.parse("u:$url"), + (_) => ImporterResult( + r"$var2: default value !default;", + syntax: Syntax.scss, + ), + ), + ); }); group("a mixin argument that is", () { test("the default value", () { - _expectScssSourceMap(r""" - $original: {{1}}1; - - @mixin foo($var: $original) { - {{2}}b: $var; - } - - {{3}}a { - @include foo(); - } - """, """ - {{3}}a { - {{2}}b: {{1}}1; - } - """); + _expectScssSourceMap( + r""" + $original: {{1}}1; + + @mixin foo($var: $original) { + {{2}}b: $var; + } + + {{3}}a { + @include foo(); + } + """, + """ + {{3}}a { + {{2}}b: {{1}}1; + } + """, + ); }); test("passed by position", () { - _expectScssSourceMap(r""" - $original: {{1}}1; - - @mixin foo($var) { - {{2}}b: $var; - } - - {{3}}a { - @include foo($original); - } - """, """ - {{3}}a { - {{2}}b: {{1}}1; - } - """); + _expectScssSourceMap( + r""" + $original: {{1}}1; + + @mixin foo($var) { + {{2}}b: $var; + } + + {{3}}a { + @include foo($original); + } + """, + """ + {{3}}a { + {{2}}b: {{1}}1; + } + """, + ); }); test("passed by name", () { - _expectScssSourceMap(r""" - $original: {{1}}1; - - @mixin foo($var) { - {{2}}b: $var; - } - - {{3}}a { - @include foo($var: $original); - } - """, """ - {{3}}a { - {{2}}b: {{1}}1; - } - """); + _expectScssSourceMap( + r""" + $original: {{1}}1; + + @mixin foo($var) { + {{2}}b: $var; + } + + {{3}}a { + @include foo($var: $original); + } + """, + """ + {{3}}a { + {{2}}b: {{1}}1; + } + """, + ); }); test("passed by arglist", () { - _expectScssSourceMap(r""" - $original: {{1}}1; - - @mixin foo($var) { - {{2}}b: $var; - } - - {{3}}a { - @include foo($original...); - } - """, """ - {{3}}a { - {{2}}b: {{1}}1; - } - """); + _expectScssSourceMap( + r""" + $original: {{1}}1; + + @mixin foo($var) { + {{2}}b: $var; + } + + {{3}}a { + @include foo($original...); + } + """, + """ + {{3}}a { + {{2}}b: {{1}}1; + } + """, + ); }); }); }); @@ -644,30 +764,39 @@ void main() { group("a stylesheet with Unicode characters", () { test("in expanded mode", () { - _expectSourceMap(""" - {{1}}föö - {{2}}bär: bäz - """, """ - {{1}}föö { - {{2}}bär: bäz; - } - """, """ - @charset "UTF-8"; - {{1}}föö { - {{2}}bär: bäz; - } - """); + _expectSourceMap( + """ + {{1}}föö + {{2}}bär: bäz + """, + """ + {{1}}föö { + {{2}}bär: bäz; + } + """, + """ + @charset "UTF-8"; + {{1}}föö { + {{2}}bär: bäz; + } + """, + ); }); test("in compressed mode", () { - _expectSourceMap(""" - {{1}}föö - {{2}}bär: bäz - """, """ - {{1}}föö { - {{2}}bär: bäz; - } - """, "\uFEFF{{1}}föö{{{2}}bär:bäz}", style: OutputStyle.compressed); + _expectSourceMap( + """ + {{1}}föö + {{2}}bär: bäz + """, + """ + {{1}}föö { + {{2}}bär: bäz; + } + """, + "\uFEFF{{1}}föö{{{2}}bär:bäz}", + style: OutputStyle.compressed, + ); }); }); }); @@ -678,16 +807,21 @@ void main() { // browsers can link to variable declarations rather than just usages. // However, we want to refer to the usages when reporting errors because // they have more context. - expect(() { - compileString(r""" - $map: (a: b); - x {y: $map} - """, sourceMap: (_) {}); - }, throwsA(predicate((untypedError) { - var error = untypedError as SourceSpanException; - expect(error.span!.text, equals(r"$map")); - return true; - }))); + expect( + () { + compileString(r""" + $map: (a: b); + x {y: $map} + """, sourceMap: (_) {}); + }, + throwsA( + predicate((untypedError) { + var error = untypedError as SourceSpanException; + expect(error.span!.text, equals(r"$map")); + return true; + }), + ), + ); }); } @@ -711,41 +845,70 @@ void main() { /// target locations. /// /// This also re-indents the input strings with [_reindent]. -void _expectSourceMap(String sass, String scss, String css, - {Importer? importer, OutputStyle? style}) { +void _expectSourceMap( + String sass, + String scss, + String css, { + Importer? importer, + OutputStyle? style, +}) { _expectSassSourceMap(sass, css, importer: importer, style: style); _expectScssSourceMap(scss, css, importer: importer, style: style); } /// Like [_expectSourceMap], but with only SCSS source. -void _expectScssSourceMap(String scss, String css, - {Importer? importer, OutputStyle? style}) { +void _expectScssSourceMap( + String scss, + String css, { + Importer? importer, + OutputStyle? style, +}) { var (scssText, scssLocations) = _extractLocations(_reindent(scss)); var (cssText, cssLocations) = _extractLocations(_reindent(css)); late SingleMapping scssMap; - var scssOutput = compileString(scssText, - sourceMap: (map) => scssMap = map, importer: importer, style: style); + var scssOutput = compileString( + scssText, + sourceMap: (map) => scssMap = map, + importer: importer, + style: style, + ); expect(scssOutput, equals(cssText)); _expectMapMatches( - scssMap, scssText, cssText, _pairsToMap(scssLocations), cssLocations); + scssMap, + scssText, + cssText, + _pairsToMap(scssLocations), + cssLocations, + ); } /// Like [_expectSourceMap], but with only indented source. -void _expectSassSourceMap(String sass, String css, - {Importer? importer, OutputStyle? style}) { +void _expectSassSourceMap( + String sass, + String css, { + Importer? importer, + OutputStyle? style, +}) { var (sassText, sassLocations) = _extractLocations(_reindent(sass)); var (cssText, cssLocations) = _extractLocations(_reindent(css)); late SingleMapping sassMap; - var sassOutput = compileString(sassText, - indented: true, - sourceMap: (map) => sassMap = map, - importer: importer, - style: style); + var sassOutput = compileString( + sassText, + indented: true, + sourceMap: (map) => sassMap = map, + importer: importer, + style: style, + ); expect(sassOutput, equals(cssText)); _expectMapMatches( - sassMap, sassText, cssText, _pairsToMap(sassLocations), cssLocations); + sassMap, + sassText, + cssText, + _pairsToMap(sassLocations), + cssLocations, + ); } /// Returns [string] with leading whitespace stripped from each line so that the @@ -779,7 +942,7 @@ String _reindent(String string) { } locations.add(( scanner.substring(start, scanner.position - 2), - SourceLocation(offset, line: line, column: column) + SourceLocation(offset, line: line, column: column), )); } else if (scanner.scanChar($lf)) { offset++; @@ -810,13 +973,16 @@ Map _pairsToMap(Iterable<(K, V)> pairs) { /// Asserts that the entries in [map] match the map given by [sourceLocations] /// and [targetLocations]. void _expectMapMatches( - SingleMapping map, - String sourceText, - String targetText, - Map sourceLocations, - List<(String, SourceLocation)> targetLocations) { - expect(sourceLocations.keys, - equals({for (var (name, _) in targetLocations) name})); + SingleMapping map, + String sourceText, + String targetText, + Map sourceLocations, + List<(String, SourceLocation)> targetLocations, +) { + expect( + sourceLocations.keys, + equals({for (var (name, _) in targetLocations) name}), + ); String actualMap() => "\nActual map:\n\n" + _mapToString(map, sourceText, targetText) + "\n"; @@ -826,9 +992,11 @@ void _expectMapMatches( var expectedSource = sourceLocations[name]!; if (!entryIter.moveNext()) { - fail('Missing mapping "$name", expected ' - '${_mapping(expectedSource, expectedTarget)}.\n' + - actualMap()); + fail( + 'Missing mapping "$name", expected ' + '${_mapping(expectedSource, expectedTarget)}.\n' + + actualMap(), + ); } var entry = entryIter.current; @@ -836,14 +1004,19 @@ void _expectMapMatches( expectedSource.column != entry.source.column || expectedTarget.line != entry.target.line || expectedTarget.column != entry.target.column) { - fail('Mapping "$name" was ${_mapping(entry.source, entry.target)}, ' - 'expected ${_mapping(expectedSource, expectedTarget)}.\n' + - actualMap()); + fail( + 'Mapping "$name" was ${_mapping(entry.source, entry.target)}, ' + 'expected ${_mapping(expectedSource, expectedTarget)}.\n' + + actualMap(), + ); } } - expect(entryIter.moveNext(), isFalse, - reason: 'Expected no more mappings.\n' + actualMap()); + expect( + entryIter.moveNext(), + isFalse, + reason: 'Expected no more mappings.\n' + actualMap(), + ); } /// Converts a [map] back into [Entry]s. @@ -851,9 +1024,10 @@ Iterable _entriesForMap(SingleMapping map) sync* { for (var lineEntry in map.lines) { for (var entry in lineEntry.entries) { yield Entry( - SourceLocation(0, line: entry.sourceLine, column: entry.sourceColumn), - SourceLocation(0, line: lineEntry.line, column: entry.column), - null); + SourceLocation(0, line: entry.sourceLine, column: entry.sourceColumn), + SourceLocation(0, line: lineEntry.line, column: entry.column), + null, + ); } } } @@ -879,8 +1053,10 @@ String _mapToString(SingleMapping map, String sourceText, String targetText) { var entryNames = <(int, int), String>{}; var i = 0; for (var entry in entriesInSourceOrder) { - entryNames.putIfAbsent( - (entry.source.line, entry.source.column), () => (++i).toString()); + entryNames.putIfAbsent(( + entry.source.line, + entry.source.column, + ), () => (++i).toString()); } var sourceScanner = LineScanner(sourceText); diff --git a/test/util/string_test.dart b/test/util/string_test.dart index 9aa6563de..c78dac5b5 100644 --- a/test/util/string_test.dart +++ b/test/util/string_test.dart @@ -10,8 +10,10 @@ import 'package:sass/src/util/map.dart'; void main() { group("toCssIdentifier()", () { group("doesn't escape", () { - test('a double hyphen', - () => expect('--'.toCssIdentifier(), equals('--'))); + test( + 'a double hyphen', + () => expect('--'.toCssIdentifier(), equals('--')), + ); group("a starting character", () { const chars = { @@ -19,7 +21,7 @@ void main() { 'upper-case alphabetic': 'E', 'an underscore': '_', 'non-ASCII': 'ä', - 'double-width': '👭' + 'double-width': '👭', }; group("at the very beginning that's", () { @@ -30,8 +32,10 @@ void main() { group("after a single hyphen that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('-$char'.toCssIdentifier(), equals('-$char'))); + test( + name, + () => expect('-$char'.toCssIdentifier(), equals('-$char')), + ); } }); }); @@ -44,28 +48,34 @@ void main() { 'an underscore': '_', 'a hyphen': '-', 'non-ASCII': 'ä', - 'double-width': '👭' + 'double-width': '👭', }; group("after a name start that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('a$char'.toCssIdentifier(), equals('a$char'))); + test( + name, + () => expect('a$char'.toCssIdentifier(), equals('a$char')), + ); } }); group("after a double hyphen that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('--$char'.toCssIdentifier(), equals('--$char'))); + test( + name, + () => expect('--$char'.toCssIdentifier(), equals('--$char')), + ); } }); }); }); group('escapes', () { - test('a single hyphen', - () => expect('-'.toCssIdentifier(), equals('\\2d'))); + test( + 'a single hyphen', + () => expect('-'.toCssIdentifier(), equals('\\2d')), + ); group('a starting character', () { const chars = { @@ -83,8 +93,10 @@ void main() { group("after a single hyphen that's", () { for (var (name, (char, escape)) in chars.pairs) { - test(name, - () => expect('-$char'.toCssIdentifier(), equals('-$escape'))); + test( + name, + () => expect('-$char'.toCssIdentifier(), equals('-$escape')), + ); } }); }); @@ -98,23 +110,29 @@ void main() { group("after a name start that's", () { for (var (name, (char, escape)) in chars.pairs) { - test(name, - () => expect('a$char'.toCssIdentifier(), equals('a$escape'))); + test( + name, + () => expect('a$char'.toCssIdentifier(), equals('a$escape')), + ); } }); group("after a double hyphen that's", () { for (var (name, (char, escape)) in chars.pairs) { - test(name, - () => expect('--$char'.toCssIdentifier(), equals('--$escape'))); + test( + name, + () => expect('--$char'.toCssIdentifier(), equals('--$escape')), + ); } }); }); }); group('throws an error for', () { - test('the empty string', - () => expect(''.toCssIdentifier, throwsFormatException)); + test( + 'the empty string', + () => expect(''.toCssIdentifier, throwsFormatException), + ); const chars = { 'zero': '\u0000', @@ -130,29 +148,37 @@ void main() { group("after a hyphen that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('-$char'.toCssIdentifier, throwsFormatException)); + test( + name, + () => expect('-$char'.toCssIdentifier, throwsFormatException), + ); } }); group("after a name start that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('a$char'.toCssIdentifier, throwsFormatException)); + test( + name, + () => expect('a$char'.toCssIdentifier, throwsFormatException), + ); } }); group("after a double hyphen that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('--$char'.toCssIdentifier, throwsFormatException)); + test( + name, + () => expect('--$char'.toCssIdentifier, throwsFormatException), + ); } }); group("before a body char that's", () { for (var (name, char) in chars.pairs) { - test(name, - () => expect('a${char}b'.toCssIdentifier, throwsFormatException)); + test( + name, + () => expect('a${char}b'.toCssIdentifier, throwsFormatException), + ); } }); }); @@ -160,27 +186,39 @@ void main() { group('adds a space between an escape and', () { test('a digit', () => expect(' 1'.toCssIdentifier(), '\\20 1')); - test('a lowercase hex letter', - () => expect(' b'.toCssIdentifier(), '\\20 b')); + test( + 'a lowercase hex letter', + () => expect(' b'.toCssIdentifier(), '\\20 b'), + ); - test('an uppercase hex letter', - () => expect(' B'.toCssIdentifier(), '\\20 B')); + test( + 'an uppercase hex letter', + () => expect(' B'.toCssIdentifier(), '\\20 B'), + ); }); group('doesn\'t add a space between an escape and', () { test( - 'the end of the string', () => expect(' '.toCssIdentifier(), '\\20')); + 'the end of the string', + () => expect(' '.toCssIdentifier(), '\\20'), + ); - test('a lowercase non-hex letter', - () => expect(' g'.toCssIdentifier(), '\\20g')); + test( + 'a lowercase non-hex letter', + () => expect(' g'.toCssIdentifier(), '\\20g'), + ); - test('an uppercase non-hex letter', - () => expect(' G'.toCssIdentifier(), '\\20G')); + test( + 'an uppercase non-hex letter', + () => expect(' G'.toCssIdentifier(), '\\20G'), + ); test('a hyphen', () => expect(' -'.toCssIdentifier(), '\\20-')); - test('a non-ascii character', - () => expect(' ä'.toCssIdentifier(), '\\20ä')); + test( + 'a non-ascii character', + () => expect(' ä'.toCssIdentifier(), '\\20ä'), + ); test('another escape', () => expect(' '.toCssIdentifier(), '\\20\\20')); }); diff --git a/tool/grind.dart b/tool/grind.dart index 6f2acc9db..df47da6bd 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -35,14 +35,21 @@ void main(List args) { pkg.homebrewFormula.value = "Formula/sass.rb"; pkg.homebrewEditFormula.value = _updateHomebrewLanguageRevision; pkg.jsRequires.value = [ - pkg.JSRequire("@parcel/watcher", - target: pkg.JSRequireTarget.cli, lazy: true, optional: true), + pkg.JSRequire( + "@parcel/watcher", + target: pkg.JSRequireTarget.cli, + lazy: true, + optional: true, + ), pkg.JSRequire("immutable", target: pkg.JSRequireTarget.all), pkg.JSRequire("chokidar", target: pkg.JSRequireTarget.cli), pkg.JSRequire("readline", target: pkg.JSRequireTarget.cli), pkg.JSRequire("fs", target: pkg.JSRequireTarget.node), - pkg.JSRequire("module", - target: pkg.JSRequireTarget.node, identifier: 'nodeModule'), + pkg.JSRequire( + "module", + target: pkg.JSRequireTarget.node, + identifier: 'nodeModule', + ), pkg.JSRequire("stream", target: pkg.JSRequireTarget.node), pkg.JSRequire("util", target: pkg.JSRequireTarget.node), ]; @@ -112,15 +119,17 @@ void main(List args) { pkg.environmentConstants.fn = () { if (!Directory('build/language').existsSync()) { - fail('Run `dart run grinder protobuf` before building Dart Sass ' - 'executables.'); + fail( + 'Run `dart run grinder protobuf` before building Dart Sass ' + 'executables.', + ); } return { ...pkg.environmentConstants.defaultValue, - "protocol-version": File('build/language/spec/EMBEDDED_PROTOCOL_VERSION') - .readAsStringSync() - .trim(), + "protocol-version": File( + 'build/language/spec/EMBEDDED_PROTOCOL_VERSION', + ).readAsStringSync().trim(), "compiler-version": pkg.pubspec.version!.toString(), }; }; @@ -141,7 +150,8 @@ void all() {} @Task('Run the Dart formatter.') void format() { - run('dart', arguments: ['format', '--fix', '.']); + run('dart', arguments: ['format', '.']); + run('dart', arguments: ['fix', '--apply', '.']); } @Task('Installs dependencies from npm.') @@ -149,8 +159,15 @@ void npmInstall() => run(Platform.isWindows ? "npm.cmd" : "npm", arguments: ["install"]); @Task('Runs the tasks that are required for running tests.') -@Depends(format, synchronize, protobuf, deprecations, "pkg-npm-dev", npmInstall, - "pkg-standalone-dev") +@Depends( + format, + synchronize, + protobuf, + deprecations, + "pkg-npm-dev", + npmInstall, + "pkg-standalone-dev", +) void beforeTest() {} String get _nuspec => """ @@ -176,10 +193,11 @@ This package is Dart Sass, the new Dart implementation of Sass. """; final _readAndResolveRegExp = RegExp( - r"^$", - multiLine: true); + r"^$", + multiLine: true, +); /// Reads a Markdown file from [path] and resolves include directives. /// @@ -187,9 +205,9 @@ final _readAndResolveRegExp = RegExp( /// which must appear on its own line. PATH is a relative file: URL to another /// Markdown file, and HEADER is the name of a header in that file whose /// contents should be included as-is. -String _readAndResolveMarkdown(String path) => File(path) - .readAsStringSync() - .replaceAllMapped(_readAndResolveRegExp, (match) { +String _readAndResolveMarkdown(String path) => File( + path, + ).readAsStringSync().replaceAllMapped(_readAndResolveRegExp, (match) { late String included; try { included = File(p.join(p.dirname(path), p.fromUri(match[1]))) @@ -229,7 +247,7 @@ Map _fetchJSTypes() { for (var entry in Directory(typeRoot).listSync(recursive: true)) if (entry is File && entry.path.endsWith('.d.ts')) p.join('types', p.relative(entry.path, from: typeRoot)): - entry.readAsStringSync() + entry.readAsStringSync(), }; } @@ -260,13 +278,17 @@ dart run protoc_plugin "\$@" run('chmod', arguments: ['a+x', 'build/protoc-gen-dart']); } - await runAsync("buf", - arguments: ["generate"], - runOptions: RunOptions(environment: { + await runAsync( + "buf", + arguments: ["generate"], + runOptions: RunOptions( + environment: { "PATH": 'build' + (Platform.isWindows ? ";" : ":") + - Platform.environment["PATH"]! - })); + Platform.environment["PATH"]!, + }, + ), + ); } /// After building the NPM package, add default exports to @@ -306,19 +328,21 @@ function defaultExportDeprecation() { /// A regular expression to locate the language repo revision in the Dart Sass /// Homebrew formula. final _homebrewLanguageRegExp = RegExp( - r'resource "language" do$' - r'(?:(?! end$).)+' - r'revision: "([a-f0-9]{40})"', - dotAll: true, - multiLine: true); + r'resource "language" do$' + r'(?:(?! end$).)+' + r'revision: "([a-f0-9]{40})"', + dotAll: true, + multiLine: true, +); /// Updates the Homebrew [formula] to change the revision of the language repo /// to the latest revision. String _updateHomebrewLanguageRevision(String formula) { - var languageRepoRevision = run("git", - arguments: ["ls-remote", "https://github.com/sass/sass"], quiet: true) - .split("\t") - .first; + var languageRepoRevision = run( + "git", + arguments: ["ls-remote", "https://github.com/sass/sass"], + quiet: true, + ).split("\t").first; var match = _homebrewLanguageRegExp.firstMatch(formula); if (match == null) { diff --git a/tool/grind/bazel.dart b/tool/grind/bazel.dart index e891f2ab4..62820145e 100644 --- a/tool/grind/bazel.dart +++ b/tool/grind/bazel.dart @@ -16,54 +16,69 @@ Future updateBazel() async { run("npm", arguments: ["install", "-g", "yarn"]); - var repo = - cloneOrCheckout("https://github.com/bazelbuild/rules_sass.git", "main"); + var repo = cloneOrCheckout( + "https://github.com/bazelbuild/rules_sass.git", + "main", + ); var packageFile = File(p.join(repo, "sass", "package.json")); log("updating ${packageFile.path}"); - packageFile.writeAsStringSync(packageFile - .readAsStringSync() - .replaceFirst(RegExp(r'"sass": "[^"]+"'), '"sass": "${pkg.version}"')); + packageFile.writeAsStringSync( + packageFile.readAsStringSync().replaceFirst( + RegExp(r'"sass": "[^"]+"'), + '"sass": "${pkg.version}"', + ), + ); try { run("yarn", workingDirectory: p.join(repo, "sass")); } on ProcessException catch (error) { if (error.stderr.contains("Couldn't find any versions for \"sass\"")) { - log("The new sass version doesn't seem to be available yet, waiting 30s..."); + log( + "The new sass version doesn't seem to be available yet, waiting 30s...", + ); await Future.delayed(Duration(minutes: 2)); run("yarn", workingDirectory: p.join(repo, "sass")); } } - run("git", - arguments: [ - "commit", - "--all", - "--message", - "Update Dart Sass to ${pkg.version}" - ], - workingDirectory: repo, - runOptions: sassBotEnvironment); + run( + "git", + arguments: [ + "commit", + "--all", + "--message", + "Update Dart Sass to ${pkg.version}", + ], + workingDirectory: repo, + runOptions: sassBotEnvironment, + ); - run("git", - arguments: ["tag", pkg.version.toString()], - workingDirectory: repo, - runOptions: sassBotEnvironment); + run( + "git", + arguments: ["tag", pkg.version.toString()], + workingDirectory: repo, + runOptions: sassBotEnvironment, + ); var username = environment('GH_USER'); var password = environment('GH_TOKEN'); - await runAsync("git", - arguments: [ - "push", - "--tags", - "https://$username:$password@github.com/bazelbuild/rules_sass.git", - ], - workingDirectory: repo); - await runAsync("git", - arguments: [ - "push", - "https://$username:$password@github.com/bazelbuild/rules_sass.git", - "HEAD:main" - ], - workingDirectory: repo); + await runAsync( + "git", + arguments: [ + "push", + "--tags", + "https://$username:$password@github.com/bazelbuild/rules_sass.git", + ], + workingDirectory: repo, + ); + await runAsync( + "git", + arguments: [ + "push", + "https://$username:$password@github.com/bazelbuild/rules_sass.git", + "HEAD:main", + ], + workingDirectory: repo, + ); } diff --git a/tool/grind/benchmark.dart b/tool/grind/benchmark.dart index 82ef95d3f..15748dd71 100644 --- a/tool/grind/benchmark.dart +++ b/tool/grind/benchmark.dart @@ -19,26 +19,48 @@ Future benchmarkGenerate() async { await _writeNTimes("${sources.path}/small_plain.scss", ".foo {a: b}", 4); await _writeNTimes( - "${sources.path}/large_plain.scss", ".foo {a: b}", math.pow(2, 17)); - await _writeNTimes("${sources.path}/preceding_sparse_extend.scss", - ".foo {a: b}", math.pow(2, 17), - header: '.x {@extend .y}', footer: '.y {a: b}'); - await _writeNTimes("${sources.path}/following_sparse_extend.scss", - ".foo {a: b}", math.pow(2, 17), - header: '.y {a: b}', footer: '.x {@extend .y}'); - await _writeNTimes("${sources.path}/preceding_dense_extend.scss", - ".foo {a: b}", math.pow(2, 17), - header: '.bar {@extend .foo}'); - await _writeNTimes("${sources.path}/following_dense_extend.scss", - ".foo {a: b}", math.pow(2, 17), - footer: '.bar {@extend .foo}'); + "${sources.path}/large_plain.scss", + ".foo {a: b}", + math.pow(2, 17), + ); + await _writeNTimes( + "${sources.path}/preceding_sparse_extend.scss", + ".foo {a: b}", + math.pow(2, 17), + header: '.x {@extend .y}', + footer: '.y {a: b}', + ); + await _writeNTimes( + "${sources.path}/following_sparse_extend.scss", + ".foo {a: b}", + math.pow(2, 17), + header: '.y {a: b}', + footer: '.x {@extend .y}', + ); + await _writeNTimes( + "${sources.path}/preceding_dense_extend.scss", + ".foo {a: b}", + math.pow(2, 17), + header: '.bar {@extend .foo}', + ); + await _writeNTimes( + "${sources.path}/following_dense_extend.scss", + ".foo {a: b}", + math.pow(2, 17), + footer: '.bar {@extend .foo}', + ); cloneOrCheckout("https://github.com/twbs/bootstrap", "v4.1.3"); - await _writeNTimes("${sources.path}/bootstrap.scss", - "@import '../bootstrap/scss/bootstrap';", 16); - - cloneOrCheckout("https://github.com/alex-page/sass-a11ycolor", - "2e7ef93ec06f8bbec80b632863e4b2811618af89"); + await _writeNTimes( + "${sources.path}/bootstrap.scss", + "@import '../bootstrap/scss/bootstrap';", + 16, + ); + + cloneOrCheckout( + "https://github.com/alex-page/sass-a11ycolor", + "2e7ef93ec06f8bbec80b632863e4b2811618af89", + ); File("${sources.path}/a11ycolor.scss").writeAsStringSync(""" @import '../sass-a11ycolor/dist'; @@ -57,16 +79,19 @@ Future benchmarkGenerate() async { """); cloneOrCheckout("https://github.com/zaydek/duomo", "v0.7.12"); - File("${sources.path}/duomo.scss") - .writeAsStringSync("@import '../duomo/scripts/duomo.scss'"); + File( + "${sources.path}/duomo.scss", + ).writeAsStringSync("@import '../duomo/scripts/duomo.scss'"); var carbon = cloneOrCheckout( - "https://github.com/carbon-design-system/ibm-cloud-cognitive", - "@carbon/ibm-cloud-cognitive@1.0.0-rc.0"); + "https://github.com/carbon-design-system/ibm-cloud-cognitive", + "@carbon/ibm-cloud-cognitive@1.0.0-rc.0", + ); await runAsync("yarn", arguments: ["install"], workingDirectory: carbon); - File("${sources.path}/carbon.scss") - .writeAsStringSync("@import '../ibm-cloud-cognitive/packages/" - "cloud-cognitive/src/index-without-carbon-released-only'"); + File("${sources.path}/carbon.scss").writeAsStringSync( + "@import '../ibm-cloud-cognitive/packages/" + "cloud-cognitive/src/index-without-carbon-released-only'", + ); } /// Writes [times] instances of [text] to [path]. @@ -74,8 +99,13 @@ Future benchmarkGenerate() async { /// If [header] is passed, it's written before [text]. If [footer] is passed, /// it's written after [text]. If the file already exists and is the expected /// length, it's not written. -Future _writeNTimes(String path, String text, num times, - {String? header, String? footer}) async { +Future _writeNTimes( + String path, + String text, + num times, { + String? header, + String? footer, +}) async { var file = File(path); var expectedLength = (header == null ? 0 : header.length + 1) + (text.length + 1) * times + @@ -96,16 +126,23 @@ Future _writeNTimes(String path, String text, num times, } @Task('Run benchmarks for Sass compilation speed.') -@Depends(benchmarkGenerate, "pkg-compile-snapshot", "pkg-compile-native", - "pkg-npm-release") +@Depends( + benchmarkGenerate, + "pkg-compile-snapshot", + "pkg-compile-native", + "pkg-npm-release", +) Future benchmark() async { var libsass = cloneOrCheckout('https://github.com/sass/libsass', 'master'); var sassc = cloneOrCheckout('https://github.com/sass/sassc', 'master'); - await runAsync("make", - runOptions: RunOptions( - workingDirectory: sassc, - environment: {"SASS_LIBSASS_PATH": p.absolute(libsass)})); + await runAsync( + "make", + runOptions: RunOptions( + workingDirectory: sassc, + environment: {"SASS_LIBSASS_PATH": p.absolute(libsass)}, + ), + ); log(""); var libsassRevision = await _revision(libsass); @@ -136,45 +173,45 @@ I ran five instances of each configuration and recorded the fastest time. [ "preceding_sparse_extend.scss", "Preceding Sparse `@extend`", - "`.x {@extend .y}`, 2^17 instances of `.foo {a: b}`, and then `.y {a: b}`" + "`.x {@extend .y}`, 2^17 instances of `.foo {a: b}`, and then `.y {a: b}`", ], [ "following_sparse_extend.scss", "Following Sparse `@extend`", - "`.y {a: b}`, 2^17 instances of `.foo {a: b}`, and then `.x {@extend .y}`" + "`.y {a: b}`, 2^17 instances of `.foo {a: b}`, and then `.x {@extend .y}`", ], [ "preceding_dense_extend.scss", "Preceding Dense `@extend`", - "`.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`" + "`.bar {@extend .foo}` followed by 2^17 instances of `.foo {a: b}`", ], [ "following_dense_extend.scss", "Following Dense `@extend`", - "2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`" + "2^17 instances of `.foo {a: b}` followed by `.bar {@extend .foo}`", ], [ "bootstrap.scss", "Bootstrap", - "16 instances of importing the Bootstrap framework" + "16 instances of importing the Bootstrap framework", ], [ "a11ycolor.scss", "a11ycolor", - "test cases for a computation-intensive color-processing library" + "test cases for a computation-intensive color-processing library", ], [ "duomo.scss", "Duomo", "the output of the numerically-intensive Duomo framework " - "(skipping LibSass due to module system use)" + "(skipping LibSass due to module system use)", ], [ "carbon.scss", "Carbon", "the output of the import-intensive Carbon framework", "-I", - "build/ibm-cloud-cognitive/node_modules" + "build/ibm-cloud-cognitive/node_modules", ], ]; @@ -193,8 +230,10 @@ I ran five instances of each configuration and recorded the fastest time. Duration? sasscTime; if (!libsassIncompatible.contains(info[1])) { - sasscTime = - await _benchmark(p.join(sassc, 'bin', 'sassc'), [path, ...extraArgs]); + sasscTime = await _benchmark(p.join(sassc, 'bin', 'sassc'), [ + path, + ...extraArgs, + ]); buffer.writeln("* sassc: ${_formatTime(sasscTime)}"); } @@ -202,50 +241,69 @@ I ran five instances of each configuration and recorded the fastest time. '--no-enable-asserts', p.join('build', 'sass.snapshot'), path, - ...extraArgs + ...extraArgs, ]); - buffer.writeln("* Dart Sass from a script snapshot: " - "${_formatTime(scriptSnapshotTime)}"); - - var nativeExecutableTime = - await _benchmark(p.join('build', 'sass.native'), [path, ...extraArgs]); - buffer.writeln("* Dart Sass native executable: " - "${_formatTime(nativeExecutableTime)}"); + buffer.writeln( + "* Dart Sass from a script snapshot: " + "${_formatTime(scriptSnapshotTime)}", + ); + + var nativeExecutableTime = await _benchmark( + p.join('build', 'sass.native'), + [path, ...extraArgs], + ); + buffer.writeln( + "* Dart Sass native executable: " + "${_formatTime(nativeExecutableTime)}", + ); - var nodeTime = await _benchmark( - "node", [p.join('build', 'npm', 'sass.js'), path, ...extraArgs]); + var nodeTime = await _benchmark("node", [ + p.join('build', 'npm', 'sass.js'), + path, + ...extraArgs, + ]); buffer.writeln("* Dart Sass on Node.js: ${_formatTime(nodeTime)}"); buffer.writeln(); - buffer.writeln('Based on these numbers, Dart Sass from a native executable ' - 'is approximately:'); + buffer.writeln( + 'Based on these numbers, Dart Sass from a native executable ' + 'is approximately:', + ); buffer.writeln(); if (sasscTime != null) { buffer.writeln('* ${_compare(nativeExecutableTime, sasscTime)} libsass'); } buffer.writeln( - '* ${_compare(nativeExecutableTime, nodeTime)} Dart Sass on Node'); + '* ${_compare(nativeExecutableTime, nodeTime)} Dart Sass on Node', + ); buffer.writeln(); log(''); } buffer.write("# Prior Measurements"); perf = perf.replaceFirst( - RegExp(r"# Measurements\n[^]*# Prior Measurements"), buffer.toString()); + RegExp(r"# Measurements\n[^]*# Prior Measurements"), + buffer.toString(), + ); File("perf.md").writeAsStringSync(perf); } /// Returns the revision of the Git repository at [path]. -Future _revision(String path) async => (await runAsync("git", - arguments: ["rev-parse", "--short", "HEAD"], - quiet: true, - workingDirectory: path)) - .trim(); +Future _revision(String path) async => (await runAsync( + "git", + arguments: ["rev-parse", "--short", "HEAD"], + quiet: true, + workingDirectory: path, + )) + .trim(); /// Returns the first line of output from `executable --version`. -Future _version(String executable) async => - (await runAsync(executable, arguments: ["--version"], quiet: true)) +Future _version(String executable) async => (await runAsync( + executable, + arguments: ["--version"], + quiet: true, + )) .split("\n") .first; @@ -265,24 +323,30 @@ Future _benchmark(String executable, List arguments) async { } Future _benchmarkOnce( - String executable, List arguments) async { - var result = await Process.run( - "sh", ["-c", "time $executable ${arguments.join(' ')}"]); + String executable, + List arguments, +) async { + var result = await Process.run("sh", [ + "-c", + "time $executable ${arguments.join(' ')}", + ]); if (result.exitCode != 0) { fail("Process failed with exit code ${result.exitCode}\n${result.stderr}"); } - var match = - RegExp(r"(\d+)m(\d+)\.(\d+)s").firstMatch(result.stderr as String); + var match = RegExp( + r"(\d+)m(\d+)\.(\d+)s", + ).firstMatch(result.stderr as String); if (match == null) { fail("Process didn't print the expected format:\n${result.stderr}"); } return Duration( - minutes: int.parse(match[1]!), - seconds: int.parse(match[2]!), - milliseconds: int.parse(match[3]!)); + minutes: int.parse(match[1]!), + seconds: int.parse(match[2]!), + milliseconds: int.parse(match[3]!), + ); } String _formatTime(Duration duration) => diff --git a/tool/grind/bump_version.dart b/tool/grind/bump_version.dart index 9aac0b9c6..aa6ca82c5 100644 --- a/tool/grind/bump_version.dart +++ b/tool/grind/bump_version.dart @@ -21,11 +21,14 @@ final _sassVersionRegExp = RegExp(r'^( +)sass: (\d.*)$', multiLine: true); void addBumpVersionTasks() { for (var patch in [false, true]) { for (var dev in [true, false]) { - addTask(GrinderTask( + addTask( + GrinderTask( 'bump-version-${patch ? 'patch' : 'minor'}' + (dev ? '-dev' : ''), taskFunction: () => _bumpVersion(patch, dev), description: 'Bump the version of all packages to the next ' - '${patch ? 'patch' : 'minor'}${dev ? ' dev' : ''} version')); + '${patch ? 'patch' : 'minor'}${dev ? ' dev' : ''} version', + ), + ); } } } @@ -49,8 +52,12 @@ void _bumpVersion(bool patch, bool dev) { var nextVersion = patch || version.major == 0 ? version.nextPatch : version.nextMinor; - return Version(nextVersion.major, nextVersion.minor, nextVersion.patch, - pre: dev ? "dev" : null); + return Version( + nextVersion.major, + nextVersion.minor, + nextVersion.patch, + pre: dev ? "dev" : null, + ); } /// Adds a "No user-visible changes" entry for [version] to the changelog in @@ -60,12 +67,14 @@ void _bumpVersion(bool patch, bool dev) { var text = File(path).readAsStringSync(); if (!dev && text.startsWith("## $version-dev\n")) { File(path).writeAsStringSync( - text.replaceFirst("## $version-dev\n", "## $version\n")); + text.replaceFirst("## $version-dev\n", "## $version\n"), + ); } else if (text.startsWith("## $version\n")) { return; } else { - File(path).writeAsStringSync( - "## $version\n\n* No user-visible changes.\n\n$text"); + File( + path, + ).writeAsStringSync("## $version\n\n* No user-visible changes.\n\n$text"); } } @@ -78,17 +87,24 @@ void _bumpVersion(bool patch, bool dev) { Version bumpDartVersion(String path, [Version? sassVersion]) { var text = File(path).readAsStringSync(); var pubspec = loadYaml(text, sourceUrl: p.toUri(path)) as YamlMap; - var version = chooseNextVersion(Version.parse(pubspec["version"] as String), - pubspec.nodes["version"]!.span); + var version = chooseNextVersion( + Version.parse(pubspec["version"] as String), + pubspec.nodes["version"]!.span, + ); text = text.replaceFirst(_pubspecVersionRegExp, 'version: $version'); if (sassVersion != null) { // Don't depend on a prerelease version, depend on its released // equivalent. - var sassDependencyVersion = - Version(sassVersion.major, sassVersion.minor, sassVersion.patch); - text = text.replaceFirstMapped(_sassVersionRegExp, - (match) => '${match[1]}sass: $sassDependencyVersion'); + var sassDependencyVersion = Version( + sassVersion.major, + sassVersion.minor, + sassVersion.patch, + ); + text = text.replaceFirstMapped( + _sassVersionRegExp, + (match) => '${match[1]}sass: $sassDependencyVersion', + ); } File(path).writeAsStringSync(text); @@ -104,10 +120,14 @@ void _bumpVersion(bool patch, bool dev) { var packageJson = loadYaml(packageJsonText, sourceUrl: p.toUri(packageJsonPath)) as YamlMap; var version = chooseNextVersion( - Version.parse(packageJson["version"] as String), - packageJson.nodes["version"]!.span); - File(packageJsonPath).writeAsStringSync(JsonEncoder.withIndent(" ") - .convert({...packageJson, "version": version.toString()}) + - "\n"); + Version.parse(packageJson["version"] as String), + packageJson.nodes["version"]!.span, + ); + File(packageJsonPath).writeAsStringSync( + JsonEncoder.withIndent( + " ", + ).convert({...packageJson, "version": version.toString()}) + + "\n", + ); addChangelogEntry("pkg/sass-parser", version); } diff --git a/tool/grind/double_check.dart b/tool/grind/double_check.dart index 2578b5bf5..7191c3460 100644 --- a/tool/grind/double_check.dart +++ b/tool/grind/double_check.dart @@ -23,8 +23,10 @@ Future doubleCheckBeforeRelease() async { fail("${pkg.version} is a dev release."); } - var versionHeader = - RegExp("^## ${RegExp.escape(pkg.version.toString())}\$", multiLine: true); + var versionHeader = RegExp( + "^## ${RegExp.escape(pkg.version.toString())}\$", + multiLine: true, + ); if (!File("CHANGELOG.md").readAsStringSync().contains(versionHeader)) { fail("There's no CHANGELOG entry for ${pkg.version}."); } @@ -33,12 +35,14 @@ Future doubleCheckBeforeRelease() async { try { for (var dir in [ ".", - ...Directory("pkg").listSync().map((entry) => entry.path) + ...Directory("pkg").listSync().map((entry) => entry.path), ]) { var pubspecFile = File("$dir/pubspec.yaml"); if (!pubspecFile.existsSync()) continue; - var pubspec = Pubspec.parse(pubspecFile.readAsStringSync(), - sourceUrl: pubspecFile.uri); + var pubspec = Pubspec.parse( + pubspecFile.readAsStringSync(), + sourceUrl: pubspecFile.uri, + ); var package = await client.packageInfo(pubspec.name); if (pubspec.version == package.latestPubspec.version) { diff --git a/tool/grind/frameworks.dart b/tool/grind/frameworks.dart index 014bfbdc2..116107e1a 100644 --- a/tool/grind/frameworks.dart +++ b/tool/grind/frameworks.dart @@ -31,8 +31,10 @@ Future fetchBulma() => _getLatestRelease('jgthms/bulma'); /// If [pattern] is passed, this will clone the latest release that matches that /// pattern. Future _getLatestRelease(String slug, {Pattern? pattern}) async { - cloneOrCheckout('https://github.com/$slug', - await _findLatestRelease(slug, pattern: pattern)); + cloneOrCheckout( + 'https://github.com/$slug', + await _findLatestRelease(slug, pattern: pattern), + ); } /// Returns the tag name of the latest release for the given GitHub repository @@ -58,13 +60,20 @@ Future _findLatestRelease(String slug, {Pattern? pattern}) async { } /// Fetches the GitHub releases page for the repo at [slug]. -Future>> _fetchReleases(String slug, - {int page = 1}) async { - var result = json.decode(await http.read( - Uri.parse("https://api.github.com/repos/$slug/releases?page=$page"), +Future>> _fetchReleases( + String slug, { + int page = 1, +}) async { + var result = json.decode( + await http.read( + Uri.parse( + "https://api.github.com/repos/$slug/releases?page=$page", + ), headers: { "accept": "application/vnd.github.v3+json", - "authorization": githubAuthorization - })) as List; + "authorization": githubAuthorization, + }, + ), + ) as List; return result.cast>(); } diff --git a/tool/grind/generate_deprecations.dart b/tool/grind/generate_deprecations.dart index cac755a8e..50d411e3d 100644 --- a/tool/grind/generate_deprecations.dart +++ b/tool/grind/generate_deprecations.dart @@ -14,8 +14,9 @@ import 'utils.dart'; const yamlPath = 'build/language/spec/deprecations.yaml'; const dartPath = 'lib/src/deprecation.dart'; -final _blockRegex = - RegExp(r'// START AUTOGENERATED CODE[\s\S]*?// END AUTOGENERATED CODE'); +final _blockRegex = RegExp( + r'// START AUTOGENERATED CODE[\s\S]*?// END AUTOGENERATED CODE', +); @Task('Generate deprecation.g.dart from the list in the language repo.') @Depends(updateLanguageRepo) @@ -34,12 +35,11 @@ void deprecations() { '''); for (var MapEntry(:String key, :value) in data.entries) { var camelCase = key.replaceAllMapped( - RegExp(r'-(.)'), (match) => match.group(1)!.toUpperCase()); + RegExp(r'-(.)'), + (match) => match.group(1)!.toUpperCase(), + ); var (description, deprecatedIn, obsoleteIn) = switch (value) { - { - 'description': String description, - 'dart-sass': {'status': 'future'}, - } => + {'description': String description, 'dart-sass': {'status': 'future'}} => (description, null, null), { 'description': String description, @@ -51,14 +51,16 @@ void deprecations() { 'dart-sass': { 'status': 'obsolete', 'deprecated': String deprecatedIn, - 'obsolete': String obsoleteIn + 'obsolete': String obsoleteIn, }, } => (description, deprecatedIn, obsoleteIn), - _ => throw Exception('Invalid deprecation $key: $value') + _ => throw Exception('Invalid deprecation $key: $value'), }; - description = - description.replaceAll(r'$PLATFORM', r"${isJS ? 'JS': 'Dart'}"); + description = description.replaceAll( + r'$PLATFORM', + r"${isJS ? 'JS': 'Dart'}", + ); var constructorName = deprecatedIn == null ? '.future' : ''; var deprecatedClause = deprecatedIn == null ? '' : "deprecatedIn: '$deprecatedIn', "; @@ -68,8 +70,9 @@ void deprecations() { '${description.substring(1)}'; buffer.writeln('/// $comment'); buffer.writeln( - "$camelCase$constructorName('$key', $deprecatedClause$obsoleteClause" - "description: '$description'),"); + "$camelCase$constructorName('$key', $deprecatedClause$obsoleteClause" + "description: '$description'),", + ); } buffer.write('\n // END AUTOGENERATED CODE'); if (!dartText.contains(_blockRegex)) { diff --git a/tool/grind/sass_api.dart b/tool/grind/sass_api.dart index a45a406e8..0008e7fcf 100644 --- a/tool/grind/sass_api.dart +++ b/tool/grind/sass_api.dart @@ -16,8 +16,10 @@ import 'package:yaml/yaml.dart'; import 'utils.dart'; /// The path in which pub expects to find its credentials file. -final String _pubCredentialsPath = - p.join(applicationConfigHome('dart'), 'pub-credentials.json'); +final String _pubCredentialsPath = p.join( + applicationConfigHome('dart'), + 'pub-credentials.json', +); @Task('Deploy pkg/sass_api to pub.') Future deploySassApi() async { @@ -29,21 +31,25 @@ Future deploySassApi() async { var client = http.Client(); var pubspecPath = "pkg/sass_api/pubspec.yaml"; - var pubspec = Pubspec.parse(File(pubspecPath).readAsStringSync(), - sourceUrl: p.toUri(pubspecPath)); + var pubspec = Pubspec.parse( + File(pubspecPath).readAsStringSync(), + sourceUrl: p.toUri(pubspecPath), + ); // Remove the dependency override on `sass`, because otherwise it will block // publishing. var pubspecYaml = Map.of( - loadYaml(File(pubspecPath).readAsStringSync()) as YamlMap); + loadYaml(File(pubspecPath).readAsStringSync()) as YamlMap, + ); pubspecYaml.remove("dependency_overrides"); File(pubspecPath).writeAsStringSync(json.encode(pubspecYaml)); // We use symlinks to avoid duplicating files between the main repo and // child repos, but `pub lish` doesn't resolve these before publishing so we // have to do so manually. - for (var entry in Directory("pkg/sass_api") - .listSync(recursive: true, followLinks: false)) { + for (var entry in Directory( + "pkg/sass_api", + ).listSync(recursive: true, followLinks: false)) { if (entry is! Link) continue; var target = p.join(p.dirname(entry.path), entry.targetSync()); entry.deleteSync(); @@ -52,7 +58,12 @@ Future deploySassApi() async { log("dart pub publish ${pubspec.name}"); var process = await Process.start( - p.join(sdkDir.path, "bin/dart"), ["pub", "publish", "--force"], + p.join(sdkDir.path, "bin/dart"), + [ + "pub", + "publish", + "--force", + ], workingDirectory: "pkg/sass_api"); LineSplitter().bind(utf8.decoder.bind(process.stdout)).listen(log); LineSplitter().bind(utf8.decoder.bind(process.stderr)).listen(log); @@ -61,16 +72,17 @@ Future deploySassApi() async { } var response = await client.post( - Uri.parse("https://api.github.com/repos/sass/dart-sass/git/refs"), - headers: { - "accept": "application/vnd.github.v3+json", - "content-type": "application/json", - "authorization": githubAuthorization - }, - body: jsonEncode({ - "ref": "refs/tags/${pubspec.name}/${pubspec.version}", - "sha": Platform.environment["GITHUB_SHA"]! - })); + Uri.parse("https://api.github.com/repos/sass/dart-sass/git/refs"), + headers: { + "accept": "application/vnd.github.v3+json", + "content-type": "application/json", + "authorization": githubAuthorization, + }, + body: jsonEncode({ + "ref": "refs/tags/${pubspec.name}/${pubspec.version}", + "sha": Platform.environment["GITHUB_SHA"]!, + }), + ); if (response.statusCode != 201) { fail("${response.statusCode} error creating tag:\n${response.body}"); diff --git a/tool/grind/synchronize.dart b/tool/grind/synchronize.dart index 7da9d83af..a3e6283cd 100644 --- a/tool/grind/synchronize.dart +++ b/tool/grind/synchronize.dart @@ -25,7 +25,7 @@ final sources = const { 'lib/src/visitor/async_evaluate.dart': 'lib/src/visitor/evaluate.dart', 'lib/src/async_compile.dart': 'lib/src/compile.dart', 'lib/src/async_environment.dart': 'lib/src/environment.dart', - 'lib/src/async_import_cache.dart': 'lib/src/import_cache.dart' + 'lib/src/async_import_cache.dart': 'lib/src/import_cache.dart', }; /// Classes that are defined in the async version of a file and used as-is in @@ -57,9 +57,10 @@ String synchronizeFile(String source) { source = p.absolute(source); var visitor = _Visitor(File(source).readAsStringSync(), source); - parseFile(path: source, featureSet: FeatureSet.latestLanguageVersion()) - .unit - .accept(visitor); + parseFile( + path: source, + featureSet: FeatureSet.latestLanguageVersion(), + ).unit.accept(visitor); return visitor.result; } @@ -212,7 +213,7 @@ class _Visitor extends RecursiveAstVisitor { if (node case MethodInvocation( target: null, - methodName: SimpleIdentifier(name: "mapAsync" || "putIfAbsentAsync") + methodName: SimpleIdentifier(name: "mapAsync" || "putIfAbsentAsync"), )) { _writeTo(node); var arguments = node.argumentList.arguments; @@ -322,13 +323,14 @@ class _Visitor extends RecursiveAstVisitor { SourceSpanException _alreadyEmittedException(SourceSpan span) { var lines = _buffer.toString().split("\n"); return SourceSpanException( - "Node was already emitted. Last 3 lines:\n\n" + - lines - .slice(math.max(lines.length - 3, 0)) - .map((line) => " $line") - .join("\n") + - "\n", - span); + "Node was already emitted. Last 3 lines:\n\n" + + lines + .slice(math.max(lines.length - 3, 0)) + .map((line) => " $line") + .join("\n") + + "\n", + span, + ); } /// Returns a [FileSpan] that represents [token]'s position in the source diff --git a/tool/grind/utils.dart b/tool/grind/utils.dart index 86ea7ed96..bfec74a27 100644 --- a/tool/grind/utils.dart +++ b/tool/grind/utils.dart @@ -14,12 +14,14 @@ import 'package:path/path.dart' as p; import 'package:grinder/src/singleton.dart'; /// Options for [run] that tell Git to commit using SassBot's name and email. -final sassBotEnvironment = RunOptions(environment: { - "GIT_AUTHOR_NAME": pkg.botName.value, - "GIT_AUTHOR_EMAIL": pkg.botEmail.value, - "GIT_COMMITTER_NAME": pkg.botName.value, - "GIT_COMMITTER_EMAIL": pkg.botEmail.value -}); +final sassBotEnvironment = RunOptions( + environment: { + "GIT_AUTHOR_NAME": pkg.botName.value, + "GIT_AUTHOR_EMAIL": pkg.botEmail.value, + "GIT_COMMITTER_NAME": pkg.botName.value, + "GIT_COMMITTER_EMAIL": pkg.botEmail.value, + }, +); /// Returns the HTTP basic authentication Authorization header from the /// environment. @@ -28,8 +30,9 @@ String get githubAuthorization { return bearerToken != null ? "Bearer $bearerToken" : "Basic " + - base64.encode(utf8 - .encode(pkg.githubUser.value + ':' + pkg.githubPassword.value)); + base64.encode( + utf8.encode(pkg.githubUser.value + ':' + pkg.githubPassword.value), + ); } /// Ensure that the `build/` directory exists. @@ -66,17 +69,25 @@ String cloneOrCheckout(String url, String ref, {String? name}) { if (!Directory(p.join(path, '.git')).existsSync()) { delete(Directory(path)); run("git", arguments: ["init", path]); - run("git", - arguments: ["config", "advice.detachedHead", "false"], - workingDirectory: path); - run("git", - arguments: ["remote", "add", "origin", url], workingDirectory: path); + run( + "git", + arguments: ["config", "advice.detachedHead", "false"], + workingDirectory: path, + ); + run( + "git", + arguments: ["remote", "add", "origin", url], + workingDirectory: path, + ); } else { log("Updating $url"); } - run("git", - arguments: ["fetch", "origin", "--depth=1", ref], workingDirectory: path); + run( + "git", + arguments: ["fetch", "origin", "--depth=1", ref], + workingDirectory: path, + ); run("git", arguments: ["checkout", "FETCH_HEAD"], workingDirectory: path); log(""); @@ -93,12 +104,15 @@ void afterTask(String taskName, FutureOr callback()) { if (index == -1) fail("There is no task named $taskName."); var oldTask = grinder.tasks[index]; - grinder.tasks[index] = GrinderTask(taskName, - description: oldTask.description, - depends: oldTask.depends, taskFunction: (TaskArgs args) async { - await oldTask.execute(context, args); - await callback(); - }); + grinder.tasks[index] = GrinderTask( + taskName, + description: oldTask.description, + depends: oldTask.depends, + taskFunction: (TaskArgs args) async { + await oldTask.execute(context, args); + await callback(); + }, + ); } /// Clones the main branch of `github.com/sass/sass`. @@ -113,7 +127,10 @@ void updateLanguageRepo() { // generically to other tasks. if (Platform.environment['UPDATE_SASS_SASS_REPO'] != 'false' && Platform.environment['UPDATE_SASS_PROTOCOL'] != 'false') { - cloneOrCheckout("https://github.com/sass/sass.git", "main", - name: 'language'); + cloneOrCheckout( + "https://github.com/sass/sass.git", + "main", + name: 'language', + ); } } diff --git a/tool/utils.dart b/tool/utils.dart index 2eb303b1d..c4d4152b1 100644 --- a/tool/utils.dart +++ b/tool/utils.dart @@ -26,14 +26,19 @@ Future cloneOrCheckout(String url, String ref) async { if (Directory(p.join(path, '.git')).existsSync()) { log("Updating $url"); - await runAsync("git", - arguments: ["fetch", "origin"], workingDirectory: path); + await runAsync( + "git", + arguments: ["fetch", "origin"], + workingDirectory: path, + ); } else { delete(Directory(path)); await runAsync("git", arguments: ["clone", url, path]); - await runAsync("git", - arguments: ["config", "advice.detachedHead", "false"], - workingDirectory: path); + await runAsync( + "git", + arguments: ["config", "advice.detachedHead", "false"], + workingDirectory: path, + ); } await runAsync("git", arguments: ["checkout", ref], workingDirectory: path); log("");