From 43b69e60a071d1e2f5041338460d1b71b8f0aa92 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 13 Jul 2021 17:15:22 -0700 Subject: [PATCH] Change CompileResult.includedFiles to CompileResult.includedUrls Rather than constructing this explicitly to match Node Sass's API, we construct it with canonical URLs and convert it into the format expected by the Node Sass API (a mix of paths and URLs) in the compatibility layer. --- CHANGELOG.md | 19 ++ lib/sass.dart | 317 +++++++++++++++------ lib/src/async_compile.dart | 29 +- lib/src/compile.dart | 6 +- lib/src/compile_result.dart | 35 +++ lib/src/executable/compile_stylesheet.dart | 1 + lib/src/importer/node/implementation.dart | 5 +- lib/src/node.dart | 6 +- lib/src/visitor/async_evaluate.dart | 36 +-- lib/src/visitor/evaluate.dart | 28 +- pubspec.yaml | 2 +- test/cli/shared/source_maps.dart | 13 +- test/dart_api/importer_test.dart | 37 +-- test/dart_api_test.dart | 74 +++++ tool/grind/synchronize.dart | 3 +- 15 files changed, 420 insertions(+), 191 deletions(-) create mode 100644 lib/src/compile_result.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 3096f4dc..d6dfbdf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 1.36.0 + +### Dart API + +* Added `compileToResult()`, `compileStringToResult()`, + `compileToResultAsync()`, and `compileStringToResultAsync()` methods. These + are intended to replace the existing `compile*()` methods, which are now + deprecated. Rather than returning a simple string, these return a + `CompileResult` object, which will allow us to add additional information + about the compilation without having to introduce further deprecations. + + * Instead of passing a `sourceMaps` callback to `compile*()`, pass + `sourceMaps: true` to `compile*ToResult()` and access + `CompileResult.sourceMap`. + + * The `CompileResult` object exposes a `includedUrls` object which lists the + canonical URLs accessed during a compilation. This information was + previously unavailable except through the JS API. + ## 1.35.2 * **Potentially breaking bug fix**: Properly throw an error for Unicode ranges diff --git a/lib/sass.dart b/lib/sass.dart index 10619293..8f73c120 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -11,6 +11,7 @@ import 'package:source_maps/source_maps.dart'; import 'src/async_import_cache.dart'; import 'src/callable.dart'; import 'src/compile.dart' as c; +import 'src/compile_result.dart'; import 'src/exception.dart'; import 'src/import_cache.dart'; import 'src/importer.dart'; @@ -20,6 +21,7 @@ import 'src/util/nullable.dart'; import 'src/visitor/serialize.dart'; export 'src/callable.dart' show Callable, AsyncCallable; +export 'src/compile_result.dart'; export 'src/exception.dart' show SassException; export 'src/importer.dart'; export 'src/logger.dart'; @@ -28,7 +30,9 @@ export 'src/value.dart'; export 'src/visitor/serialize.dart' show OutputStyle; export 'src/warn.dart' show warn; -/// Loads the Sass file at [path], compiles it to CSS, and returns the result. +/// Loads the Sass file at [path], compiles it to CSS, and returns a +/// [CompileResult] containing the CSS and additional metadata about the +/// compilation. /// /// If [color] is `true`, this will use terminal colors in warnings. It's /// ignored if [logger] is passed. @@ -67,12 +71,13 @@ export 'src/warn.dart' show warn; /// times, further warnings for that feature are silenced. If [verbose] is true, /// all deprecation warnings are printed instead. /// -/// If [sourceMap] is passed, it's passed a [SingleMapping] that indicates which -/// sections of the source file(s) correspond to which in the resulting CSS. -/// It's called immediately before this method returns, and only if compilation -/// succeeds. Note that [SingleMapping.targetUrl] will always be `null`. Users -/// using the [SourceMap] API should be sure to add the [`source_maps`][] -/// package to their pubspec. +/// If [sourceMap] is `true`, [CompileResult.sourceMap] will be set to a +/// [SingleMapping] that indicates which sections of the source file(s) +/// correspond to which in the resulting CSS. [SingleMapping.targetUrl] will be +/// `null`. It's up to the caller to save this mapping to disk and add a source +/// map comment to [CompileResult.css] pointing to it. Users using the +/// [SourceMap] API should be sure to add the [`source_maps`][] package to their +/// pubspec. /// /// [`source_maps`]: https://pub.dartlang.org/packages/source_maps /// @@ -83,46 +88,35 @@ export 'src/warn.dart' show warn; /// /// [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 /// -/// This parameter is meant to be used as an out parameter, so that users who -/// want access to the source map can get it. For example: -/// -/// ```dart -/// SingleMapping sourceMap; -/// var css = compile(sassPath, sourceMap: (map) => sourceMap = map); -/// ``` -/// /// Throws a [SassException] if conversion fails. -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, - void sourceMap(SingleMapping map)?, - bool charset = true}) { - logger ??= Logger.stderr(color: color); - var result = c.compile(path, - logger: logger, - importCache: ImportCache( - importers: importers, - logger: logger, - loadPaths: loadPaths, - packageConfig: packageConfig), - functions: functions, - style: style, - quietDeps: quietDeps, - verbose: verbose, - sourceMap: sourceMap != null, - charset: charset); - result.sourceMap.andThen(sourceMap); - return result.css; -} +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}) => + c.compile(path, + logger: logger, + importCache: ImportCache( + importers: importers, + logger: logger ?? Logger.stderr(color: color), + loadPaths: loadPaths, + packageConfig: packageConfig), + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset); -/// Compiles [source] to CSS and returns the result. +/// Compiles [source] to CSS and returns a [CompileResult] containing the CSS +/// and additional metadata about the compilation.. /// /// This parses the stylesheet as [syntax], which defaults to [Syntax.scss]. /// @@ -167,12 +161,13 @@ String compile(String path, /// times, further warnings for that feature are silenced. If [verbose] is true, /// all deprecation warnings are printed instead. /// -/// If [sourceMap] is passed, it's passed a [SingleMapping] that indicates which -/// sections of the source file(s) correspond to which in the resulting CSS. -/// It's called immediately before this method returns, and only if compilation -/// succeeds. Note that [SingleMapping.targetUrl] will always be `null`. Users -/// using the [SourceMap] API should be sure to add the [`source_maps`][] -/// package to their pubspec. +/// If [sourceMap] is `true`, [CompileResult.sourceMap] will be set to a +/// [SingleMapping] that indicates which sections of the source file(s) +/// correspond to which in the resulting CSS. [SingleMapping.targetUrl] will be +/// `null`. It's up to the caller to save this mapping to disk and add a source +/// map comment to [CompileResult.css] pointing to it. Users using the +/// [SourceMap] API should be sure to add the [`source_maps`][] package to their +/// pubspec. /// /// [`source_maps`]: https://pub.dartlang.org/packages/source_maps /// @@ -183,6 +178,117 @@ String compile(String path, /// /// [byte-order mark]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 /// +/// Throws a [SassException] if conversion fails. +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}) => + c.compileString(source, + syntax: syntax, + logger: logger, + importCache: ImportCache( + importers: importers, + logger: logger ?? Logger.stderr(color: color), + packageConfig: packageConfig, + loadPaths: loadPaths), + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset); + +/// 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}) => + c.compileAsync(path, + logger: logger, + importCache: AsyncImportCache( + importers: importers, + logger: logger ?? Logger.stderr(color: color), + loadPaths: loadPaths, + packageConfig: packageConfig), + functions: functions, + style: style, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap); + +/// Like [compileStringToResult], 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 [compileStringToResult] should be preferred if possible. +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}) => + c.compileStringAsync(source, + syntax: syntax, + logger: logger, + importCache: AsyncImportCache( + importers: importers, + logger: logger ?? Logger.stderr(color: color), + packageConfig: packageConfig, + loadPaths: loadPaths), + functions: functions, + style: style, + importer: importer, + url: url, + quietDeps: quietDeps, + verbose: verbose, + sourceMap: sourceMap, + charset: charset); + +/// Like [compileToResult], but returns [CompileResult.css] rather than +/// returning [CompileResult] directly. +/// +/// If [sourceMap] is passed, it's passed a [SingleMapping] that indicates which +/// sections of the source file(s) correspond to which in the resulting CSS. +/// It's called immediately before this method returns, and only if compilation +/// succeeds. Note that [SingleMapping.targetUrl] will always be `null`. Users +/// using the [SourceMap] API should be sure to add the [`source_maps`][] +/// package to their pubspec. +/// +/// [`source_maps`]: https://pub.dartlang.org/packages/source_maps +/// /// This parameter is meant to be used as an out parameter, so that users who /// want access to the source map can get it. For example: /// @@ -190,9 +296,58 @@ String compile(String path, /// SingleMapping sourceMap; /// var css = compile(sassPath, sourceMap: (map) => sourceMap = map); /// ``` +@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); + result.sourceMap.andThen(sourceMap); + return result.css; +} + +/// Like [compileStringToResult], but returns [CompileResult.css] rather than +/// returning [CompileResult] directly. /// -/// Throws a [SassException] if conversion fails. -String compileString(String source, +/// If [sourceMap] is passed, it's passed a [SingleMapping] that indicates which +/// sections of the source file(s) correspond to which in the resulting CSS. +/// It's called immediately before this method returns, and only if compilation +/// succeeds. Note that [SingleMapping.targetUrl] will always be `null`. Users +/// using the [SourceMap] API should be sure to add the [`source_maps`][] +/// package to their pubspec. +/// +/// [`source_maps`]: https://pub.dartlang.org/packages/source_maps +/// +/// This parameter is meant to be used as an out parameter, so that users who +/// want access to the source map can get it. For example: +/// +/// ```dart +/// SingleMapping sourceMap; +/// var css = compileString(sass, sourceMap: (map) => sourceMap = map); +/// ``` +@Deprecated("Use compileStringToResult() instead.") +String compileString( + String source, {Syntax? syntax, bool color = false, Logger? logger, @@ -205,18 +360,17 @@ String compileString(String source, Object? url, bool quietDeps = false, bool verbose = false, - void sourceMap(SingleMapping map)?, + @Deprecated("Use CompileResult.sourceMap from compileStringToResult() instead.") + void sourceMap(SingleMapping map)?, bool charset = true, - @Deprecated("Use syntax instead.") bool indented = false}) { - logger ??= Logger.stderr(color: color); - var result = c.compileString(source, + @Deprecated("Use syntax instead.") + bool indented = false}) { + var result = compileStringToResult(source, syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), logger: logger, - importCache: ImportCache( - importers: importers, - logger: logger, - packageConfig: packageConfig, - loadPaths: loadPaths), + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, functions: functions, style: style, importer: importer, @@ -234,7 +388,9 @@ String compileString(String source, /// Running asynchronously allows this to take [AsyncImporter]s rather than /// synchronous [Importer]s. However, running asynchronously is also somewhat /// slower, so [compile] should be preferred if possible. -Future compileAsync(String path, +@Deprecated("Use compileToResultAsync() instead.") +Future compileAsync( + String path, {bool color = false, Logger? logger, Iterable? importers, @@ -244,15 +400,13 @@ Future compileAsync(String path, OutputStyle? style, bool quietDeps = false, bool verbose = false, - void sourceMap(SingleMapping map)?}) async { - logger ??= Logger.stderr(color: color); - var result = await c.compileAsync(path, + @Deprecated("Use CompileResult.sourceMap from compileToResultAsync() instead.") + void sourceMap(SingleMapping map)?}) async { + var result = await compileToResultAsync(path, logger: logger, - importCache: AsyncImportCache( - importers: importers, - logger: logger, - loadPaths: loadPaths, - packageConfig: packageConfig), + importers: importers, + loadPaths: loadPaths, + packageConfig: packageConfig, functions: functions, style: style, quietDeps: quietDeps, @@ -267,7 +421,9 @@ Future compileAsync(String path, /// Running asynchronously allows this to take [AsyncImporter]s rather than /// synchronous [Importer]s. However, running asynchronously is also somewhat /// slower, so [compileString] should be preferred if possible. -Future compileStringAsync(String source, +@Deprecated("Use compileStringToResultAsync() instead.") +Future compileStringAsync( + String source, {Syntax? syntax, bool color = false, Logger? logger, @@ -280,18 +436,17 @@ Future compileStringAsync(String source, Object? url, bool quietDeps = false, bool verbose = false, - void sourceMap(SingleMapping map)?, + @Deprecated("Use CompileResult.sourceMap from compileStringToResultAsync() instead.") + void sourceMap(SingleMapping map)?, bool charset = true, - @Deprecated("Use syntax instead.") bool indented = false}) async { - logger ??= Logger.stderr(color: color); - var result = await c.compileStringAsync(source, + @Deprecated("Use syntax instead.") + bool indented = false}) async { + var result = await compileStringToResultAsync(source, syntax: syntax ?? (indented ? Syntax.sass : Syntax.scss), logger: logger, - importCache: AsyncImportCache( - importers: importers, - logger: logger, - packageConfig: packageConfig, - loadPaths: loadPaths), + importers: importers, + packageConfig: packageConfig, + loadPaths: loadPaths, functions: functions, style: style, importer: importer, diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index 4a7cc6d3..5b7541c5 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -5,11 +5,11 @@ import 'dart:convert'; import 'package:path/path.dart' as p; -import 'package:source_maps/source_maps.dart'; import 'ast/sass.dart'; import 'async_import_cache.dart'; import 'callable.dart'; +import 'compile_result.dart'; import 'importer.dart'; import 'importer/node.dart'; import 'io.dart'; @@ -171,30 +171,3 @@ Future _compileStylesheet( return CompileResult(evaluateResult, serializeResult); } - -/// The result of compiling a Sass document to CSS, along with metadata about -/// the compilation process. -class CompileResult { - /// The result of evaluating the source file. - final EvaluateResult _evaluate; - - /// The result of serializing the CSS AST to CSS text. - final SerializeResult _serialize; - - /// The compiled CSS. - String get css => _serialize.css; - - /// The source map indicating how the source files map to [css]. - /// - /// This is `null` if source mapping was disabled for this compilation. - SingleMapping? get sourceMap => _serialize.sourceMap; - - /// The set that will eventually populate the JS API's - /// `result.stats.includedFiles` field. - /// - /// For filesystem imports, this contains the import path. For all other - /// imports, it contains the URL passed to the `@import`. - Set get includedFiles => _evaluate.includedFiles; - - CompileResult(this._evaluate, this._serialize); -} diff --git a/lib/src/compile.dart b/lib/src/compile.dart index adc07280..d7a43fe3 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,22 +5,20 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 8e813f2ead6e78899ce820e279983278809a7ea5 +// Checksum: 1a1251aa9f7312612a64760f59803568bd09a07c // // ignore_for_file: unused_import -import 'async_compile.dart'; export 'async_compile.dart'; import 'dart:convert'; import 'package:path/path.dart' as p; -import 'package:source_maps/source_maps.dart'; -import 'package:source_span/source_span.dart'; import 'ast/sass.dart'; import 'import_cache.dart'; import 'callable.dart'; +import 'compile_result.dart'; import 'importer.dart'; import 'importer/node.dart'; import 'io.dart'; diff --git a/lib/src/compile_result.dart b/lib/src/compile_result.dart new file mode 100644 index 00000000..c85d510c --- /dev/null +++ b/lib/src/compile_result.dart @@ -0,0 +1,35 @@ +// Copyright 2021 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:meta/meta.dart'; +import 'package:source_maps/source_maps.dart'; + +import 'visitor/async_evaluate.dart'; +import 'visitor/serialize.dart'; + +/// The result of compiling a Sass document to CSS, along with metadata about +/// the compilation process. +@sealed +class CompileResult { + /// The result of evaluating the source file. + final EvaluateResult _evaluate; + + /// The result of serializing the CSS AST to CSS text. + final SerializeResult _serialize; + + /// The compiled CSS. + String get css => _serialize.css; + + /// The source map indicating how the source files map to [css]. + /// + /// This is `null` if source mapping was disabled for this compilation. + SingleMapping? get sourceMap => _serialize.sourceMap; + + /// The canonical URLs of all stylesheets loaded during compilation. + Set get includedUrls => _evaluate.includedUrls; + + /// @nodoc + @internal + CompileResult(this._evaluate, this._serialize); +} diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 23a90121..f9e61cb0 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -9,6 +9,7 @@ import 'package:source_maps/source_maps.dart'; import '../async_import_cache.dart'; import '../compile.dart'; +import '../compile_result.dart'; import '../exception.dart'; import '../importer/filesystem.dart'; import '../io.dart'; diff --git a/lib/src/importer/node/implementation.dart b/lib/src/importer/node/implementation.dart index 2c350be7..e6446620 100644 --- a/lib/src/importer/node/implementation.dart +++ b/lib/src/importer/node/implementation.dart @@ -27,9 +27,8 @@ import '../utils.dart'; /// imported by a different importer. /// /// * Importers can return file paths rather than the contents of the imported -/// file. These paths are made absolute before they're included in -/// [EvaluateResult.includedFiles] or passed as the previous "URL" to other -/// importers. +/// file. These paths are made absolute before they'repassed as the previous +/// "URL" to other importers. /// /// * The working directory is always implicitly an include path. /// diff --git a/lib/src/node.dart b/lib/src/node.dart index f37ff474..7f7d40d2 100644 --- a/lib/src/node.dart +++ b/lib/src/node.dart @@ -15,6 +15,7 @@ import 'package:tuple/tuple.dart'; import 'ast/sass.dart'; import 'callable.dart'; import 'compile.dart'; +import 'compile_result.dart'; import 'exception.dart'; import 'io.dart'; import 'importer/node.dart'; @@ -408,7 +409,10 @@ RenderResult _newRenderResult( start: start.millisecondsSinceEpoch, end: end.millisecondsSinceEpoch, duration: end.difference(start).inMilliseconds, - includedFiles: result.includedFiles.toList())); + includedFiles: [ + for (var url in result.includedUrls) + if (url.scheme == 'file') p.fromUri(url) else url.toString() + ])); } /// Returns whether source maps are enabled by [options]. diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 4b3d7340..5d4e8f01 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -216,12 +216,8 @@ class _EvaluateVisitor /// Whether we're currently building the output of a `@keyframes` rule. var _inKeyframes = false; - /// The set that will eventually populate the JS API's - /// `result.stats.includedFiles` field. - /// - /// For filesystem imports, this contains the import path. For all other - /// imports, it contains the URL passed to the `@import`. - final _includedFiles = {}; + /// The canonical URLs of all stylesheets loaded during compilation. + final _includedUrls = {}; /// A map from canonical URLs for modules (or imported files) that are /// currently being evaluated to AST nodes whose spans indicate the original @@ -508,18 +504,12 @@ class _EvaluateVisitor var url = node.span.sourceUrl; if (url != null) { _activeModules[url] = null; - if (_asNodeSass) { - if (url.scheme == 'file') { - _includedFiles.add(p.fromUri(url)); - } else if (url.toString() != 'stdin') { - _includedFiles.add(url.toString()); - } - } + if (!(_asNodeSass && url.toString() == 'stdin')) _includedUrls.add(url); } var module = await _execute(importer, node); - return EvaluateResult(_combineCss(module), _includedFiles); + return EvaluateResult(_combineCss(module), _includedUrls); }); } @@ -1577,13 +1567,17 @@ class _EvaluateVisitor tuple.item1, tuple.item2, originalUrl: tuple.item3, quiet: _quietDeps && isDependency); if (stylesheet != null) { + _includedUrls.add(tuple.item2); return _LoadedStylesheet(stylesheet, importer: tuple.item1, isDependency: isDependency); } } } else { var result = await _importLikeNode(url, forImport); - if (result != null) return result; + if (result != null) { + result.stylesheet.span.sourceUrl.andThen(_includedUrls.add); + return result; + } } if (url.startsWith('package:') && isNode) { @@ -1629,8 +1623,6 @@ class _EvaluateVisitor var contents = result.item1; var url = result.item2; - _includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url); - return _LoadedStylesheet( Stylesheet.parse(contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, @@ -3316,14 +3308,10 @@ class EvaluateResult { /// The CSS syntax tree. final CssStylesheet stylesheet; - /// The set that will eventually populate the JS API's - /// `result.stats.includedFiles` field. - /// - /// For filesystem imports, this contains the import path. For all other - /// imports, it contains the URL passed to the `@import`. - final Set includedFiles; + /// The canonical URLs of all stylesheets loaded during compilation. + final Set includedUrls; - EvaluateResult(this.stylesheet, this.includedFiles); + EvaluateResult(this.stylesheet, this.includedUrls); } /// The result of evaluating arguments to a function or mixin. diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 34296235..5b1d6316 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: b2321a00031707d2df699e6888a334deba39995d +// Checksum: 5e03597bf64a5e03b483e64e4d35e4e86288bc10 // // ignore_for_file: unused_import @@ -224,12 +224,8 @@ class _EvaluateVisitor /// Whether we're currently building the output of a `@keyframes` rule. var _inKeyframes = false; - /// The set that will eventually populate the JS API's - /// `result.stats.includedFiles` field. - /// - /// For filesystem imports, this contains the import path. For all other - /// imports, it contains the URL passed to the `@import`. - final _includedFiles = {}; + /// The canonical URLs of all stylesheets loaded during compilation. + final _includedUrls = {}; /// A map from canonical URLs for modules (or imported files) that are /// currently being evaluated to AST nodes whose spans indicate the original @@ -513,18 +509,12 @@ class _EvaluateVisitor var url = node.span.sourceUrl; if (url != null) { _activeModules[url] = null; - if (_asNodeSass) { - if (url.scheme == 'file') { - _includedFiles.add(p.fromUri(url)); - } else if (url.toString() != 'stdin') { - _includedFiles.add(url.toString()); - } - } + if (!(_asNodeSass && url.toString() == 'stdin')) _includedUrls.add(url); } var module = _execute(importer, node); - return EvaluateResult(_combineCss(module), _includedFiles); + return EvaluateResult(_combineCss(module), _includedUrls); }); } @@ -1575,13 +1565,17 @@ class _EvaluateVisitor var stylesheet = importCache.importCanonical(tuple.item1, tuple.item2, originalUrl: tuple.item3, quiet: _quietDeps && isDependency); if (stylesheet != null) { + _includedUrls.add(tuple.item2); return _LoadedStylesheet(stylesheet, importer: tuple.item1, isDependency: isDependency); } } } else { var result = _importLikeNode(url, forImport); - if (result != null) return result; + if (result != null) { + result.stylesheet.span.sourceUrl.andThen(_includedUrls.add); + return result; + } } if (url.startsWith('package:') && isNode) { @@ -1626,8 +1620,6 @@ class _EvaluateVisitor var contents = result.item1; var url = result.item2; - _includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url); - return _LoadedStylesheet( Stylesheet.parse(contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, diff --git a/pubspec.yaml b/pubspec.yaml index 140e349a..eada8ad4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.35.2 +version: 1.36.0 description: A Sass implementation in Dart. author: Sass Team homepage: https://github.com/sass/dart-sass diff --git a/test/cli/shared/source_maps.dart b/test/cli/shared/source_maps.dart index 09d9a8b8..647f6aa1 100644 --- a/test/cli/shared/source_maps.dart +++ b/test/cli/shared/source_maps.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:path/path.dart' as p; -import 'package:source_maps/source_maps.dart'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; import 'package:test_process/test_process.dart'; @@ -35,9 +34,9 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("contains mappings", () { - late SingleMapping sourceMap; - sass.compileString("a {b: 1 + 2}", sourceMap: (map) => sourceMap = map); - expect(map, containsPair("mappings", sourceMap.toJson()["mappings"])); + var result = sass.compileStringToResult("a {b: 1 + 2}"); + expect(map, + containsPair("mappings", result.sourceMap!.toJson()["mappings"])); }); }); @@ -288,9 +287,9 @@ void sharedTests(Future runSass(Iterable arguments)) { }); test("contains mappings in the generated CSS", () { - late SingleMapping sourceMap; - sass.compileString("a {b: 1 + 2}", sourceMap: (map) => sourceMap = map); - expect(map, containsPair("mappings", sourceMap.toJson()["mappings"])); + var result = sass.compileStringToResult("a {b: 1 + 2}"); + expect(map, + containsPair("mappings", result.sourceMap!.toJson()["mappings"])); }); test("refers to the source file", () { diff --git a/test/dart_api/importer_test.dart b/test/dart_api/importer_test.dart index efbebb06..641c59d1 100644 --- a/test/dart_api/importer_test.dart +++ b/test/dart_api/importer_test.dart @@ -6,7 +6,6 @@ import 'dart:convert'; -import 'package:source_maps/source_maps.dart'; import 'package:test/test.dart'; import 'package:sass/sass.dart'; @@ -115,33 +114,27 @@ void main() { }); test("uses an importer's source map URL", () { - late SingleMapping map; - compileString('@import "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: (map_) => map = map_); + var result = compileStringToResult('@import "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); + }) + ]); - expect(map.urls, contains("u:blue")); + expect(result.sourceMap!.urls, contains("u:blue")); }); test("uses a data: source map URL if the importer doesn't provide one", () { - late SingleMapping map; - compileString('@import "orange";', - importers: [ - TestImporter((url) => Uri.parse("u:$url"), (url) { - var color = url.path; - return ImporterResult('.$color {color: $color}', indented: false); - }) - ], - sourceMap: (map_) => map = map_); + var result = compileStringToResult('@import "orange";', importers: [ + TestImporter((url) => Uri.parse("u:$url"), (url) { + var color = url.path; + return ImporterResult('.$color {color: $color}', indented: false); + }) + ]); expect( - map.urls, + result.sourceMap!.urls, contains(Uri.dataFromString(".orange {color: orange}", encoding: utf8) .toString())); }); diff --git a/test/dart_api_test.dart b/test/dart_api_test.dart index ec7e8644..93638ae3 100644 --- a/test/dart_api_test.dart +++ b/test/dart_api_test.dart @@ -226,6 +226,80 @@ a { }); }); + group("includedUrls", () { + group("contains the entrypoint's URL", () { + group("in compileStringToResult()", () { + test("if it's given", () { + var result = compileStringToResult("a {b: c}", url: "source.scss"); + expect(result.includedUrls, equals([Uri.parse("source.scss")])); + }); + + test("unless it's not given", () { + var result = compileStringToResult("a {b: c}"); + expect(result.includedUrls, isEmpty); + }); + }); + + test("in compileToResult()", () async { + await d.file("input.scss", "a {b: c};").create(); + var result = compileToResult(d.path('input.scss')); + expect(result.includedUrls, equals([p.toUri(d.path('input.scss'))])); + }); + }); + + 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')); + expect(result.includedUrls, contains(p.toUri(d.path('_other.scss')))); + }); + + test("contains a URL loaded via @use", () async { + await d.file("_other.scss", "a {b: c}").create(); + await d.file("input.scss", "@use 'other';").create(); + var result = compileToResult(d.path('input.scss')); + expect(result.includedUrls, contains(p.toUri(d.path('_other.scss')))); + }); + + test("contains a URL loaded via @forward", () async { + await d.file("_other.scss", "a {b: c}").create(); + await d.file("input.scss", "@forward 'other';").create(); + var result = compileToResult(d.path('input.scss')); + expect(result.includedUrls, contains(p.toUri(d.path('_other.scss')))); + }); + + test("contains a URL loaded via @meta.load-css()", () async { + await d.file("_other.scss", "a {b: c}").create(); + await d.file("input.scss", """ + @use 'sass:meta'; + @include meta.load-css('other'); + """).create(); + var result = compileToResult(d.path('input.scss')); + expect(result.includedUrls, contains(p.toUri(d.path('_other.scss')))); + }); + + test("contains a URL loaded via a chain of loads", () async { + await d.file("_jupiter.scss", "a {b: c}").create(); + await d.file("_mars.scss", "@forward 'jupiter';").create(); + await d.file("_earth.scss", "@import 'mars';").create(); + await d.file("_venus.scss", "@use 'earth';").create(); + await d.file("mercury.scss", """ + @use 'sass:meta'; + @include meta.load-css('venus'); + """).create(); + var result = compileToResult(d.path('mercury.scss')); + expect( + result.includedUrls, + 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(); diff --git a/tool/grind/synchronize.dart b/tool/grind/synchronize.dart index 452e16df..9cb3c409 100644 --- a/tool/grind/synchronize.dart +++ b/tool/grind/synchronize.dart @@ -27,7 +27,7 @@ final sources = const { /// Classes that are defined in the async version of a file and used as-is in /// the sync version, and thus should not be copied over. -final _sharedClasses = const ['EvaluateResult', 'CompileResult']; +final _sharedClasses = const ['EvaluateResult']; /// This is how we support both synchronous and asynchronous compilation modes. /// @@ -97,7 +97,6 @@ class _Visitor extends RecursiveAstVisitor { _buffer.writeln(); } else if (p.basename(path) == 'async_compile.dart') { _buffer.writeln(); - _buffer.writeln("import 'async_compile.dart';"); _buffer.writeln("export 'async_compile.dart';"); _buffer.writeln(); }