mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
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:
parent
a077094f24
commit
7e371666f4
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,4 +1,4 @@
|
||||
## 1.34.2
|
||||
## 1.35.0
|
||||
|
||||
* 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.
|
||||
@ -6,6 +6,14 @@
|
||||
* Fix incorrect recommendation for migrating division expressions that reference
|
||||
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
|
||||
|
||||
* Fix a bug where `--update` would always compile any file that depends on a
|
||||
|
@ -102,8 +102,8 @@ class AsyncImportCache {
|
||||
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
|
||||
///
|
||||
/// 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`.
|
||||
/// canonicalized URL and the original URL (resolved relative to [baseUrl] if
|
||||
/// applicable). Otherwise, returns `null`.
|
||||
Future<Tuple3<AsyncImporter, Uri, Uri>?> canonicalize(Uri url,
|
||||
{AsyncImporter? baseImporter,
|
||||
Uri? baseUrl,
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_import_cache.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 6821c9a63333c3c99b0c9515aa04e73a14e0f141
|
||||
// Checksum: 27d8582c2ab318a52d433390ec256497b6af5dec
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -108,8 +108,8 @@ class ImportCache {
|
||||
/// canonicalize [url] (resolved relative to [baseUrl] if it's passed).
|
||||
///
|
||||
/// 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`.
|
||||
/// canonicalized URL and the original URL (resolved relative to [baseUrl] if
|
||||
/// applicable). Otherwise, returns `null`.
|
||||
Tuple3<Importer, Uri, Uri>? canonicalize(Uri url,
|
||||
{Importer? baseImporter, Uri? baseUrl, bool forImport = false}) {
|
||||
if (baseImporter != null) {
|
||||
|
@ -68,18 +68,33 @@ class NodeImporter {
|
||||
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
|
||||
/// appeared. Returns the contents of the stylesheet and the URL to use as
|
||||
/// [previous] for imports within the loaded stylesheet.
|
||||
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.
|
||||
var previousString = _previousToString(previous);
|
||||
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
|
||||
/// appeared. Returns the contents of the stylesheet and the URL to use as
|
||||
/// [previous] for imports within the loaded stylesheet.
|
||||
Future<Tuple2<String, String>?> loadAsync(
|
||||
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.
|
||||
var previousString = _previousToString(previous);
|
||||
for (var importer in _importers) {
|
||||
@ -116,20 +126,7 @@ class NodeImporter {
|
||||
}
|
||||
}
|
||||
|
||||
return _resolveLoadPathFromUrl(parsed, 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);
|
||||
return _resolveLoadPathFromUrl(Uri.parse(url), forImport);
|
||||
}
|
||||
|
||||
/// Converts [previous] to a string to pass to the importer function.
|
||||
@ -192,7 +189,8 @@ class NodeImporter {
|
||||
} else if (contents != null) {
|
||||
return Tuple2(contents, file);
|
||||
} else {
|
||||
var resolved = _resolveRelativePath(file, previous, forImport) ??
|
||||
var resolved =
|
||||
loadRelative(p.toUri(file).toString(), previous, forImport) ??
|
||||
_resolveLoadPath(file, forImport);
|
||||
if (resolved != null) return resolved;
|
||||
throw "Can't find stylesheet to import.";
|
||||
|
@ -8,6 +8,10 @@ class NodeImporter {
|
||||
NodeImporter(Object options, Iterable<String> includePaths,
|
||||
Iterable<Object> importers);
|
||||
|
||||
Tuple2<String, String>? loadRelative(
|
||||
String url, Uri? previous, bool forImport) =>
|
||||
throw '';
|
||||
|
||||
Tuple2<String, String>? load(String url, Uri? previous, bool forImport) =>
|
||||
throw '';
|
||||
|
||||
|
@ -106,6 +106,8 @@ Future<RenderResult> _renderAsync(RenderOptions options) async {
|
||||
indentWidth: _parseIndentWidth(options.indentWidth),
|
||||
lineFeed: _parseLineFeed(options.linefeed),
|
||||
url: file == null ? 'stdin' : p.toUri(file).toString(),
|
||||
quietDeps: options.quietDeps ?? false,
|
||||
verbose: options.verbose ?? false,
|
||||
sourceMap: _enableSourceMaps(options));
|
||||
} else if (file != null) {
|
||||
result = await compileAsync(file,
|
||||
@ -116,6 +118,8 @@ Future<RenderResult> _renderAsync(RenderOptions options) async {
|
||||
useSpaces: options.indentType != 'tab',
|
||||
indentWidth: _parseIndentWidth(options.indentWidth),
|
||||
lineFeed: _parseLineFeed(options.linefeed),
|
||||
quietDeps: options.quietDeps ?? false,
|
||||
verbose: options.verbose ?? false,
|
||||
sourceMap: _enableSourceMaps(options));
|
||||
} else {
|
||||
throw ArgumentError("Either options.data or options.file must be set.");
|
||||
@ -147,6 +151,8 @@ RenderResult _renderSync(RenderOptions options) {
|
||||
indentWidth: _parseIndentWidth(options.indentWidth),
|
||||
lineFeed: _parseLineFeed(options.linefeed),
|
||||
url: file == null ? 'stdin' : p.toUri(file).toString(),
|
||||
quietDeps: options.quietDeps ?? false,
|
||||
verbose: options.verbose ?? false,
|
||||
sourceMap: _enableSourceMaps(options));
|
||||
} else if (file != null) {
|
||||
result = compile(file,
|
||||
@ -157,6 +163,8 @@ RenderResult _renderSync(RenderOptions options) {
|
||||
useSpaces: options.indentType != 'tab',
|
||||
indentWidth: _parseIndentWidth(options.indentWidth),
|
||||
lineFeed: _parseLineFeed(options.linefeed),
|
||||
quietDeps: options.quietDeps ?? false,
|
||||
verbose: options.verbose ?? false,
|
||||
sourceMap: _enableSourceMaps(options));
|
||||
} else {
|
||||
throw ArgumentError("Either options.data or options.file must be set.");
|
||||
|
@ -26,6 +26,8 @@ class RenderOptions {
|
||||
external bool? get sourceMapContents;
|
||||
external bool? get sourceMapEmbed;
|
||||
external String? get sourceMapRoot;
|
||||
external bool? get quietDeps;
|
||||
external bool? get verbose;
|
||||
|
||||
external factory RenderOptions(
|
||||
{String? file,
|
||||
@ -44,5 +46,7 @@ class RenderOptions {
|
||||
Object? sourceMap,
|
||||
bool? sourceMapContents,
|
||||
bool? sourceMapEmbed,
|
||||
String? sourceMapRoot});
|
||||
String? sourceMapRoot,
|
||||
bool? quietDeps,
|
||||
bool? verbose});
|
||||
}
|
||||
|
@ -157,13 +157,7 @@ class _EvaluateVisitor
|
||||
/// consoles with redundant warnings.
|
||||
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.
|
||||
///
|
||||
/// A "dependency" in this context is any stylesheet loaded through an
|
||||
/// importer other than [_originalImporter].
|
||||
final bool _quietDeps;
|
||||
|
||||
/// 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
|
||||
/// 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.
|
||||
Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet");
|
||||
@ -523,7 +517,6 @@ class _EvaluateVisitor
|
||||
}
|
||||
}
|
||||
|
||||
_originalImporter = importer;
|
||||
var module = await _execute(importer, node);
|
||||
|
||||
return EvaluateResult(_combineCss(module), _includedFiles);
|
||||
@ -620,8 +613,7 @@ class _EvaluateVisitor
|
||||
await _withStackFrame(stackFrame, nodeWithSpan, () async {
|
||||
var result = await _loadStylesheet(url.toString(), nodeWithSpan.span,
|
||||
baseUrl: baseUrl);
|
||||
var importer = result.item1;
|
||||
var stylesheet = result.item2;
|
||||
var stylesheet = result.stylesheet;
|
||||
|
||||
var canonicalUrl = stylesheet.span.sourceUrl;
|
||||
if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) {
|
||||
@ -637,14 +629,17 @@ class _EvaluateVisitor
|
||||
}
|
||||
if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan;
|
||||
|
||||
var oldInDependency = _inDependency;
|
||||
_inDependency = result.isDependency;
|
||||
Module module;
|
||||
try {
|
||||
module = await _execute(importer, stylesheet,
|
||||
module = await _execute(result.importer, stylesheet,
|
||||
configuration: configuration,
|
||||
nodeWithSpan: nodeWithSpan,
|
||||
namesInErrors: namesInErrors);
|
||||
} finally {
|
||||
_activeModules.remove(canonicalUrl);
|
||||
_inDependency = oldInDependency;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1465,8 +1460,7 @@ class _EvaluateVisitor
|
||||
return _withStackFrame("@import", import, () async {
|
||||
var result =
|
||||
await _loadStylesheet(import.url, import.span, forImport: true);
|
||||
var importer = result.item1;
|
||||
var stylesheet = result.item2;
|
||||
var stylesheet = result.stylesheet;
|
||||
|
||||
var url = stylesheet.span.sourceUrl;
|
||||
if (url != null) {
|
||||
@ -1486,7 +1480,7 @@ class _EvaluateVisitor
|
||||
if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) {
|
||||
var oldImporter = _importer;
|
||||
var oldStylesheet = _stylesheet;
|
||||
_importer = importer;
|
||||
_importer = result.importer;
|
||||
_stylesheet = stylesheet;
|
||||
await visitStylesheet(stylesheet);
|
||||
_importer = oldImporter;
|
||||
@ -1505,12 +1499,14 @@ class _EvaluateVisitor
|
||||
var oldEndOfImports = _endOfImports;
|
||||
var oldOutOfOrderImports = _outOfOrderImports;
|
||||
var oldConfiguration = _configuration;
|
||||
_importer = importer;
|
||||
var oldInDependency = _inDependency;
|
||||
_importer = result.importer;
|
||||
_stylesheet = stylesheet;
|
||||
_root = ModifiableCssStylesheet(stylesheet.span);
|
||||
_parent = _root;
|
||||
_endOfImports = 0;
|
||||
_outOfOrderImports = null;
|
||||
_inDependency = result.isDependency;
|
||||
|
||||
// This configuration is only used if it passes through a `@forward`
|
||||
// rule, so we avoid creating unnecessary ones for performance reasons.
|
||||
@ -1528,6 +1524,7 @@ class _EvaluateVisitor
|
||||
_endOfImports = oldEndOfImports;
|
||||
_outOfOrderImports = oldOutOfOrderImports;
|
||||
_configuration = oldConfiguration;
|
||||
_inDependency = oldInDependency;
|
||||
});
|
||||
|
||||
// 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
|
||||
/// `_stylesheet.span.sourceUrl`.
|
||||
Future<Tuple2<AsyncImporter?, Stylesheet>> _loadStylesheet(
|
||||
String url, FileSpan span,
|
||||
Future<_LoadedStylesheet> _loadStylesheet(String url, FileSpan span,
|
||||
{Uri? baseUrl, bool forImport = false}) async {
|
||||
try {
|
||||
assert(_importSpan == null);
|
||||
@ -1568,21 +1564,23 @@ class _EvaluateVisitor
|
||||
|
||||
var importCache = _importCache;
|
||||
if (importCache != null) {
|
||||
baseUrl ??= _stylesheet.span.sourceUrl;
|
||||
var tuple = await importCache.canonicalize(Uri.parse(url),
|
||||
baseImporter: _importer,
|
||||
baseUrl: baseUrl ?? _stylesheet.span.sourceUrl,
|
||||
forImport: forImport);
|
||||
baseImporter: _importer, baseUrl: baseUrl, forImport: forImport);
|
||||
|
||||
if (tuple != null) {
|
||||
var isDependency = _inDependency || tuple.item1 != _importer;
|
||||
var stylesheet = await importCache.importCanonical(
|
||||
tuple.item1, tuple.item2,
|
||||
originalUrl: tuple.item3,
|
||||
quiet: _quietDeps && tuple.item1 != _originalImporter);
|
||||
if (stylesheet != null) return Tuple2(tuple.item1, stylesheet);
|
||||
originalUrl: tuple.item3, quiet: _quietDeps && isDependency);
|
||||
if (stylesheet != null) {
|
||||
return _LoadedStylesheet(stylesheet,
|
||||
importer: tuple.item1, isDependency: isDependency);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var stylesheet = await _importLikeNode(url, forImport);
|
||||
if (stylesheet != null) return Tuple2(null, stylesheet);
|
||||
var result = await _importLikeNode(url, forImport);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
if (url.startsWith('package:') && isNode) {
|
||||
@ -1610,20 +1608,32 @@ class _EvaluateVisitor
|
||||
/// Imports a stylesheet using [_nodeImporter].
|
||||
///
|
||||
/// Returns the [Stylesheet], or `null` if the import failed.
|
||||
Future<Stylesheet?> _importLikeNode(
|
||||
Future<_LoadedStylesheet?> _importLikeNode(
|
||||
String originalUrl, bool forImport) async {
|
||||
var result = await _nodeImporter!
|
||||
var result = _nodeImporter!
|
||||
.loadRelative(originalUrl, _stylesheet.span.sourceUrl, forImport);
|
||||
|
||||
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 url = result.item2;
|
||||
|
||||
_includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url);
|
||||
|
||||
return Stylesheet.parse(
|
||||
contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
|
||||
url: url, logger: _logger);
|
||||
return _LoadedStylesheet(
|
||||
Stylesheet.parse(contents,
|
||||
url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
|
||||
url: url,
|
||||
logger: _quietDeps && isDependency ? Logger.quiet : _logger),
|
||||
isDependency: isDependency);
|
||||
}
|
||||
|
||||
/// Adds a CSS import for [import].
|
||||
@ -3341,3 +3351,23 @@ class _ArgumentResults {
|
||||
_ArgumentResults(this.positional, this.positionalNodes, this.named,
|
||||
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});
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
// DO NOT EDIT. This file was generated from async_evaluate.dart.
|
||||
// See tool/grind/synchronize.dart for details.
|
||||
//
|
||||
// Checksum: 24b9012f1cf8908b2cbde11cd10974113d4c8163
|
||||
// Checksum: 203ba98fd9ca92ccc0b6465fd0d617e88c8dd37e
|
||||
//
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
@ -165,13 +165,7 @@ class _EvaluateVisitor
|
||||
/// consoles with redundant warnings.
|
||||
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.
|
||||
///
|
||||
/// A "dependency" in this context is any stylesheet loaded through an
|
||||
/// importer other than [_originalImporter].
|
||||
final bool _quietDeps;
|
||||
|
||||
/// 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
|
||||
/// 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.
|
||||
Stylesheet get _stylesheet => _assertInModule(__stylesheet, "_stylesheet");
|
||||
@ -528,7 +522,6 @@ class _EvaluateVisitor
|
||||
}
|
||||
}
|
||||
|
||||
_originalImporter = importer;
|
||||
var module = _execute(importer, node);
|
||||
|
||||
return EvaluateResult(_combineCss(module), _includedFiles);
|
||||
@ -625,8 +618,7 @@ class _EvaluateVisitor
|
||||
_withStackFrame(stackFrame, nodeWithSpan, () {
|
||||
var result =
|
||||
_loadStylesheet(url.toString(), nodeWithSpan.span, baseUrl: baseUrl);
|
||||
var importer = result.item1;
|
||||
var stylesheet = result.item2;
|
||||
var stylesheet = result.stylesheet;
|
||||
|
||||
var canonicalUrl = stylesheet.span.sourceUrl;
|
||||
if (canonicalUrl != null && _activeModules.containsKey(canonicalUrl)) {
|
||||
@ -642,14 +634,17 @@ class _EvaluateVisitor
|
||||
}
|
||||
if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan;
|
||||
|
||||
var oldInDependency = _inDependency;
|
||||
_inDependency = result.isDependency;
|
||||
Module<Callable> module;
|
||||
try {
|
||||
module = _execute(importer, stylesheet,
|
||||
module = _execute(result.importer, stylesheet,
|
||||
configuration: configuration,
|
||||
nodeWithSpan: nodeWithSpan,
|
||||
namesInErrors: namesInErrors);
|
||||
} finally {
|
||||
_activeModules.remove(canonicalUrl);
|
||||
_inDependency = oldInDependency;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1464,8 +1459,7 @@ class _EvaluateVisitor
|
||||
void _visitDynamicImport(DynamicImport import) {
|
||||
return _withStackFrame("@import", import, () {
|
||||
var result = _loadStylesheet(import.url, import.span, forImport: true);
|
||||
var importer = result.item1;
|
||||
var stylesheet = result.item2;
|
||||
var stylesheet = result.stylesheet;
|
||||
|
||||
var url = stylesheet.span.sourceUrl;
|
||||
if (url != null) {
|
||||
@ -1485,7 +1479,7 @@ class _EvaluateVisitor
|
||||
if (stylesheet.uses.isEmpty && stylesheet.forwards.isEmpty) {
|
||||
var oldImporter = _importer;
|
||||
var oldStylesheet = _stylesheet;
|
||||
_importer = importer;
|
||||
_importer = result.importer;
|
||||
_stylesheet = stylesheet;
|
||||
visitStylesheet(stylesheet);
|
||||
_importer = oldImporter;
|
||||
@ -1504,12 +1498,14 @@ class _EvaluateVisitor
|
||||
var oldEndOfImports = _endOfImports;
|
||||
var oldOutOfOrderImports = _outOfOrderImports;
|
||||
var oldConfiguration = _configuration;
|
||||
_importer = importer;
|
||||
var oldInDependency = _inDependency;
|
||||
_importer = result.importer;
|
||||
_stylesheet = stylesheet;
|
||||
_root = ModifiableCssStylesheet(stylesheet.span);
|
||||
_parent = _root;
|
||||
_endOfImports = 0;
|
||||
_outOfOrderImports = null;
|
||||
_inDependency = result.isDependency;
|
||||
|
||||
// This configuration is only used if it passes through a `@forward`
|
||||
// rule, so we avoid creating unnecessary ones for performance reasons.
|
||||
@ -1527,6 +1523,7 @@ class _EvaluateVisitor
|
||||
_endOfImports = oldEndOfImports;
|
||||
_outOfOrderImports = oldOutOfOrderImports;
|
||||
_configuration = oldConfiguration;
|
||||
_inDependency = oldInDependency;
|
||||
});
|
||||
|
||||
// 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
|
||||
/// `_stylesheet.span.sourceUrl`.
|
||||
Tuple2<Importer?, Stylesheet> _loadStylesheet(String url, FileSpan span,
|
||||
_LoadedStylesheet _loadStylesheet(String url, FileSpan span,
|
||||
{Uri? baseUrl, bool forImport = false}) {
|
||||
try {
|
||||
assert(_importSpan == null);
|
||||
@ -1566,20 +1563,22 @@ class _EvaluateVisitor
|
||||
|
||||
var importCache = _importCache;
|
||||
if (importCache != null) {
|
||||
baseUrl ??= _stylesheet.span.sourceUrl;
|
||||
var tuple = importCache.canonicalize(Uri.parse(url),
|
||||
baseImporter: _importer,
|
||||
baseUrl: baseUrl ?? _stylesheet.span.sourceUrl,
|
||||
forImport: forImport);
|
||||
baseImporter: _importer, baseUrl: baseUrl, forImport: forImport);
|
||||
|
||||
if (tuple != null) {
|
||||
var isDependency = _inDependency || tuple.item1 != _importer;
|
||||
var stylesheet = importCache.importCanonical(tuple.item1, tuple.item2,
|
||||
originalUrl: tuple.item3,
|
||||
quiet: _quietDeps && tuple.item1 != _originalImporter);
|
||||
if (stylesheet != null) return Tuple2(tuple.item1, stylesheet);
|
||||
originalUrl: tuple.item3, quiet: _quietDeps && isDependency);
|
||||
if (stylesheet != null) {
|
||||
return _LoadedStylesheet(stylesheet,
|
||||
importer: tuple.item1, isDependency: isDependency);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var stylesheet = _importLikeNode(url, forImport);
|
||||
if (stylesheet != null) return Tuple2(null, stylesheet);
|
||||
var result = _importLikeNode(url, forImport);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
if (url.startsWith('package:') && isNode) {
|
||||
@ -1607,19 +1606,31 @@ class _EvaluateVisitor
|
||||
/// Imports a stylesheet using [_nodeImporter].
|
||||
///
|
||||
/// Returns the [Stylesheet], or `null` if the import failed.
|
||||
Stylesheet? _importLikeNode(String originalUrl, bool forImport) {
|
||||
var result =
|
||||
_nodeImporter!.load(originalUrl, _stylesheet.span.sourceUrl, forImport);
|
||||
_LoadedStylesheet? _importLikeNode(String originalUrl, bool forImport) {
|
||||
var result = _nodeImporter!
|
||||
.loadRelative(originalUrl, _stylesheet.span.sourceUrl, forImport);
|
||||
|
||||
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 url = result.item2;
|
||||
|
||||
_includedFiles.add(url.startsWith('file:') ? p.fromUri(url) : url);
|
||||
|
||||
return Stylesheet.parse(
|
||||
contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
|
||||
url: url, logger: _logger);
|
||||
return _LoadedStylesheet(
|
||||
Stylesheet.parse(contents,
|
||||
url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss,
|
||||
url: url,
|
||||
logger: _quietDeps && isDependency ? Logger.quiet : _logger),
|
||||
isDependency: isDependency);
|
||||
}
|
||||
|
||||
/// Adds a CSS import for [import].
|
||||
@ -3278,3 +3289,23 @@ class _ArgumentResults {
|
||||
_ArgumentResults(this.positional, this.positionalNodes, this.named,
|
||||
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});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: sass
|
||||
version: 1.34.2-dev
|
||||
version: 1.35.0
|
||||
description: A Sass implementation in Dart.
|
||||
author: Sass Team
|
||||
homepage: https://github.com/sass/dart-sass
|
||||
|
@ -264,6 +264,171 @@ a {
|
||||
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", () {
|
||||
test("uses the data parameter as the source", () {
|
||||
expect(renderSync(RenderOptions(data: "x {y: z}", file: sassPath)),
|
||||
|
Loading…
Reference in New Issue
Block a user