Add quietDeps and verbose to the JS API (#1353)

To support this, we now run Node-Sass-style relative loads outside of
the Node importer. This allows the evaluator to check whether a
relative load succeeded and use that to determine whether the
stylesheet counts as a dependency.

See sass/sass#3065
This commit is contained in:
Natalie Weizenbaum 2021-06-14 17:41:56 -07:00 committed by GitHub
parent a077094f24
commit 7e371666f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 351 additions and 103 deletions

View File

@ -1,4 +1,4 @@
## 1.34.2 ## 1.35.0
* Fix a couple bugs that could prevent some members from being found in certain * Fix a couple bugs that could prevent some members from being found in certain
files that use a mix of imports and the module system. files that use a mix of imports and the module system.
@ -6,6 +6,14 @@
* Fix incorrect recommendation for migrating division expressions that reference * Fix incorrect recommendation for migrating division expressions that reference
namespaced variables. namespaced variables.
### JS API
* Add a `quietDeps` option which silences compiler warnings from stylesheets
loaded through importers and load paths.
* Add a `verbose` option which causes the compiler to emit all deprecation
warnings, not just 5 per feature.
## 1.34.1 ## 1.34.1
* Fix a bug where `--update` would always compile any file that depends on a * Fix a bug where `--update` would always compile any file that depends on a

View File

@ -102,8 +102,8 @@ class AsyncImportCache {
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed). /// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
/// ///
/// If any importers understand [url], returns that importer as well as the /// If any importers understand [url], returns that importer as well as the
/// canonicalized URL and the original URL resolved relative to [baseUrl] if /// canonicalized URL and the original URL (resolved relative to [baseUrl] if
/// applicable. Otherwise, returns `null`. /// applicable). Otherwise, returns `null`.
Future<Tuple3<AsyncImporter, Uri, Uri>?> canonicalize(Uri url, Future<Tuple3<AsyncImporter, Uri, Uri>?> canonicalize(Uri url,
{AsyncImporter? baseImporter, {AsyncImporter? baseImporter,
Uri? baseUrl, Uri? baseUrl,

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_import_cache.dart. // DO NOT EDIT. This file was generated from async_import_cache.dart.
// See tool/grind/synchronize.dart for details. // See tool/grind/synchronize.dart for details.
// //
// Checksum: 6821c9a63333c3c99b0c9515aa04e73a14e0f141 // Checksum: 27d8582c2ab318a52d433390ec256497b6af5dec
// //
// ignore_for_file: unused_import // ignore_for_file: unused_import
@ -108,8 +108,8 @@ class ImportCache {
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed). /// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
/// ///
/// If any importers understand [url], returns that importer as well as the /// If any importers understand [url], returns that importer as well as the
/// canonicalized URL and the original URL resolved relative to [baseUrl] if /// canonicalized URL and the original URL (resolved relative to [baseUrl] if
/// applicable. Otherwise, returns `null`. /// applicable). Otherwise, returns `null`.
Tuple3<Importer, Uri, Uri>? canonicalize(Uri url, Tuple3<Importer, Uri, Uri>? canonicalize(Uri url,
{Importer? baseImporter, Uri? baseUrl, bool forImport = false}) { {Importer? baseImporter, Uri? baseUrl, bool forImport = false}) {
if (baseImporter != null) { if (baseImporter != null) {

View File

@ -68,18 +68,33 @@ class NodeImporter {
yield* sassPath.split(isWindows ? ';' : ':'); yield* sassPath.split(isWindows ? ';' : ':');
} }
/// Loads the stylesheet at [url]. /// Loads the stylesheet at [url] relative to [previous] if possible.
///
/// This can also load [url] directly if it's an absolute `file:` URL, even if
/// `previous` isn't defined or isn't a `file:` URL.
///
/// Returns the stylesheet at that path and the URL used to load it, or `null`
/// if loading failed.
Tuple2<String, String>? loadRelative(
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);
}
if (previous?.scheme != 'file') return null;
// 1: Filesystem imports relative to the base file.
return _tryPath(
p.join(p.dirname(p.fromUri(previous)), p.fromUri(url)), forImport);
}
/// Loads the stylesheet at [url] from an importer or load path.
/// ///
/// The [previous] URL is the URL of the stylesheet in which the import /// The [previous] URL is the URL of the stylesheet in which the import
/// appeared. Returns the contents of the stylesheet and the URL to use as /// appeared. Returns the contents of the stylesheet and the URL to use as
/// [previous] for imports within the loaded stylesheet. /// [previous] for imports within the loaded stylesheet.
Tuple2<String, String>? load(String url, Uri? previous, bool forImport) { Tuple2<String, String>? load(String url, Uri? previous, bool forImport) {
var parsed = Uri.parse(url);
if (parsed.scheme == '' || parsed.scheme == 'file') {
var result = _resolveRelativePath(p.fromUri(parsed), previous, forImport);
if (result != null) return result;
}
// The previous URL is always an absolute file path for filesystem imports. // The previous URL is always an absolute file path for filesystem imports.
var previousString = _previousToString(previous); var previousString = _previousToString(previous);
for (var importer in _importers) { for (var importer in _importers) {
@ -90,22 +105,17 @@ class NodeImporter {
} }
} }
return _resolveLoadPathFromUrl(parsed, forImport); return _resolveLoadPathFromUrl(Uri.parse(url), forImport);
} }
/// Asynchronously loads the stylesheet at [url]. /// Asynchronously loads the stylesheet at [url] from an importer or load
/// path.
/// ///
/// The [previous] URL is the URL of the stylesheet in which the import /// The [previous] URL is the URL of the stylesheet in which the import
/// appeared. Returns the contents of the stylesheet and the URL to use as /// appeared. Returns the contents of the stylesheet and the URL to use as
/// [previous] for imports within the loaded stylesheet. /// [previous] for imports within the loaded stylesheet.
Future<Tuple2<String, String>?> loadAsync( Future<Tuple2<String, String>?> loadAsync(
String url, Uri? previous, bool forImport) async { String url, Uri? previous, bool forImport) async {
var parsed = Uri.parse(url);
if (parsed.scheme == '' || parsed.scheme == 'file') {
var result = _resolveRelativePath(p.fromUri(parsed), previous, forImport);
if (result != null) return result;
}
// The previous URL is always an absolute file path for filesystem imports. // The previous URL is always an absolute file path for filesystem imports.
var previousString = _previousToString(previous); var previousString = _previousToString(previous);
for (var importer in _importers) { for (var importer in _importers) {
@ -116,20 +126,7 @@ class NodeImporter {
} }
} }
return _resolveLoadPathFromUrl(parsed, forImport); return _resolveLoadPathFromUrl(Uri.parse(url), forImport);
}
/// Tries to load a stylesheet at the given [path] relative to [previous].
///
/// Returns the stylesheet at that path and the URL used to load it, or `null`
/// if loading failed.
Tuple2<String, String>? _resolveRelativePath(
String path, Uri? previous, bool forImport) {
if (p.isAbsolute(path)) return _tryPath(path, forImport);
if (previous?.scheme != 'file') return null;
// 1: Filesystem imports relative to the base file.
return _tryPath(p.join(p.dirname(p.fromUri(previous)), path), forImport);
} }
/// Converts [previous] to a string to pass to the importer function. /// Converts [previous] to a string to pass to the importer function.
@ -192,8 +189,9 @@ class NodeImporter {
} else if (contents != null) { } else if (contents != null) {
return Tuple2(contents, file); return Tuple2(contents, file);
} else { } else {
var resolved = _resolveRelativePath(file, previous, forImport) ?? var resolved =
_resolveLoadPath(file, forImport); loadRelative(p.toUri(file).toString(), previous, forImport) ??
_resolveLoadPath(file, forImport);
if (resolved != null) return resolved; if (resolved != null) return resolved;
throw "Can't find stylesheet to import."; throw "Can't find stylesheet to import.";
} }

View File

@ -8,6 +8,10 @@ class NodeImporter {
NodeImporter(Object options, Iterable<String> includePaths, NodeImporter(Object options, Iterable<String> includePaths,
Iterable<Object> importers); Iterable<Object> importers);
Tuple2<String, String>? loadRelative(
String url, Uri? previous, bool forImport) =>
throw '';
Tuple2<String, String>? load(String url, Uri? previous, bool forImport) => Tuple2<String, String>? load(String url, Uri? previous, bool forImport) =>
throw ''; throw '';

View File

@ -106,6 +106,8 @@ Future<RenderResult> _renderAsync(RenderOptions options) async {
indentWidth: _parseIndentWidth(options.indentWidth), indentWidth: _parseIndentWidth(options.indentWidth),
lineFeed: _parseLineFeed(options.linefeed), lineFeed: _parseLineFeed(options.linefeed),
url: file == null ? 'stdin' : p.toUri(file).toString(), url: file == null ? 'stdin' : p.toUri(file).toString(),
quietDeps: options.quietDeps ?? false,
verbose: options.verbose ?? false,
sourceMap: _enableSourceMaps(options)); sourceMap: _enableSourceMaps(options));
} else if (file != null) { } else if (file != null) {
result = await compileAsync(file, result = await compileAsync(file,
@ -116,6 +118,8 @@ Future<RenderResult> _renderAsync(RenderOptions options) async {
useSpaces: options.indentType != 'tab', useSpaces: options.indentType != 'tab',
indentWidth: _parseIndentWidth(options.indentWidth), indentWidth: _parseIndentWidth(options.indentWidth),
lineFeed: _parseLineFeed(options.linefeed), lineFeed: _parseLineFeed(options.linefeed),
quietDeps: options.quietDeps ?? false,
verbose: options.verbose ?? false,
sourceMap: _enableSourceMaps(options)); sourceMap: _enableSourceMaps(options));
} else { } else {
throw ArgumentError("Either options.data or options.file must be set."); throw ArgumentError("Either options.data or options.file must be set.");
@ -147,6 +151,8 @@ RenderResult _renderSync(RenderOptions options) {
indentWidth: _parseIndentWidth(options.indentWidth), indentWidth: _parseIndentWidth(options.indentWidth),
lineFeed: _parseLineFeed(options.linefeed), lineFeed: _parseLineFeed(options.linefeed),
url: file == null ? 'stdin' : p.toUri(file).toString(), url: file == null ? 'stdin' : p.toUri(file).toString(),
quietDeps: options.quietDeps ?? false,
verbose: options.verbose ?? false,
sourceMap: _enableSourceMaps(options)); sourceMap: _enableSourceMaps(options));
} else if (file != null) { } else if (file != null) {
result = compile(file, result = compile(file,
@ -157,6 +163,8 @@ RenderResult _renderSync(RenderOptions options) {
useSpaces: options.indentType != 'tab', useSpaces: options.indentType != 'tab',
indentWidth: _parseIndentWidth(options.indentWidth), indentWidth: _parseIndentWidth(options.indentWidth),
lineFeed: _parseLineFeed(options.linefeed), lineFeed: _parseLineFeed(options.linefeed),
quietDeps: options.quietDeps ?? false,
verbose: options.verbose ?? false,
sourceMap: _enableSourceMaps(options)); sourceMap: _enableSourceMaps(options));
} else { } else {
throw ArgumentError("Either options.data or options.file must be set."); throw ArgumentError("Either options.data or options.file must be set.");

View File

@ -26,6 +26,8 @@ class RenderOptions {
external bool? get sourceMapContents; external bool? get sourceMapContents;
external bool? get sourceMapEmbed; external bool? get sourceMapEmbed;
external String? get sourceMapRoot; external String? get sourceMapRoot;
external bool? get quietDeps;
external bool? get verbose;
external factory RenderOptions( external factory RenderOptions(
{String? file, {String? file,
@ -44,5 +46,7 @@ class RenderOptions {
Object? sourceMap, Object? sourceMap,
bool? sourceMapContents, bool? sourceMapContents,
bool? sourceMapEmbed, bool? sourceMapEmbed,
String? sourceMapRoot}); String? sourceMapRoot,
bool? quietDeps,
bool? verbose});
} }

View File

@ -157,13 +157,7 @@ class _EvaluateVisitor
/// consoles with redundant warnings. /// consoles with redundant warnings.
final _warningsEmitted = <Tuple2<String, SourceSpan>>{}; final _warningsEmitted = <Tuple2<String, SourceSpan>>{};
// The importer from which the entrypoint stylesheet was loaded.
late final AsyncImporter? _originalImporter;
/// Whether to avoid emitting warnings for files loaded from dependencies. /// Whether to avoid emitting warnings for files loaded from dependencies.
///
/// A "dependency" in this context is any stylesheet loaded through an
/// importer other than [_originalImporter].
final bool _quietDeps; final bool _quietDeps;
/// Whether to track source map information. /// Whether to track source map information.
@ -266,7 +260,7 @@ class _EvaluateVisitor
/// ///
/// A dependency is defined as a stylesheet imported by an importer other than /// A dependency is defined as a stylesheet imported by an importer other than
/// the original. In Node importers, nothing is considered a dependency. /// the original. In Node importers, nothing is considered a dependency.
bool get _inDependency => !_asNodeSass && _importer != _originalImporter; var _inDependency = false;
/// The stylesheet that's currently being evaluated. /// The stylesheet that's currently being evaluated.
Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet"); Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet");
@ -523,7 +517,6 @@ class _EvaluateVisitor
} }
} }
_originalImporter = importer;
var module = await _execute(importer, node); var module = await _execute(importer, node);
return EvaluateResult(_combineCss(module), _includedFiles); return EvaluateResult(_combineCss(module), _includedFiles);
@ -620,8 +613,7 @@ class _EvaluateVisitor
await _withStackFrame(stackFrame, nodeWithSpan, () async { await _withStackFrame(stackFrame, nodeWithSpan, () async {
var result = await _loadStylesheet(url.toString(), nodeWithSpan.span, var result = await _loadStylesheet(url.toString(), nodeWithSpan.span,
baseUrl: baseUrl); baseUrl: baseUrl);
var importer = result.item1; var stylesheet = result.stylesheet;
var stylesheet = result.item2;
var canonicalUrl = stylesheet.span.sourceUrl; var canonicalUrl = stylesheet.span.sourceUrl;
if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) { if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) {
@ -637,14 +629,17 @@ class _EvaluateVisitor
} }
if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan; if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan;
var oldInDependency = _inDependency;
_inDependency = result.isDependency;
Module module; Module module;
try { try {
module = await _execute(importer, stylesheet, module = await _execute(result.importer, stylesheet,
configuration: configuration, configuration: configuration,
nodeWithSpan: nodeWithSpan, nodeWithSpan: nodeWithSpan,
namesInErrors: namesInErrors); namesInErrors: namesInErrors);
} finally { } finally {
_activeModules.remove(canonicalUrl); _activeModules.remove(canonicalUrl);
_inDependency = oldInDependency;
} }
try { try {
@ -1465,8 +1460,7 @@ class _EvaluateVisitor
return _withStackFrame("@import", import, () async { return _withStackFrame("@import", import, () async {
var result = var result =
await _loadStylesheet(import.url, import.span, forImport: true); await _loadStylesheet(import.url, import.span, forImport: true);
var importer = result.item1; var stylesheet = result.stylesheet;
var stylesheet = result.item2;
var url = stylesheet.span.sourceUrl; var url = stylesheet.span.sourceUrl;
if (url != null) { if (url != null) {
@ -1486,7 +1480,7 @@ class _EvaluateVisitor
if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) { if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) {
var oldImporter = _importer; var oldImporter = _importer;
var oldStylesheet = _stylesheet; var oldStylesheet = _stylesheet;
_importer = importer; _importer = result.importer;
_stylesheet = stylesheet; _stylesheet = stylesheet;
await visitStylesheet(stylesheet); await visitStylesheet(stylesheet);
_importer = oldImporter; _importer = oldImporter;
@ -1505,12 +1499,14 @@ class _EvaluateVisitor
var oldEndOfImports = _endOfImports; var oldEndOfImports = _endOfImports;
var oldOutOfOrderImports = _outOfOrderImports; var oldOutOfOrderImports = _outOfOrderImports;
var oldConfiguration = _configuration; var oldConfiguration = _configuration;
_importer = importer; var oldInDependency = _inDependency;
_importer = result.importer;
_stylesheet = stylesheet; _stylesheet = stylesheet;
_root = ModifiableCssStylesheet(stylesheet.span); _root = ModifiableCssStylesheet(stylesheet.span);
_parent = _root; _parent = _root;
_endOfImports = 0; _endOfImports = 0;
_outOfOrderImports = null; _outOfOrderImports = null;
_inDependency = result.isDependency;
// This configuration is only used if it passes through a `@forward` // This configuration is only used if it passes through a `@forward`
// rule, so we avoid creating unnecessary ones for performance reasons. // rule, so we avoid creating unnecessary ones for performance reasons.
@ -1528,6 +1524,7 @@ class _EvaluateVisitor
_endOfImports = oldEndOfImports; _endOfImports = oldEndOfImports;
_outOfOrderImports = oldOutOfOrderImports; _outOfOrderImports = oldOutOfOrderImports;
_configuration = oldConfiguration; _configuration = oldConfiguration;
_inDependency = oldInDependency;
}); });
// Create a dummy module with empty CSS and no extensions to make forwarded // Create a dummy module with empty CSS and no extensions to make forwarded
@ -1559,8 +1556,7 @@ class _EvaluateVisitor
/// ///
/// This first tries loading [url] relative to [baseUrl], which defaults to /// This first tries loading [url] relative to [baseUrl], which defaults to
/// `_stylesheet.span.sourceUrl`. /// `_stylesheet.span.sourceUrl`.
Future<Tuple2<AsyncImporter?, Stylesheet>> _loadStylesheet( Future<_LoadedStylesheet> _loadStylesheet(String url, FileSpan span,
String url, FileSpan span,
{Uri? baseUrl, bool forImport = false}) async { {Uri? baseUrl, bool forImport = false}) async {
try { try {
assert(_importSpan == null); assert(_importSpan == null);
@ -1568,21 +1564,23 @@ class _EvaluateVisitor
var importCache = _importCache; var importCache = _importCache;
if (importCache != null) { if (importCache != null) {
baseUrl ??= _stylesheet.span.sourceUrl;
var tuple = await importCache.canonicalize(Uri.parse(url), var tuple = await importCache.canonicalize(Uri.parse(url),
baseImporter: _importer, baseImporter: _importer, baseUrl: baseUrl, forImport: forImport);
baseUrl: baseUrl ?? _stylesheet.span.sourceUrl,
forImport: forImport);
if (tuple != null) { if (tuple != null) {
var isDependency = _inDependency || tuple.item1 != _importer;
var stylesheet = await importCache.importCanonical( var stylesheet = await importCache.importCanonical(
tuple.item1, tuple.item2, tuple.item1, tuple.item2,
originalUrl: tuple.item3, originalUrl: tuple.item3, quiet: _quietDeps && isDependency);
quiet: _quietDeps && tuple.item1 != _originalImporter); if (stylesheet != null) {
if (stylesheet != null) return Tuple2(tuple.item1, stylesheet); return _LoadedStylesheet(stylesheet,
importer: tuple.item1, isDependency: isDependency);
}
} }
} else { } else {
var stylesheet = await _importLikeNode(url, forImport); var result = await _importLikeNode(url, forImport);
if (stylesheet != null) return Tuple2(null, stylesheet); if (result != null) return result;
} }
if (url.startsWith('package:') && isNode) { if (url.startsWith('package:') && isNode) {
@ -1610,20 +1608,32 @@ class _EvaluateVisitor
/// Imports a stylesheet using [_nodeImporter]. /// Imports a stylesheet using [_nodeImporter].
/// ///
/// Returns the [Stylesheet], or `null` if the import failed. /// Returns the [Stylesheet], or `null` if the import failed.
Future<Stylesheet?> _importLikeNode( Future<_LoadedStylesheet?> _importLikeNode(
String originalUrl, bool forImport) async { String originalUrl, bool forImport) async {
var result = await _nodeImporter! var result = _nodeImporter!
.loadAsync(originalUrl, _stylesheet.span.sourceUrl, forImport); .loadRelative(originalUrl, _stylesheet.span.sourceUrl, forImport);
if (result == null) return null;
bool isDependency;
if (result != null) {
isDependency = _inDependency;
} else {
result = await _nodeImporter!
.loadAsync(originalUrl, _stylesheet.span.sourceUrl, forImport);
if (result == null) return null;
isDependency = true;
}
var contents = result.item1; var contents = result.item1;
var url = result.item2; var url = result.item2;
_includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url); _includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url);
return Stylesheet.parse( return _LoadedStylesheet(
contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, Stylesheet.parse(contents,
url: url, logger: _logger); url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
url: url,
logger: _quietDeps && isDependency ? Logger.quiet : _logger),
isDependency: isDependency);
} }
/// Adds a CSS import for [import]. /// Adds a CSS import for [import].
@ -3341,3 +3351,23 @@ class _ArgumentResults {
_ArgumentResults(this.positional, this.positionalNodes, this.named, _ArgumentResults(this.positional, this.positionalNodes, this.named,
this.namedNodes, this.separator); this.namedNodes, this.separator);
} }
/// The result of loading a stylesheet via [AsyncEvaluator._loadStylesheet].
class _LoadedStylesheet {
/// The stylesheet itself.
final Stylesheet stylesheet;
/// The importer that was used to load the stylesheet.
///
/// This is `null` when running in Node Sass compatibility mode.
final AsyncImporter? importer;
/// Whether this load counts as a dependency.
///
/// That is, whether this was (transitively) loaded through a load path or
/// importer rather than relative to the entrypoint.
final bool isDependency;
_LoadedStylesheet(this.stylesheet,
{this.importer, required this.isDependency});
}

View File

@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart. // DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details. // See tool/grind/synchronize.dart for details.
// //
// Checksum: 24b9012f1cf8908b2cbde11cd10974113d4c8163 // Checksum: 203ba98fd9ca92ccc0b6465fd0d617e88c8dd37e
// //
// ignore_for_file: unused_import // ignore_for_file: unused_import
@ -165,13 +165,7 @@ class _EvaluateVisitor
/// consoles with redundant warnings. /// consoles with redundant warnings.
final _warningsEmitted = <Tuple2<String, SourceSpan>>{}; final _warningsEmitted = <Tuple2<String, SourceSpan>>{};
// The importer from which the entrypoint stylesheet was loaded.
late final Importer? _originalImporter;
/// Whether to avoid emitting warnings for files loaded from dependencies. /// Whether to avoid emitting warnings for files loaded from dependencies.
///
/// A "dependency" in this context is any stylesheet loaded through an
/// importer other than [_originalImporter].
final bool _quietDeps; final bool _quietDeps;
/// Whether to track source map information. /// Whether to track source map information.
@ -274,7 +268,7 @@ class _EvaluateVisitor
/// ///
/// A dependency is defined as a stylesheet imported by an importer other than /// A dependency is defined as a stylesheet imported by an importer other than
/// the original. In Node importers, nothing is considered a dependency. /// the original. In Node importers, nothing is considered a dependency.
bool get _inDependency => !_asNodeSass && _importer != _originalImporter; var _inDependency = false;
/// The stylesheet that's currently being evaluated. /// The stylesheet that's currently being evaluated.
Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet"); Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet");
@ -528,7 +522,6 @@ class _EvaluateVisitor
} }
} }
_originalImporter = importer;
var module = _execute(importer, node); var module = _execute(importer, node);
return EvaluateResult(_combineCss(module), _includedFiles); return EvaluateResult(_combineCss(module), _includedFiles);
@ -625,8 +618,7 @@ class _EvaluateVisitor
_withStackFrame(stackFrame, nodeWithSpan, () { _withStackFrame(stackFrame, nodeWithSpan, () {
var result = var result =
_loadStylesheet(url.toString(), nodeWithSpan.span, baseUrl: baseUrl); _loadStylesheet(url.toString(), nodeWithSpan.span, baseUrl: baseUrl);
var importer = result.item1; var stylesheet = result.stylesheet;
var stylesheet = result.item2;
var canonicalUrl = stylesheet.span.sourceUrl; var canonicalUrl = stylesheet.span.sourceUrl;
if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) { if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) {
@ -642,14 +634,17 @@ class _EvaluateVisitor
} }
if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan; if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan;
var oldInDependency = _inDependency;
_inDependency = result.isDependency;
Module<Callable> module; Module<Callable> module;
try { try {
module = _execute(importer, stylesheet, module = _execute(result.importer, stylesheet,
configuration: configuration, configuration: configuration,
nodeWithSpan: nodeWithSpan, nodeWithSpan: nodeWithSpan,
namesInErrors: namesInErrors); namesInErrors: namesInErrors);
} finally { } finally {
_activeModules.remove(canonicalUrl); _activeModules.remove(canonicalUrl);
_inDependency = oldInDependency;
} }
try { try {
@ -1464,8 +1459,7 @@ class _EvaluateVisitor
void _visitDynamicImport(DynamicImport import) { void _visitDynamicImport(DynamicImport import) {
return _withStackFrame("@import", import, () { return _withStackFrame("@import", import, () {
var result = _loadStylesheet(import.url, import.span, forImport: true); var result = _loadStylesheet(import.url, import.span, forImport: true);
var importer = result.item1; var stylesheet = result.stylesheet;
var stylesheet = result.item2;
var url = stylesheet.span.sourceUrl; var url = stylesheet.span.sourceUrl;
if (url != null) { if (url != null) {
@ -1485,7 +1479,7 @@ class _EvaluateVisitor
if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) { if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) {
var oldImporter = _importer; var oldImporter = _importer;
var oldStylesheet = _stylesheet; var oldStylesheet = _stylesheet;
_importer = importer; _importer = result.importer;
_stylesheet = stylesheet; _stylesheet = stylesheet;
visitStylesheet(stylesheet); visitStylesheet(stylesheet);
_importer = oldImporter; _importer = oldImporter;
@ -1504,12 +1498,14 @@ class _EvaluateVisitor
var oldEndOfImports = _endOfImports; var oldEndOfImports = _endOfImports;
var oldOutOfOrderImports = _outOfOrderImports; var oldOutOfOrderImports = _outOfOrderImports;
var oldConfiguration = _configuration; var oldConfiguration = _configuration;
_importer = importer; var oldInDependency = _inDependency;
_importer = result.importer;
_stylesheet = stylesheet; _stylesheet = stylesheet;
_root = ModifiableCssStylesheet(stylesheet.span); _root = ModifiableCssStylesheet(stylesheet.span);
_parent = _root; _parent = _root;
_endOfImports = 0; _endOfImports = 0;
_outOfOrderImports = null; _outOfOrderImports = null;
_inDependency = result.isDependency;
// This configuration is only used if it passes through a `@forward` // This configuration is only used if it passes through a `@forward`
// rule, so we avoid creating unnecessary ones for performance reasons. // rule, so we avoid creating unnecessary ones for performance reasons.
@ -1527,6 +1523,7 @@ class _EvaluateVisitor
_endOfImports = oldEndOfImports; _endOfImports = oldEndOfImports;
_outOfOrderImports = oldOutOfOrderImports; _outOfOrderImports = oldOutOfOrderImports;
_configuration = oldConfiguration; _configuration = oldConfiguration;
_inDependency = oldInDependency;
}); });
// Create a dummy module with empty CSS and no extensions to make forwarded // Create a dummy module with empty CSS and no extensions to make forwarded
@ -1558,7 +1555,7 @@ class _EvaluateVisitor
/// ///
/// This first tries loading [url] relative to [baseUrl], which defaults to /// This first tries loading [url] relative to [baseUrl], which defaults to
/// `_stylesheet.span.sourceUrl`. /// `_stylesheet.span.sourceUrl`.
Tuple2<Importer?, Stylesheet> _loadStylesheet(String url, FileSpan span, _LoadedStylesheet _loadStylesheet(String url, FileSpan span,
{Uri? baseUrl, bool forImport = false}) { {Uri? baseUrl, bool forImport = false}) {
try { try {
assert(_importSpan == null); assert(_importSpan == null);
@ -1566,20 +1563,22 @@ class _EvaluateVisitor
var importCache = _importCache; var importCache = _importCache;
if (importCache != null) { if (importCache != null) {
baseUrl ??= _stylesheet.span.sourceUrl;
var tuple = importCache.canonicalize(Uri.parse(url), var tuple = importCache.canonicalize(Uri.parse(url),
baseImporter: _importer, baseImporter: _importer, baseUrl: baseUrl, forImport: forImport);
baseUrl: baseUrl ?? _stylesheet.span.sourceUrl,
forImport: forImport);
if (tuple != null) { if (tuple != null) {
var isDependency = _inDependency || tuple.item1 != _importer;
var stylesheet = importCache.importCanonical(tuple.item1, tuple.item2, var stylesheet = importCache.importCanonical(tuple.item1, tuple.item2,
originalUrl: tuple.item3, originalUrl: tuple.item3, quiet: _quietDeps && isDependency);
quiet: _quietDeps && tuple.item1 != _originalImporter); if (stylesheet != null) {
if (stylesheet != null) return Tuple2(tuple.item1, stylesheet); return _LoadedStylesheet(stylesheet,
importer: tuple.item1, isDependency: isDependency);
}
} }
} else { } else {
var stylesheet = _importLikeNode(url, forImport); var result = _importLikeNode(url, forImport);
if (stylesheet != null) return Tuple2(null, stylesheet); if (result != null) return result;
} }
if (url.startsWith('package:') && isNode) { if (url.startsWith('package:') && isNode) {
@ -1607,19 +1606,31 @@ class _EvaluateVisitor
/// Imports a stylesheet using [_nodeImporter]. /// Imports a stylesheet using [_nodeImporter].
/// ///
/// Returns the [Stylesheet], or `null` if the import failed. /// Returns the [Stylesheet], or `null` if the import failed.
Stylesheet? _importLikeNode(String originalUrl, bool forImport) { _LoadedStylesheet? _importLikeNode(String originalUrl, bool forImport) {
var result = var result = _nodeImporter!
_nodeImporter!.load(originalUrl, _stylesheet.span.sourceUrl, forImport); .loadRelative(originalUrl, _stylesheet.span.sourceUrl, forImport);
if (result == null) return null;
bool isDependency;
if (result != null) {
isDependency = _inDependency;
} else {
result = _nodeImporter!
.load(originalUrl, _stylesheet.span.sourceUrl, forImport);
if (result == null) return null;
isDependency = true;
}
var contents = result.item1; var contents = result.item1;
var url = result.item2; var url = result.item2;
_includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url); _includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url);
return Stylesheet.parse( return _LoadedStylesheet(
contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, Stylesheet.parse(contents,
url: url, logger: _logger); url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
url: url,
logger: _quietDeps && isDependency ? Logger.quiet : _logger),
isDependency: isDependency);
} }
/// Adds a CSS import for [import]. /// Adds a CSS import for [import].
@ -3278,3 +3289,23 @@ class _ArgumentResults {
_ArgumentResults(this.positional, this.positionalNodes, this.named, _ArgumentResults(this.positional, this.positionalNodes, this.named,
this.namedNodes, this.separator); this.namedNodes, this.separator);
} }
/// The result of loading a stylesheet via [Evaluator._loadStylesheet].
class _LoadedStylesheet {
/// The stylesheet itself.
final Stylesheet stylesheet;
/// The importer that was used to load the stylesheet.
///
/// This is `null` when running in Node Sass compatibility mode.
final Importer? importer;
/// Whether this load counts as a dependency.
///
/// That is, whether this was (transitively) loaded through a load path or
/// importer rather than relative to the entrypoint.
final bool isDependency;
_LoadedStylesheet(this.stylesheet,
{this.importer, required this.isDependency});
}

View File

@ -1,5 +1,5 @@
name: sass name: sass
version: 1.34.2-dev version: 1.35.0
description: A Sass implementation in Dart. description: A Sass implementation in Dart.
author: Sass Team author: Sass Team
homepage: https://github.com/sass/dart-sass homepage: https://github.com/sass/dart-sass

View File

@ -264,6 +264,171 @@ a {
renderSync(RenderOptions(data: "@debug 'what the heck'")), isEmpty); renderSync(RenderOptions(data: "@debug 'what the heck'")), isEmpty);
}); });
group("with quietDeps", () {
group("in a relative load from the entrypoint", () {
test("emits @warn", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await writeTextFile(p.join(sandbox, "_other.scss"), "@warn heck");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("heck")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"), quietDeps: true));
});
test("emits @debug", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await writeTextFile(p.join(sandbox, "_other.scss"), "@debug heck");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("heck")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"), quietDeps: true));
});
test("emits parser warnings", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await writeTextFile(p.join(sandbox, "_other.scss"), "a {b: c && d}");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("&&")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"), quietDeps: true));
});
test("emits runner warnings", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await writeTextFile(p.join(sandbox, "_other.scss"), "#{blue} {x: y}");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("blue")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"), quietDeps: true));
});
});
group("in a load path load", () {
test("emits @warn", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await createDirectory(p.join(sandbox, "dir"));
await writeTextFile(
p.join(sandbox, "dir", "_other.scss"), "@warn heck");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("heck")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"),
includePaths: [p.join(sandbox, "dir")],
quietDeps: true));
});
test("emits @debug", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await createDirectory(p.join(sandbox, "dir"));
await writeTextFile(
p.join(sandbox, "dir", "_other.scss"), "@debug heck");
expect(const LineSplitter().bind(interceptStderr()),
emitsThrough(contains("heck")));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"),
includePaths: [p.join(sandbox, "dir")],
quietDeps: true));
});
test("doesn't emit parser warnings", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await createDirectory(p.join(sandbox, "dir"));
await writeTextFile(
p.join(sandbox, "dir", "_other.scss"), "a {b: c && d}");
// No stderr should be printed at all.
const LineSplitter()
.bind(interceptStderr())
.listen(expectAsync1((_) {}, count: 0));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"),
includePaths: [p.join(sandbox, "dir")],
quietDeps: true));
// Give stderr a chance to be piped through if it's going to be.
await pumpEventQueue();
});
test("doesn't emit runner warnings", () async {
await writeTextFile(p.join(sandbox, "test.scss"), "@use 'other'");
await createDirectory(p.join(sandbox, "dir"));
await writeTextFile(
p.join(sandbox, "dir", "_other.scss"), "#{blue} {x: y}");
// No stderr should be printed at all.
const LineSplitter()
.bind(interceptStderr())
.listen(expectAsync1((_) {}, count: 0));
renderSync(RenderOptions(
file: p.join(sandbox, "test.scss"),
includePaths: [p.join(sandbox, "dir")],
quietDeps: true));
// Give stderr a chance to be piped through if it's going to be.
await pumpEventQueue();
});
});
});
group("with a bunch of deprecation warnings", () {
setUp(() async {
await writeTextFile(p.join(sandbox, "test.scss"), r"""
$_: call("inspect", null);
$_: call("rgb", 0, 0, 0);
$_: call("nth", null, 1);
$_: call("join", null, null);
$_: call("if", true, 1, 2);
$_: call("hsl", 0, 100%, 100%);
$_: 1/2;
$_: 1/3;
$_: 1/4;
$_: 1/5;
$_: 1/6;
$_: 1/7;
""");
});
test("without --verbose, only prints five", () async {
expect(
const LineSplitter().bind(interceptStderr()),
emitsInOrder([
...List.filled(5, emitsThrough(contains("call()"))),
...List.filled(5, emitsThrough(contains("math.div"))),
emitsThrough(
contains("2 repetitive deprecation warnings omitted."))
]));
renderSync(RenderOptions(file: p.join(sandbox, "test.scss")));
});
test("with --verbose, prints all", () async {
expect(
const LineSplitter().bind(interceptStderr()),
emitsInOrder([
...List.filled(6, emitsThrough(contains("call()"))),
...List.filled(6, emitsThrough(contains("math.div")))
]));
renderSync(
RenderOptions(file: p.join(sandbox, "test.scss"), verbose: true));
});
});
group("with both data and file", () { group("with both data and file", () {
test("uses the data parameter as the source", () { test("uses the data parameter as the source", () {
expect(renderSync(RenderOptions(data: "x {y: z}", file: sassPath)), expect(renderSync(RenderOptions(data: "x {y: z}", file: sassPath)),