From 60d440a60dfba602cba2d88f2f292faed8784339 Mon Sep 17 00:00:00 2001 From: Jennifer Thakar Date: Fri, 18 Oct 2024 15:28:30 -0700 Subject: [PATCH] Emit all warnings in the evaluator (#2396) --- CHANGELOG.md | 7 + bin/sass.dart | 12 +- lib/sass.dart | 12 -- lib/src/ast/css/media_query.dart | 6 +- lib/src/ast/sass/argument_declaration.dart | 6 +- lib/src/ast/sass/at_root_query.dart | 5 +- lib/src/ast/sass/expression.dart | 5 +- lib/src/ast/sass/statement/stylesheet.dart | 45 +++-- lib/src/ast/sass/statement/use_rule.dart | 5 +- .../sass/statement/variable_declaration.dart | 6 +- lib/src/ast/selector/complex.dart | 6 +- lib/src/ast/selector/compound.dart | 6 +- lib/src/ast/selector/list.dart | 3 - lib/src/ast/selector/simple.dart | 6 +- lib/src/async_compile.dart | 7 +- lib/src/async_import_cache.dart | 65 ++---- lib/src/compile.dart | 9 +- lib/src/executable/compile_stylesheet.dart | 8 +- lib/src/executable/repl.dart | 34 +++- lib/src/import_cache.dart | 66 ++----- lib/src/js/parser.dart | 23 +-- lib/src/parse/at_root_query.dart | 3 +- lib/src/parse/css.dart | 2 +- lib/src/parse/keyframe_selector.dart | 3 +- lib/src/parse/media_query.dart | 3 +- lib/src/parse/parser.dart | 24 +-- lib/src/parse/sass.dart | 2 +- lib/src/parse/scss.dart | 19 +- lib/src/parse/selector.dart | 1 - lib/src/parse/stylesheet.dart | 186 ++++++++++-------- lib/src/visitor/async_evaluate.dart | 37 ++-- lib/src/visitor/evaluate.dart | 39 ++-- pkg/sass-parser/CHANGELOG.md | 5 + pkg/sass-parser/lib/index.ts | 8 +- pkg/sass-parser/lib/src/sass-internal.ts | 7 +- pkg/sass-parser/package.json | 2 +- pkg/sass_api/CHANGELOG.md | 25 ++- pkg/sass_api/lib/sass_api.dart | 7 +- pkg/sass_api/pubspec.yaml | 4 +- pubspec.yaml | 2 +- test/dart_api/logger_test.dart | 4 +- test/deprecations_test.dart | 2 +- test/embedded/protocol_test.dart | 9 +- 43 files changed, 340 insertions(+), 396 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0f81854..3b93fabaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.80.3 + +* Improve consistency of how warnings are emitted by different parts of the + compiler. This should result in minimal user-visible changes, but different + types of warnings should now respond more reliably to flags like `--quiet`, + `--verbose`, and `--silence-deprecation`. + ## 1.80.2 * Fix a bug where deprecation warnings were incorrectly emitted for the diff --git a/bin/sass.dart b/bin/sass.dart index bd5684d25..78ac31370 100644 --- a/bin/sass.dart +++ b/bin/sass.dart @@ -44,14 +44,14 @@ Future main(List args) async { return; } + // Eagerly check these so that we fail here and don't hang in watch mode. + options.silenceDeprecations; + options.futureDeprecations; + options.fatalDeprecations; + var graph = StylesheetGraph(ImportCache( importers: [...options.pkgImporters, FilesystemImporter.noLoadPath], - loadPaths: options.loadPaths, - logger: ImportCache.wrapLogger( - options.logger, - options.silenceDeprecations, - options.fatalDeprecations, - options.futureDeprecations))); + loadPaths: options.loadPaths)); if (options.watch) { await watch(options, graph); return; diff --git a/lib/sass.dart b/lib/sass.dart index 39f14f7b1..19c6f8f87 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -120,9 +120,6 @@ CompileResult compileToResult(String path, logger: logger, importCache: ImportCache( importers: importers, - logger: ImportCache.wrapLogger(logger, silenceDeprecations, - fatalDeprecations, futureDeprecations, - color: color), loadPaths: loadPaths, packageConfig: packageConfig), functions: functions, @@ -224,9 +221,6 @@ CompileResult compileStringToResult(String source, logger: logger, importCache: ImportCache( importers: importers, - logger: ImportCache.wrapLogger(logger, silenceDeprecations, - fatalDeprecations, futureDeprecations, - color: color), packageConfig: packageConfig, loadPaths: loadPaths), functions: functions, @@ -265,9 +259,6 @@ Future compileToResultAsync(String path, logger: logger, importCache: AsyncImportCache( importers: importers, - logger: AsyncImportCache.wrapLogger(logger, silenceDeprecations, - fatalDeprecations, futureDeprecations, - color: color), loadPaths: loadPaths, packageConfig: packageConfig), functions: functions, @@ -310,9 +301,6 @@ Future compileStringToResultAsync(String source, logger: logger, importCache: AsyncImportCache( importers: importers, - logger: AsyncImportCache.wrapLogger(logger, silenceDeprecations, - fatalDeprecations, futureDeprecations, - color: color), packageConfig: packageConfig, loadPaths: loadPaths), functions: functions, diff --git a/lib/src/ast/css/media_query.dart b/lib/src/ast/css/media_query.dart index dc2d5d532..bad97ada6 100644 --- a/lib/src/ast/css/media_query.dart +++ b/lib/src/ast/css/media_query.dart @@ -3,7 +3,6 @@ // https://opensource.org/licenses/MIT. import '../../interpolation_map.dart'; -import '../../logger.dart'; import '../../parse/media_query.dart'; import '../../utils.dart'; @@ -44,9 +43,8 @@ final class CssMediaQuery { /// /// Throws a [SassFormatException] if parsing fails. static List parseList(String contents, - {Object? url, Logger? logger, InterpolationMap? interpolationMap}) => - MediaQueryParser(contents, - url: url, logger: logger, interpolationMap: interpolationMap) + {Object? url, InterpolationMap? interpolationMap}) => + MediaQueryParser(contents, url: url, interpolationMap: interpolationMap) .parse(); /// Creates a media query specifies a type and, optionally, conditions. diff --git a/lib/src/ast/sass/argument_declaration.dart b/lib/src/ast/sass/argument_declaration.dart index 7ab73c3a5..ed1951cad 100644 --- a/lib/src/ast/sass/argument_declaration.dart +++ b/lib/src/ast/sass/argument_declaration.dart @@ -5,7 +5,6 @@ import 'package:source_span/source_span.dart'; import '../../exception.dart'; -import '../../logger.dart'; import '../../parse/scss.dart'; import '../../util/character.dart'; import '../../util/span.dart'; @@ -71,9 +70,8 @@ final class ArgumentDeclaration implements SassNode { /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory ArgumentDeclaration.parse(String contents, - {Object? url, Logger? logger}) => - ScssParser(contents, url: url, logger: logger).parseArgumentDeclaration(); + factory ArgumentDeclaration.parse(String contents, {Object? url}) => + ScssParser(contents, url: url).parseArgumentDeclaration(); /// Throws a [SassScriptException] if [positional] and [names] aren't valid /// for this argument declaration. diff --git a/lib/src/ast/sass/at_root_query.dart b/lib/src/ast/sass/at_root_query.dart index 58b2e2f94..0ed522c39 100644 --- a/lib/src/ast/sass/at_root_query.dart +++ b/lib/src/ast/sass/at_root_query.dart @@ -7,7 +7,6 @@ import 'package:collection/collection.dart'; import '../../exception.dart'; import '../../interpolation_map.dart'; -import '../../logger.dart'; import '../../parse/at_root_query.dart'; import '../css.dart'; @@ -58,8 +57,8 @@ final class AtRootQuery { /// /// Throws a [SassFormatException] if parsing fails. factory AtRootQuery.parse(String contents, - {Object? url, Logger? logger, InterpolationMap? interpolationMap}) => - AtRootQueryParser(contents, url: url, logger: logger).parse(); + {Object? url, InterpolationMap? interpolationMap}) => + AtRootQueryParser(contents, url: url).parse(); /// Returns whether `this` excludes [node]. /// diff --git a/lib/src/ast/sass/expression.dart b/lib/src/ast/sass/expression.dart index 6a54fd26c..ad0fff946 100644 --- a/lib/src/ast/sass/expression.dart +++ b/lib/src/ast/sass/expression.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import '../../exception.dart'; -import '../../logger.dart'; import '../../parse/scss.dart'; import '../../visitor/interface/expression.dart'; import '../../visitor/is_calculation_safe.dart'; @@ -50,6 +49,6 @@ abstract class Expression implements SassNode { /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory Expression.parse(String contents, {Object? url, Logger? logger}) => - ScssParser(contents, url: url, logger: logger).parseExpression(); + factory Expression.parse(String contents, {Object? url}) => + ScssParser(contents, url: url).parseExpression().$1; } diff --git a/lib/src/ast/sass/statement/stylesheet.dart b/lib/src/ast/sass/statement/stylesheet.dart index 1ae060854..55aad5419 100644 --- a/lib/src/ast/sass/statement/stylesheet.dart +++ b/lib/src/ast/sass/statement/stylesheet.dart @@ -7,8 +7,8 @@ import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; +import '../../../deprecation.dart'; import '../../../exception.dart'; -import '../../../logger.dart'; import '../../../parse/css.dart'; import '../../../parse/sass.dart'; import '../../../parse/scss.dart'; @@ -46,16 +46,25 @@ final class Stylesheet extends ParentStatement> { List get forwards => UnmodifiableListView(_forwards); final _forwards = []; + /// List of warnings discovered while parsing this stylesheet, to be emitted + /// during evaluation once we have a proper logger to use. + /// + /// @nodoc + @internal + final List parseTimeWarnings; + Stylesheet(Iterable children, FileSpan span) - : this.internal(children, span); + : this.internal(children, span, []); /// A separate internal constructor that allows [plainCss] to be set. /// /// @nodoc @internal Stylesheet.internal(Iterable children, this.span, + List parseTimeWarnings, {this.plainCss = false}) - : super(List.unmodifiable(children)) { + : parseTimeWarnings = UnmodifiableListView(parseTimeWarnings), + super(List.unmodifiable(children)) { loop: for (var child in this.children) { switch (child) { @@ -81,16 +90,15 @@ final class Stylesheet extends ParentStatement> { /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory Stylesheet.parse(String contents, Syntax syntax, - {Object? url, Logger? logger}) { + factory Stylesheet.parse(String contents, Syntax syntax, {Object? url}) { try { switch (syntax) { case Syntax.sass: - return Stylesheet.parseSass(contents, url: url, logger: logger); + return Stylesheet.parseSass(contents, url: url); case Syntax.scss: - return Stylesheet.parseScss(contents, url: url, logger: logger); + return Stylesheet.parseScss(contents, url: url); case Syntax.css: - return Stylesheet.parseCss(contents, url: url, logger: logger); + return Stylesheet.parseCss(contents, url: url); } } on SassException catch (error, stackTrace) { var url = error.span.sourceUrl; @@ -106,28 +114,33 @@ final class Stylesheet extends ParentStatement> { /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory Stylesheet.parseSass(String contents, - {Object? url, Logger? logger}) => - SassParser(contents, url: url, logger: logger).parse(); + factory Stylesheet.parseSass(String contents, {Object? url}) => + SassParser(contents, url: url).parse(); /// Parses an SCSS stylesheet from [contents]. /// /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory Stylesheet.parseScss(String contents, - {Object? url, Logger? logger}) => - ScssParser(contents, url: url, logger: logger).parse(); + factory Stylesheet.parseScss(String contents, {Object? url}) => + ScssParser(contents, url: url).parse(); /// Parses a plain CSS stylesheet from [contents]. /// /// If passed, [url] is the name of the file from which [contents] comes. /// /// Throws a [SassFormatException] if parsing fails. - factory Stylesheet.parseCss(String contents, {Object? url, Logger? logger}) => - CssParser(contents, url: url, logger: logger).parse(); + factory Stylesheet.parseCss(String contents, {Object? url}) => + CssParser(contents, url: url).parse(); T accept(StatementVisitor visitor) => visitor.visitStylesheet(this); String toString() => children.join(" "); } + +/// Record type for a warning discovered while parsing a stylesheet. +typedef ParseTimeWarning = ({ + Deprecation? deprecation, + FileSpan span, + String message +}); diff --git a/lib/src/ast/sass/statement/use_rule.dart b/lib/src/ast/sass/statement/use_rule.dart index 77aa81eda..70005f8fe 100644 --- a/lib/src/ast/sass/statement/use_rule.dart +++ b/lib/src/ast/sass/statement/use_rule.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../../../exception.dart'; -import '../../../logger.dart'; import '../../../parse/scss.dart'; import '../../../util/span.dart'; import '../../../visitor/interface/statement.dart'; @@ -56,8 +55,8 @@ final class UseRule extends Statement implements SassDependency { /// /// @nodoc @internal - factory UseRule.parse(String contents, {Object? url, Logger? logger}) => - ScssParser(contents, url: url, logger: logger).parseUseRule(); + factory UseRule.parse(String contents, {Object? url}) => + ScssParser(contents, url: url).parseUseRule().$1; T accept(StatementVisitor visitor) => visitor.visitUseRule(this); diff --git a/lib/src/ast/sass/statement/variable_declaration.dart b/lib/src/ast/sass/statement/variable_declaration.dart index f94619e99..56f75b12c 100644 --- a/lib/src/ast/sass/statement/variable_declaration.dart +++ b/lib/src/ast/sass/statement/variable_declaration.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../../../exception.dart'; -import '../../../logger.dart'; import '../../../parse/scss.dart'; import '../../../utils.dart'; import '../../../util/span.dart'; @@ -81,9 +80,8 @@ final class VariableDeclaration extends Statement implements SassDeclaration { /// /// @nodoc @internal - factory VariableDeclaration.parse(String contents, - {Object? url, Logger? logger}) => - ScssParser(contents, url: url, logger: logger).parseVariableDeclaration(); + factory VariableDeclaration.parse(String contents, {Object? url}) => + ScssParser(contents, url: url).parseVariableDeclaration().$1; T accept(StatementVisitor visitor) => visitor.visitVariableDeclaration(this); diff --git a/lib/src/ast/selector/complex.dart b/lib/src/ast/selector/complex.dart index f6d417901..bc0e057fa 100644 --- a/lib/src/ast/selector/complex.dart +++ b/lib/src/ast/selector/complex.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:source_span/source_span.dart'; import '../../extend/functions.dart'; -import '../../logger.dart'; import '../../parse/selector.dart'; import '../../utils.dart'; import '../../visitor/interface/selector.dart'; @@ -88,9 +87,8 @@ final class ComplexSelector extends Selector { /// /// Throws a [SassFormatException] if parsing fails. factory ComplexSelector.parse(String contents, - {Object? url, Logger? logger, bool allowParent = true}) => - SelectorParser(contents, - url: url, logger: logger, allowParent: allowParent) + {Object? url, bool allowParent = true}) => + SelectorParser(contents, url: url, allowParent: allowParent) .parseComplexSelector(); T accept(SelectorVisitor visitor) => visitor.visitComplexSelector(this); diff --git a/lib/src/ast/selector/compound.dart b/lib/src/ast/selector/compound.dart index 9fbc8d397..94cadc6df 100644 --- a/lib/src/ast/selector/compound.dart +++ b/lib/src/ast/selector/compound.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import '../../extend/functions.dart'; -import '../../logger.dart'; import '../../parse/selector.dart'; import '../../utils.dart'; import '../../visitor/interface/selector.dart'; @@ -69,9 +68,8 @@ final class CompoundSelector extends Selector { /// /// Throws a [SassFormatException] if parsing fails. factory CompoundSelector.parse(String contents, - {Object? url, Logger? logger, bool allowParent = true}) => - SelectorParser(contents, - url: url, logger: logger, allowParent: allowParent) + {Object? url, bool allowParent = true}) => + SelectorParser(contents, url: url, allowParent: allowParent) .parseCompoundSelector(); T accept(SelectorVisitor visitor) => diff --git a/lib/src/ast/selector/list.dart b/lib/src/ast/selector/list.dart index b45fa2a2d..96f3c4f27 100644 --- a/lib/src/ast/selector/list.dart +++ b/lib/src/ast/selector/list.dart @@ -7,7 +7,6 @@ import 'package:meta/meta.dart'; import '../../exception.dart'; import '../../extend/functions.dart'; import '../../interpolation_map.dart'; -import '../../logger.dart'; import '../../parse/selector.dart'; import '../../utils.dart'; import '../../util/iterable.dart'; @@ -68,13 +67,11 @@ final class SelectorList extends Selector { /// Throws a [SassFormatException] if parsing fails. factory SelectorList.parse(String contents, {Object? url, - Logger? logger, InterpolationMap? interpolationMap, bool allowParent = true, bool plainCss = false}) => SelectorParser(contents, url: url, - logger: logger, interpolationMap: interpolationMap, allowParent: allowParent, plainCss: plainCss) diff --git a/lib/src/ast/selector/simple.dart b/lib/src/ast/selector/simple.dart index acc86949e..8440b6642 100644 --- a/lib/src/ast/selector/simple.dart +++ b/lib/src/ast/selector/simple.dart @@ -5,7 +5,6 @@ import 'package:meta/meta.dart'; import '../../exception.dart'; -import '../../logger.dart'; import '../../parse/selector.dart'; import '../selector.dart'; @@ -54,9 +53,8 @@ abstract base class SimpleSelector extends Selector { /// /// Throws a [SassFormatException] if parsing fails. factory SimpleSelector.parse(String contents, - {Object? url, Logger? logger, bool allowParent = true}) => - SelectorParser(contents, - url: url, logger: logger, allowParent: allowParent) + {Object? url, bool allowParent = true}) => + SelectorParser(contents, url: url, allowParent: allowParent) .parseSimpleSelector(); /// Returns a new [SimpleSelector] based on `this`, as though it had been diff --git a/lib/src/async_compile.dart b/lib/src/async_compile.dart index 01d9574d7..dcbd7337e 100644 --- a/lib/src/async_compile.dart +++ b/lib/src/async_compile.dart @@ -58,14 +58,14 @@ Future compileAsync(String path, Stylesheet? stylesheet; if (nodeImporter == null && (syntax == null || syntax == Syntax.forPath(path))) { - importCache ??= AsyncImportCache.none(logger: logger); + importCache ??= AsyncImportCache.none(); stylesheet = (await importCache.importCanonical( FilesystemImporter.cwd, p.toUri(canonicalize(path)), originalUrl: p.toUri(path)))!; } else { stylesheet = Stylesheet.parse( readFile(path), syntax ?? Syntax.forPath(path), - url: p.toUri(path), logger: logger); + url: p.toUri(path)); } var result = await _compileStylesheet( @@ -120,8 +120,7 @@ Future compileStringAsync(String source, limitRepetition: !verbose) ..validate(); - var stylesheet = - Stylesheet.parse(source, syntax ?? Syntax.scss, url: url, logger: logger); + var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url); var result = await _compileStylesheet( stylesheet, diff --git a/lib/src/async_import_cache.dart b/lib/src/async_import_cache.dart index 170a62e4b..0bc3b4da7 100644 --- a/lib/src/async_import_cache.dart +++ b/lib/src/async_import_cache.dart @@ -9,14 +9,11 @@ import 'package:package_config/package_config_types.dart'; import 'package:path/path.dart' as p; import 'ast/sass.dart'; -import 'deprecation.dart'; import 'importer.dart'; import 'importer/canonicalize_context.dart'; import 'importer/no_op.dart'; import 'importer/utils.dart'; import 'io.dart'; -import 'logger.dart'; -import 'logger/deprecation_processing.dart'; import 'util/map.dart'; import 'util/nullable.dart'; import 'utils.dart'; @@ -38,9 +35,6 @@ final class AsyncImportCache { /// The importers to use when loading new Sass files. final List _importers; - /// The logger to use to emit warnings when parsing stylesheets. - final Logger _logger; - /// The canonicalized URLs for each non-canonical URL. /// /// The `forImport` in each key is true when this canonicalization is for an @@ -95,21 +89,16 @@ final class AsyncImportCache { AsyncImportCache( {Iterable? importers, Iterable? loadPaths, - PackageConfig? packageConfig, - Logger? logger}) - : _importers = _toImporters(importers, loadPaths, packageConfig), - _logger = logger ?? Logger.stderr(); + PackageConfig? packageConfig}) + : _importers = _toImporters(importers, loadPaths, packageConfig); /// Creates an import cache without any globally-available importers. - AsyncImportCache.none({Logger? logger}) - : _importers = const [], - _logger = logger ?? Logger.stderr(); + AsyncImportCache.none() : _importers = const []; /// Creates an import cache without any globally-available importers, and only /// the passed in importers. - AsyncImportCache.only(Iterable importers, {Logger? logger}) - : _importers = List.unmodifiable(importers), - _logger = logger ?? Logger.stderr(); + AsyncImportCache.only(Iterable importers) + : _importers = List.unmodifiable(importers); /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. @@ -244,13 +233,11 @@ final class AsyncImportCache { if (result == null) return (null, cacheable); - if (result.scheme == '') { - _logger.warnForDeprecation( - Deprecation.relativeCanonical, - "Importer $importer canonicalized $url to $result.\n" - "Relative canonical URLs are deprecated and will eventually be " - "disallowed."); - } else if (await importer.isNonCanonicalScheme(result.scheme)) { + // Relative canonical URLs (empty scheme) should throw an error starting in + // Dart Sass 2.0.0, but for now, they only emit a deprecation warning in + // the evaluator. + if (result.scheme != '' && + await importer.isNonCanonicalScheme(result.scheme)) { throw "Importer $importer canonicalized $url to $result, which uses a " "scheme declared as non-canonical."; } @@ -291,12 +278,9 @@ final class AsyncImportCache { /// into [canonicalUrl]. It's used to resolve a relative canonical URL, which /// importers may return for legacy reasons. /// - /// If [quiet] is `true`, this will disable logging warnings when parsing the - /// newly imported stylesheet. - /// /// Caches the result of the import and uses cached results if possible. Future importCanonical(AsyncImporter importer, Uri canonicalUrl, - {Uri? originalUrl, bool quiet = false}) async { + {Uri? originalUrl}) async { return await putIfAbsentAsync(_importCache, canonicalUrl, () async { var result = await importer.load(canonicalUrl); if (result == null) return null; @@ -307,8 +291,7 @@ final class AsyncImportCache { // relative to [originalUrl]. url: originalUrl == null ? canonicalUrl - : originalUrl.resolveUri(canonicalUrl), - logger: quiet ? Logger.quiet : _logger); + : originalUrl.resolveUri(canonicalUrl)); }); } @@ -361,28 +344,4 @@ final class AsyncImportCache { _resultsCache.remove(canonicalUrl); _importCache.remove(canonicalUrl); } - - /// Wraps [logger] to process deprecations within an ImportCache. - /// - /// This wrapped logger will handle the deprecation options, but will not - /// limit repetition, as it can be re-used across parses. A logger passed to - /// an ImportCache or AsyncImportCache should generally be wrapped here first, - /// unless it's already been wrapped to process deprecations, in which case - /// this method has no effect. - static DeprecationProcessingLogger wrapLogger( - Logger? logger, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations, - {bool color = false}) { - if (logger is DeprecationProcessingLogger) return logger; - return DeprecationProcessingLogger(logger ?? Logger.stderr(color: color), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - // TODO - sass/dart-sass#2390: We should limit repetition within a given - // compilation while allowing it across different compilations (such as - // with `--watch`). - limitRepetition: true); - } } diff --git a/lib/src/compile.dart b/lib/src/compile.dart index 265443efc..35bc75872 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_compile.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 42c9e2008d449ba4b73b3b92a64cf4d51253837d +// Checksum: 9dcf6641342288ad16f44b134ad9a555b4e1e992 // // ignore_for_file: unused_import @@ -67,14 +67,14 @@ CompileResult compile(String path, Stylesheet? stylesheet; if (nodeImporter == null && (syntax == null || syntax == Syntax.forPath(path))) { - importCache ??= ImportCache.none(logger: logger); + importCache ??= ImportCache.none(); stylesheet = importCache.importCanonical( FilesystemImporter.cwd, p.toUri(canonicalize(path)), originalUrl: p.toUri(path))!; } else { stylesheet = Stylesheet.parse( readFile(path), syntax ?? Syntax.forPath(path), - url: p.toUri(path), logger: logger); + url: p.toUri(path)); } var result = _compileStylesheet( @@ -129,8 +129,7 @@ CompileResult compileString(String source, limitRepetition: !verbose) ..validate(); - var stylesheet = - Stylesheet.parse(source, syntax ?? Syntax.scss, url: url, logger: logger); + var stylesheet = Stylesheet.parse(source, syntax ?? Syntax.scss, url: url); var result = _compileStylesheet( stylesheet, diff --git a/lib/src/executable/compile_stylesheet.dart b/lib/src/executable/compile_stylesheet.dart index 0993e63e2..c2f26d260 100644 --- a/lib/src/executable/compile_stylesheet.dart +++ b/lib/src/executable/compile_stylesheet.dart @@ -95,13 +95,7 @@ Future _compileStylesheetWithoutErrorHandling(ExecutableOptions options, try { if (options.asynchronous) { var importCache = AsyncImportCache( - importers: options.pkgImporters, - loadPaths: options.loadPaths, - logger: AsyncImportCache.wrapLogger( - options.logger, - options.silenceDeprecations, - options.fatalDeprecations, - options.futureDeprecations)); + importers: options.pkgImporters, loadPaths: options.loadPaths); result = source == null ? await compileStringAsync(await readStdin(), diff --git a/lib/src/executable/repl.dart b/lib/src/executable/repl.dart index ad9ad5ecd..726279bd8 100644 --- a/lib/src/executable/repl.dart +++ b/lib/src/executable/repl.dart @@ -14,7 +14,9 @@ import '../import_cache.dart'; import '../importer/filesystem.dart'; import '../logger/deprecation_processing.dart'; import '../logger/tracking.dart'; +import '../logger.dart'; import '../parse/parser.dart'; +import '../parse/scss.dart'; import '../utils.dart'; import '../visitor/evaluate.dart'; @@ -28,29 +30,41 @@ Future repl(ExecutableOptions options) async { futureDeprecations: options.futureDeprecations, limitRepetition: !options.verbose) ..validate(); + + void warn(ParseTimeWarning warning) { + switch (warning) { + case (:var message, :var span, :var deprecation?): + logger.warnForDeprecation(deprecation, message, span: span); + case (:var message, :var span, deprecation: null): + logger.warn(message, span: span); + } + } + var evaluator = Evaluator( importer: FilesystemImporter.cwd, importCache: ImportCache( - importers: options.pkgImporters, - loadPaths: options.loadPaths, - logger: logger), + importers: options.pkgImporters, loadPaths: options.loadPaths), logger: logger); await for (String line in repl.runAsync()) { if (line.trim().isEmpty) continue; try { if (line.startsWith("@")) { - evaluator.use(UseRule.parse(line, logger: logger)); + var (node, warnings) = ScssParser(line).parseUseRule(); + warnings.forEach(warn); + evaluator.use(node); continue; } if (Parser.isVariableDeclarationLike(line)) { - var declaration = VariableDeclaration.parse(line, logger: logger); - evaluator.setVariable(declaration); - print(evaluator.evaluate(VariableExpression( - declaration.name, declaration.span, - namespace: declaration.namespace))); + var (node, warnings) = ScssParser(line).parseVariableDeclaration(); + warnings.forEach(warn); + evaluator.setVariable(node); + print(evaluator.evaluate(VariableExpression(node.name, node.span, + namespace: node.namespace))); } else { - print(evaluator.evaluate(Expression.parse(line, logger: logger))); + var (node, warnings) = ScssParser(line).parseExpression(); + warnings.forEach(warn); + print(evaluator.evaluate(node)); } } on SassException catch (error, stackTrace) { _logError(error, getTrace(error) ?? stackTrace, line, repl, options, diff --git a/lib/src/import_cache.dart b/lib/src/import_cache.dart index f37c96e1f..e9c627fb1 100644 --- a/lib/src/import_cache.dart +++ b/lib/src/import_cache.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_import_cache.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: f459089f21fcbfca2d073ba73a5bcf4e98ecab1b +// Checksum: 4d09da97db4e59518d193f58963897d36ef4db00 // // ignore_for_file: unused_import @@ -16,14 +16,11 @@ import 'package:package_config/package_config_types.dart'; import 'package:path/path.dart' as p; import 'ast/sass.dart'; -import 'deprecation.dart'; import 'importer.dart'; import 'importer/canonicalize_context.dart'; import 'importer/no_op.dart'; import 'importer/utils.dart'; import 'io.dart'; -import 'logger.dart'; -import 'logger/deprecation_processing.dart'; import 'util/map.dart'; import 'util/nullable.dart'; import 'utils.dart'; @@ -41,9 +38,6 @@ final class ImportCache { /// The importers to use when loading new Sass files. final List _importers; - /// The logger to use to emit warnings when parsing stylesheets. - final Logger _logger; - /// The canonicalized URLs for each non-canonical URL. /// /// The `forImport` in each key is true when this canonicalization is for an @@ -96,21 +90,16 @@ final class ImportCache { ImportCache( {Iterable? importers, Iterable? loadPaths, - PackageConfig? packageConfig, - Logger? logger}) - : _importers = _toImporters(importers, loadPaths, packageConfig), - _logger = logger ?? Logger.stderr(); + PackageConfig? packageConfig}) + : _importers = _toImporters(importers, loadPaths, packageConfig); /// Creates an import cache without any globally-available importers. - ImportCache.none({Logger? logger}) - : _importers = const [], - _logger = logger ?? Logger.stderr(); + ImportCache.none() : _importers = const []; /// Creates an import cache without any globally-available importers, and only /// the passed in importers. - ImportCache.only(Iterable importers, {Logger? logger}) - : _importers = List.unmodifiable(importers), - _logger = logger ?? Logger.stderr(); + ImportCache.only(Iterable importers) + : _importers = List.unmodifiable(importers); /// Converts the user's [importers], [loadPaths], and [packageConfig] /// options into a single list of importers. @@ -242,13 +231,10 @@ final class ImportCache { if (result == null) return (null, cacheable); - if (result.scheme == '') { - _logger.warnForDeprecation( - Deprecation.relativeCanonical, - "Importer $importer canonicalized $url to $result.\n" - "Relative canonical URLs are deprecated and will eventually be " - "disallowed."); - } else if (importer.isNonCanonicalScheme(result.scheme)) { + // Relative canonical URLs (empty scheme) should throw an error starting in + // Dart Sass 2.0.0, but for now, they only emit a deprecation warning in + // the evaluator. + if (result.scheme != '' && importer.isNonCanonicalScheme(result.scheme)) { throw "Importer $importer canonicalized $url to $result, which uses a " "scheme declared as non-canonical."; } @@ -286,12 +272,9 @@ final class ImportCache { /// into [canonicalUrl]. It's used to resolve a relative canonical URL, which /// importers may return for legacy reasons. /// - /// If [quiet] is `true`, this will disable logging warnings when parsing the - /// newly imported stylesheet. - /// /// Caches the result of the import and uses cached results if possible. Stylesheet? importCanonical(Importer importer, Uri canonicalUrl, - {Uri? originalUrl, bool quiet = false}) { + {Uri? originalUrl}) { return _importCache.putIfAbsent(canonicalUrl, () { var result = importer.load(canonicalUrl); if (result == null) return null; @@ -302,8 +285,7 @@ final class ImportCache { // relative to [originalUrl]. url: originalUrl == null ? canonicalUrl - : originalUrl.resolveUri(canonicalUrl), - logger: quiet ? Logger.quiet : _logger); + : originalUrl.resolveUri(canonicalUrl)); }); } @@ -356,28 +338,4 @@ final class ImportCache { _resultsCache.remove(canonicalUrl); _importCache.remove(canonicalUrl); } - - /// Wraps [logger] to process deprecations within an ImportCache. - /// - /// This wrapped logger will handle the deprecation options, but will not - /// limit repetition, as it can be re-used across parses. A logger passed to - /// an ImportCache or AsyncImportCache should generally be wrapped here first, - /// unless it's already been wrapped to process deprecations, in which case - /// this method has no effect. - static DeprecationProcessingLogger wrapLogger( - Logger? logger, - Iterable? silenceDeprecations, - Iterable? fatalDeprecations, - Iterable? futureDeprecations, - {bool color = false}) { - if (logger is DeprecationProcessingLogger) return logger; - return DeprecationProcessingLogger(logger ?? Logger.stderr(color: color), - silenceDeprecations: {...?silenceDeprecations}, - fatalDeprecations: {...?fatalDeprecations}, - futureDeprecations: {...?futureDeprecations}, - // TODO - sass/dart-sass#2390: We should limit repetition within a given - // compilation while allowing it across different compilations (such as - // with `--watch`). - limitRepetition: true); - } } diff --git a/lib/src/js/parser.dart b/lib/src/js/parser.dart index 2f4c75109..82bc12b0c 100644 --- a/lib/src/js/parser.dart +++ b/lib/src/js/parser.dart @@ -10,14 +10,11 @@ import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; import '../ast/sass.dart'; -import '../logger.dart'; -import '../logger/js_to_dart.dart'; import '../syntax.dart'; import '../util/nullable.dart'; import '../util/span.dart'; import '../visitor/interface/expression.dart'; import '../visitor/interface/statement.dart'; -import 'logger.dart'; import 'reflection.dart'; import 'visitor/expression.dart'; import 'visitor/statement.dart'; @@ -111,14 +108,12 @@ void _addSupportsConditionToInterpolation() { } /// A JavaScript-friendly method to parse a stylesheet. -Stylesheet _parse(String css, String syntax, String? path, JSLogger? logger) => - Stylesheet.parse( - css, - switch (syntax) { - 'scss' => Syntax.scss, - 'sass' => Syntax.sass, - 'css' => Syntax.css, - _ => throw UnsupportedError('Unknown syntax "$syntax"') - }, - url: path.andThen(p.toUri), - logger: JSToDartLogger(logger, Logger.stderr())); +Stylesheet _parse(String css, String syntax, String? path) => Stylesheet.parse( + css, + switch (syntax) { + 'scss' => Syntax.scss, + 'sass' => Syntax.sass, + 'css' => Syntax.css, + _ => throw UnsupportedError('Unknown syntax "$syntax"') + }, + url: path.andThen(p.toUri)); diff --git a/lib/src/parse/at_root_query.dart b/lib/src/parse/at_root_query.dart index a107458c1..f69501ea6 100644 --- a/lib/src/parse/at_root_query.dart +++ b/lib/src/parse/at_root_query.dart @@ -9,8 +9,7 @@ import 'parser.dart'; /// A parser for `@at-root` queries. class AtRootQueryParser extends Parser { - AtRootQueryParser(super.contents, - {super.url, super.logger, super.interpolationMap}); + AtRootQueryParser(super.contents, {super.url, super.interpolationMap}); AtRootQuery parse() { return wrapSpanFormatException(() { diff --git a/lib/src/parse/css.dart b/lib/src/parse/css.dart index fa48e635f..050d031ba 100644 --- a/lib/src/parse/css.dart +++ b/lib/src/parse/css.dart @@ -30,7 +30,7 @@ final _disallowedFunctionNames = class CssParser extends ScssParser { bool get plainCss => true; - CssParser(super.contents, {super.url, super.logger}); + CssParser(super.contents, {super.url}); bool silentComment() { if (inExpression) return false; diff --git a/lib/src/parse/keyframe_selector.dart b/lib/src/parse/keyframe_selector.dart index 25a690813..fb69ee638 100644 --- a/lib/src/parse/keyframe_selector.dart +++ b/lib/src/parse/keyframe_selector.dart @@ -9,8 +9,7 @@ import 'parser.dart'; /// A parser for `@keyframes` block selectors. class KeyframeSelectorParser extends Parser { - KeyframeSelectorParser(super.contents, - {super.url, super.logger, super.interpolationMap}); + KeyframeSelectorParser(super.contents, {super.url, super.interpolationMap}); List parse() { return wrapSpanFormatException(() { diff --git a/lib/src/parse/media_query.dart b/lib/src/parse/media_query.dart index ce54dae57..89d854161 100644 --- a/lib/src/parse/media_query.dart +++ b/lib/src/parse/media_query.dart @@ -10,8 +10,7 @@ import 'parser.dart'; /// A parser for `@media` queries. class MediaQueryParser extends Parser { - MediaQueryParser(super.contents, - {super.url, super.logger, super.interpolationMap}); + MediaQueryParser(super.contents, {super.url, super.interpolationMap}); List parse() { return wrapSpanFormatException(() { diff --git a/lib/src/parse/parser.dart b/lib/src/parse/parser.dart index 8efbbe772..4c4d6ba5e 100644 --- a/lib/src/parse/parser.dart +++ b/lib/src/parse/parser.dart @@ -10,7 +10,6 @@ import 'package:string_scanner/string_scanner.dart'; import '../exception.dart'; import '../interpolation_map.dart'; import '../io.dart'; -import '../logger.dart'; import '../util/character.dart'; import '../util/lazy_file_span.dart'; import '../util/map.dart'; @@ -25,10 +24,6 @@ class Parser { /// The scanner that scans through the text being parsed. final SpanScanner scanner; - /// The logger to use when emitting warnings. - @protected - final Logger logger; - /// A map used to map source spans in the text being parsed back to their /// original locations in the source file, if this isn't being parsed directly /// from source. @@ -37,13 +32,12 @@ class Parser { /// Parses [text] as a CSS identifier and returns the result. /// /// Throws a [SassFormatException] if parsing fails. - static String parseIdentifier(String text, {Logger? logger}) => - Parser(text, logger: logger)._parseIdentifier(); + static String parseIdentifier(String text) => Parser(text)._parseIdentifier(); /// Returns whether [text] is a valid CSS identifier. - static bool isIdentifier(String text, {Logger? logger}) { + static bool isIdentifier(String text) { try { - parseIdentifier(text, logger: logger); + parseIdentifier(text); return true; } on SassFormatException { return false; @@ -53,14 +47,12 @@ class Parser { /// Returns whether [text] starts like a variable declaration. /// /// Ignores everything after the `:`. - static bool isVariableDeclarationLike(String text, {Logger? logger}) => - Parser(text, logger: logger)._isVariableDeclarationLike(); + static bool isVariableDeclarationLike(String text) => + Parser(text)._isVariableDeclarationLike(); @protected - Parser(String contents, - {Object? url, Logger? logger, InterpolationMap? interpolationMap}) + Parser(String contents, {Object? url, InterpolationMap? interpolationMap}) : scanner = SpanScanner(contents, sourceUrl: url), - logger = logger ?? const Logger.stderr(), _interpolationMap = interpolationMap; String _parseIdentifier() { @@ -662,10 +654,6 @@ class Parser { : LazyFileSpan(() => _interpolationMap!.mapSpan(span)); } - /// Prints a warning to standard error, associated with [span]. - @protected - void warn(String message, FileSpan span) => logger.warn(message, span: span); - /// Throws an error associated with [span]. /// /// If [trace] is passed, attaches it as the error's stack trace. diff --git a/lib/src/parse/sass.dart b/lib/src/parse/sass.dart index 823f44c20..55b2086c9 100644 --- a/lib/src/parse/sass.dart +++ b/lib/src/parse/sass.dart @@ -38,7 +38,7 @@ class SassParser extends StylesheetParser { bool get indented => true; - SassParser(super.contents, {super.url, super.logger}); + SassParser(super.contents, {super.url}); Interpolation styleRuleSelector() { var start = scanner.state; diff --git a/lib/src/parse/scss.dart b/lib/src/parse/scss.dart index e9b9693f3..d8b923104 100644 --- a/lib/src/parse/scss.dart +++ b/lib/src/parse/scss.dart @@ -7,7 +7,6 @@ import 'package:charcode/charcode.dart'; import '../ast/sass.dart'; import '../deprecation.dart'; import '../interpolation_buffer.dart'; -import '../logger.dart'; import '../util/character.dart'; import 'stylesheet.dart'; @@ -16,7 +15,7 @@ class ScssParser extends StylesheetParser { bool get indented => false; int get currentIndentation => 0; - ScssParser(super.contents, {super.url, super.logger}); + ScssParser(super.contents, {super.url}); Interpolation styleRuleSelector() => almostAnyValue(); @@ -44,13 +43,15 @@ class ScssParser extends StylesheetParser { if (scanner.scanChar($at)) { if (scanIdentifier('else', caseSensitive: true)) return true; if (scanIdentifier('elseif', caseSensitive: true)) { - logger.warnForDeprecation( - Deprecation.elseif, - '@elseif is deprecated and will not be supported in future Sass ' - 'versions.\n' - '\n' - 'Recommendation: @else if', - span: scanner.spanFrom(beforeAt)); + warnings.add(( + deprecation: Deprecation.elseif, + message: + '@elseif is deprecated and will not be supported in future Sass ' + 'versions.\n' + '\n' + 'Recommendation: @else if', + span: scanner.spanFrom(beforeAt) + )); scanner.position -= 2; return true; } diff --git a/lib/src/parse/selector.dart b/lib/src/parse/selector.dart index 97a334166..595c0bba7 100644 --- a/lib/src/parse/selector.dart +++ b/lib/src/parse/selector.dart @@ -43,7 +43,6 @@ class SelectorParser extends Parser { /// selector rather than a Sass selector. SelectorParser(super.contents, {super.url, - super.logger, super.interpolationMap, bool allowParent = true, bool plainCss = false}) diff --git a/lib/src/parse/stylesheet.dart b/lib/src/parse/stylesheet.dart index 3fab4e0ba..cab1bf972 100644 --- a/lib/src/parse/stylesheet.dart +++ b/lib/src/parse/stylesheet.dart @@ -13,7 +13,6 @@ import '../color_names.dart'; import '../deprecation.dart'; import '../exception.dart'; import '../interpolation_buffer.dart'; -import '../logger.dart'; import '../util/character.dart'; import '../utils.dart'; import '../util/nullable.dart'; @@ -70,11 +69,16 @@ abstract class StylesheetParser extends Parser { /// the same set of variable names no matter how it's evaluated. final _globalVariables = {}; + /// Warnings discovered while parsing that should be emitted during + /// evaluation once a proper logger is available. + @protected + final warnings = []; + /// The silent comment this parser encountered previously. @protected SilentComment? lastSilentComment; - StylesheetParser(super.contents, {super.url, super.logger}); + StylesheetParser(super.contents, {super.url}); // ## Statements @@ -103,7 +107,7 @@ abstract class StylesheetParser extends Parser { NullExpression(declaration.expression.span), declaration.span, guarded: true))); - return Stylesheet.internal(statements, scanner.spanFrom(start), + return Stylesheet.internal(statements, scanner.spanFrom(start), warnings, plainCss: plainCss); }); } @@ -119,25 +123,31 @@ abstract class StylesheetParser extends Parser { return arguments; }); - Expression parseExpression() => _parseSingleProduction(_expression); + (Expression, List) parseExpression() => + (_parseSingleProduction(_expression), warnings); SassNumber parseNumber() { var expression = _parseSingleProduction(_number); return SassNumber(expression.value, expression.unit); } - VariableDeclaration parseVariableDeclaration() => - _parseSingleProduction(() => lookingAtIdentifier() - ? _variableDeclarationWithNamespace() - : variableDeclarationWithoutNamespace()); + (VariableDeclaration, List) parseVariableDeclaration() => ( + _parseSingleProduction(() => lookingAtIdentifier() + ? _variableDeclarationWithNamespace() + : variableDeclarationWithoutNamespace()), + warnings + ); - UseRule parseUseRule() => _parseSingleProduction(() { - var start = scanner.state; - scanner.expectChar($at, name: "@-rule"); - expectIdentifier("use"); - whitespace(); - return _useRule(start); - }); + (UseRule, List) parseUseRule() => ( + _parseSingleProduction(() { + var start = scanner.state; + scanner.expectChar($at, name: "@-rule"); + expectIdentifier("use"); + whitespace(); + return _useRule(start); + }), + warnings + ); /// Parses and returns [production] as the entire contents of [scanner]. T _parseSingleProduction(T production()) { @@ -239,11 +249,13 @@ abstract class StylesheetParser extends Parser { switch (identifier()) { case 'default': if (guarded) { - logger.warnForDeprecation( - Deprecation.duplicateVarFlags, - '!default should only be written once for each variable.\n' - 'This will be an error in Dart Sass 2.0.0.', - span: scanner.spanFrom(flagStart)); + warnings.add(( + deprecation: Deprecation.duplicateVarFlags, + message: + '!default should only be written once for each variable.\n' + 'This will be an error in Dart Sass 2.0.0.', + span: scanner.spanFrom(flagStart) + )); } guarded = true; @@ -252,11 +264,13 @@ abstract class StylesheetParser extends Parser { error("!global isn't allowed for variables in other modules.", scanner.spanFrom(flagStart)); } else if (global) { - logger.warnForDeprecation( - Deprecation.duplicateVarFlags, - '!global should only be written once for each variable.\n' - 'This will be an error in Dart Sass 2.0.0.', - span: scanner.spanFrom(flagStart)); + warnings.add(( + deprecation: Deprecation.duplicateVarFlags, + message: + '!global should only be written once for each variable.\n' + 'This will be an error in Dart Sass 2.0.0.', + span: scanner.spanFrom(flagStart) + )); } global = true; @@ -493,8 +507,12 @@ abstract class StylesheetParser extends Parser { return _withChildren(_statement, start, (children, span) { if (indented && children.isEmpty) { - warn("This selector doesn't have any properties and won't be rendered.", - interpolation.span); + warnings.add(( + deprecation: null, + message: "This selector doesn't have any properties and won't be " + "rendered.", + span: interpolation.span + )); } _inStyleRule = wasInStyleRule; @@ -865,13 +883,15 @@ abstract class StylesheetParser extends Parser { var name = identifier(); if (name.startsWith('--')) { - logger.warnForDeprecation( - Deprecation.cssFunctionMixin, - 'Sass @function names beginning with -- are deprecated for forward-' - 'compatibility with plain CSS mixins.\n' - '\n' - 'For details, see https://sass-lang.com/d/css-function-mixin', - span: scanner.spanFrom(beforeName)); + warnings.add(( + deprecation: Deprecation.cssFunctionMixin, + message: + 'Sass @function names beginning with -- are deprecated for forward-' + 'compatibility with plain CSS mixins.\n' + '\n' + 'For details, see https://sass-lang.com/d/css-function-mixin', + span: scanner.spanFrom(beforeName) + )); } whitespace(); @@ -1055,12 +1075,15 @@ abstract class StylesheetParser extends Parser { whitespace(); var argument = importArgument(); if (argument is DynamicImport) { - logger.warnForDeprecation( - Deprecation.import, - 'Sass @import rules are deprecated and will be removed in Dart ' - 'Sass 3.0.0.\n\n' - 'More info and automated migrator: https://sass-lang.com/d/import', - span: argument.span); + warnings.add(( + deprecation: Deprecation.import, + message: + 'Sass @import rules are deprecated and will be removed in Dart ' + 'Sass 3.0.0.\n\n' + 'More info and automated migrator: ' + 'https://sass-lang.com/d/import', + span: argument.span + )); } if ((_inControlDirective || _inMixin) && argument is DynamicImport) { _disallowedAtRule(start); @@ -1295,13 +1318,15 @@ abstract class StylesheetParser extends Parser { var name = identifier(); if (name.startsWith('--')) { - logger.warnForDeprecation( - Deprecation.cssFunctionMixin, - 'Sass @mixin names beginning with -- are deprecated for forward-' - 'compatibility with plain CSS mixins.\n' - '\n' - 'For details, see https://sass-lang.com/d/css-function-mixin', - span: scanner.spanFrom(beforeName)); + warnings.add(( + deprecation: Deprecation.cssFunctionMixin, + message: + 'Sass @mixin names beginning with -- are deprecated for forward-' + 'compatibility with plain CSS mixins.\n' + '\n' + 'For details, see https://sass-lang.com/d/css-function-mixin', + span: scanner.spanFrom(beforeName) + )); } whitespace(); @@ -1397,13 +1422,15 @@ abstract class StylesheetParser extends Parser { var value = buffer.interpolation(scanner.spanFrom(valueStart)); return _withChildren(_statement, start, (children, span) { if (needsDeprecationWarning) { - logger.warnForDeprecation( - Deprecation.mozDocument, - "@-moz-document is deprecated and support will be removed in Dart " - "Sass 2.0.0.\n" - "\n" - "For details, see https://sass-lang.com/d/moz-document.", - span: span); + warnings.add(( + deprecation: Deprecation.mozDocument, + message: + "@-moz-document is deprecated and support will be removed in " + "Dart Sass 2.0.0.\n" + "\n" + "For details, see https://sass-lang.com/d/moz-document.", + span: span + )); } return AtRule(name, span, value: value, children: children); @@ -1466,7 +1493,7 @@ abstract class StylesheetParser extends Parser { var namespace = basename.substring( basename.startsWith("_") ? 1 : 0, dot == -1 ? basename.length : dot); try { - return Parser.parseIdentifier(namespace, logger: logger); + return Parser.parseIdentifier(namespace); } on SassFormatException { error( 'The default namespace "$namespace" is not a valid Sass identifier.\n' @@ -1803,25 +1830,26 @@ abstract class StylesheetParser extends Parser { right.span.start.offset - 1, right.span.start.offset) == operator.operator && scanner.string.codeUnitAt(left.span.end.offset).isWhitespace) { - logger.warnForDeprecation( - Deprecation.strictUnary, - "This operation is parsed as:\n" - "\n" - " $left ${operator.operator} $right\n" - "\n" - "but you may have intended it to mean:\n" - "\n" - " $left (${operator.operator}$right)\n" - "\n" - "Add a space after ${operator.operator} to clarify that it's " - "meant to be a binary operation, or wrap\n" - "it in parentheses to make it a unary operation. This will be " - "an error in future\n" - "versions of Sass.\n" - "\n" - "More info and automated migrator: " - "https://sass-lang.com/d/strict-unary", - span: singleExpression_!.span); + warnings.add(( + deprecation: Deprecation.strictUnary, + message: "This operation is parsed as:\n" + "\n" + " $left ${operator.operator} $right\n" + "\n" + "but you may have intended it to mean:\n" + "\n" + " $left (${operator.operator}$right)\n" + "\n" + "Add a space after ${operator.operator} to clarify that it's " + "meant to be a binary operation, or wrap\n" + "it in parentheses to make it a unary operation. This will be " + "an error in future\n" + "versions of Sass.\n" + "\n" + "More info and automated migrator: " + "https://sass-lang.com/d/strict-unary", + span: singleExpression_!.span + )); } } } @@ -2506,10 +2534,12 @@ abstract class StylesheetParser extends Parser { scanner.expectChar($ampersand); if (scanner.scanChar($ampersand)) { - warn( - 'In Sass, "&&" means two copies of the parent selector. You ' - 'probably want to use "and" instead.', - scanner.spanFrom(start)); + warnings.add(( + deprecation: null, + message: 'In Sass, "&&" means two copies of the parent selector. You ' + 'probably want to use "and" instead.', + span: scanner.spanFrom(start) + )); scanner.position--; } diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index d0e36c360..69d9b0d88 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -339,9 +339,7 @@ final class _EvaluateVisitor bool quietDeps = false, bool sourceMap = false}) : _importCache = importCache ?? - (nodeImporter == null - ? AsyncImportCache.none(logger: logger) - : null), + (nodeImporter == null ? AsyncImportCache.none() : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, @@ -1001,6 +999,9 @@ final class _EvaluateVisitor // ## Statements Future visitStylesheet(Stylesheet node) async { + for (var warning in node.parseTimeWarnings) { + _warn(warning.message, warning.span, warning.deprecation); + } for (var child in node.children) { await child.accept(this); } @@ -1012,8 +1013,7 @@ final class _EvaluateVisitor if (node.query case var unparsedQuery?) { var (resolved, map) = await _performInterpolationWithMap(unparsedQuery, warnForColor: true); - query = - AtRootQuery.parse(resolved, interpolationMap: map, logger: _logger); + query = AtRootQuery.parse(resolved, interpolationMap: map); } var parent = _parent; @@ -1340,7 +1340,7 @@ final class _EvaluateVisitor await _performInterpolationWithMap(node.selector, warnForColor: true); var list = SelectorList.parse(trimAscii(targetText, excludeEscape: true), - interpolationMap: targetMap, logger: _logger, allowParent: false); + interpolationMap: targetMap, allowParent: false); for (var complex in list.components) { var compound = complex.singleCompound; @@ -1745,6 +1745,13 @@ final class _EvaluateVisitor if (await importCache.canonicalize(Uri.parse(url), baseImporter: _importer, baseUrl: baseUrl, forImport: forImport) case (var importer, var canonicalUrl, :var originalUrl)) { + if (canonicalUrl.scheme == '') { + _logger.warnForDeprecation( + Deprecation.relativeCanonical, + "Importer $importer canonicalized $url to $canonicalUrl.\n" + "Relative canonical URLs are deprecated and will eventually be " + "disallowed."); + } // Make sure we record the canonical URL as "loaded" even if the // actual load fails, because watchers should watch it to see if it // changes in a way that allows the load to succeed. @@ -1752,7 +1759,7 @@ final class _EvaluateVisitor var isDependency = _inDependency || importer != _importer; if (await importCache.importCanonical(importer, canonicalUrl, - originalUrl: originalUrl, quiet: _quietDeps && isDependency) + originalUrl: originalUrl) case var stylesheet?) { return (stylesheet, importer: importer, isDependency: isDependency); } @@ -1806,8 +1813,7 @@ final class _EvaluateVisitor return ( Stylesheet.parse( contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, - url: url, - logger: _quietDeps && isDependency ? Logger.quiet : _logger), + url: url), importer: null, isDependency: isDependency ); @@ -2002,8 +2008,7 @@ final class _EvaluateVisitor Interpolation interpolation) async { var (resolved, map) = await _performInterpolationWithMap(interpolation, warnForColor: true); - return CssMediaQuery.parseList(resolved, - logger: _logger, interpolationMap: map); + return CssMediaQuery.parseList(resolved, interpolationMap: map); } /// Returns a list of queries that selects for contexts that match both @@ -2055,9 +2060,9 @@ final class _EvaluateVisitor // NOTE: this logic is largely duplicated in [visitCssKeyframeBlock]. Most // changes here should be mirrored there. - var parsedSelector = KeyframeSelectorParser(selectorText, - logger: _logger, interpolationMap: selectorMap) - .parse(); + var parsedSelector = + KeyframeSelectorParser(selectorText, interpolationMap: selectorMap) + .parse(); var rule = ModifiableCssKeyframeBlock( CssValue(List.unmodifiable(parsedSelector), node.selector.span), node.span); @@ -2072,9 +2077,7 @@ final class _EvaluateVisitor } var parsedSelector = SelectorList.parse(selectorText, - interpolationMap: selectorMap, - plainCss: _stylesheet.plainCss, - logger: _logger); + interpolationMap: selectorMap, plainCss: _stylesheet.plainCss); var nest = !(_styleRule?.fromPlainCss ?? false); if (nest) { diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 8ee041bee..5e29377df 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: 6f39aea0955dc6ea8669496ea2270387b61b8aa7 +// Checksum: e7260fedcd4f374ba517a93d038c3c53586c9622 // // ignore_for_file: unused_import @@ -346,8 +346,8 @@ final class _EvaluateVisitor Logger? logger, bool quietDeps = false, bool sourceMap = false}) - : _importCache = importCache ?? - (nodeImporter == null ? ImportCache.none(logger: logger) : null), + : _importCache = + importCache ?? (nodeImporter == null ? ImportCache.none() : null), _nodeImporter = nodeImporter, _logger = logger ?? const Logger.stderr(), _quietDeps = quietDeps, @@ -999,6 +999,9 @@ final class _EvaluateVisitor // ## Statements Value? visitStylesheet(Stylesheet node) { + for (var warning in node.parseTimeWarnings) { + _warn(warning.message, warning.span, warning.deprecation); + } for (var child in node.children) { child.accept(this); } @@ -1010,8 +1013,7 @@ final class _EvaluateVisitor if (node.query case var unparsedQuery?) { var (resolved, map) = _performInterpolationWithMap(unparsedQuery, warnForColor: true); - query = - AtRootQuery.parse(resolved, interpolationMap: map, logger: _logger); + query = AtRootQuery.parse(resolved, interpolationMap: map); } var parent = _parent; @@ -1337,7 +1339,7 @@ final class _EvaluateVisitor _performInterpolationWithMap(node.selector, warnForColor: true); var list = SelectorList.parse(trimAscii(targetText, excludeEscape: true), - interpolationMap: targetMap, logger: _logger, allowParent: false); + interpolationMap: targetMap, allowParent: false); for (var complex in list.components) { var compound = complex.singleCompound; @@ -1739,6 +1741,13 @@ final class _EvaluateVisitor if (importCache.canonicalize(Uri.parse(url), baseImporter: _importer, baseUrl: baseUrl, forImport: forImport) case (var importer, var canonicalUrl, :var originalUrl)) { + if (canonicalUrl.scheme == '') { + _logger.warnForDeprecation( + Deprecation.relativeCanonical, + "Importer $importer canonicalized $url to $canonicalUrl.\n" + "Relative canonical URLs are deprecated and will eventually be " + "disallowed."); + } // Make sure we record the canonical URL as "loaded" even if the // actual load fails, because watchers should watch it to see if it // changes in a way that allows the load to succeed. @@ -1746,7 +1755,7 @@ final class _EvaluateVisitor var isDependency = _inDependency || importer != _importer; if (importCache.importCanonical(importer, canonicalUrl, - originalUrl: originalUrl, quiet: _quietDeps && isDependency) + originalUrl: originalUrl) case var stylesheet?) { return (stylesheet, importer: importer, isDependency: isDependency); } @@ -1800,8 +1809,7 @@ final class _EvaluateVisitor return ( Stylesheet.parse( contents, url.startsWith('file') ? Syntax.forPath(url) : Syntax.scss, - url: url, - logger: _quietDeps && isDependency ? Logger.quiet : _logger), + url: url), importer: null, isDependency: isDependency ); @@ -1992,8 +2000,7 @@ final class _EvaluateVisitor List _visitMediaQueries(Interpolation interpolation) { var (resolved, map) = _performInterpolationWithMap(interpolation, warnForColor: true); - return CssMediaQuery.parseList(resolved, - logger: _logger, interpolationMap: map); + return CssMediaQuery.parseList(resolved, interpolationMap: map); } /// Returns a list of queries that selects for contexts that match both @@ -2045,9 +2052,9 @@ final class _EvaluateVisitor // NOTE: this logic is largely duplicated in [visitCssKeyframeBlock]. Most // changes here should be mirrored there. - var parsedSelector = KeyframeSelectorParser(selectorText, - logger: _logger, interpolationMap: selectorMap) - .parse(); + var parsedSelector = + KeyframeSelectorParser(selectorText, interpolationMap: selectorMap) + .parse(); var rule = ModifiableCssKeyframeBlock( CssValue(List.unmodifiable(parsedSelector), node.selector.span), node.span); @@ -2062,9 +2069,7 @@ final class _EvaluateVisitor } var parsedSelector = SelectorList.parse(selectorText, - interpolationMap: selectorMap, - plainCss: _stylesheet.plainCss, - logger: _logger); + interpolationMap: selectorMap, plainCss: _stylesheet.plainCss); var nest = !(_styleRule?.fromPlainCss ?? false); if (nest) { diff --git a/pkg/sass-parser/CHANGELOG.md b/pkg/sass-parser/CHANGELOG.md index 96134c138..968d6d72b 100644 --- a/pkg/sass-parser/CHANGELOG.md +++ b/pkg/sass-parser/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0 + +* **Breaking change:** Warnings are no longer emitted during parsing, so the + `logger` option has been removed from `SassParserOptions`. + ## 0.3.2 * No user-visible changes. diff --git a/pkg/sass-parser/lib/index.ts b/pkg/sass-parser/lib/index.ts index 057f7881d..5746f98e4 100644 --- a/pkg/sass-parser/lib/index.ts +++ b/pkg/sass-parser/lib/index.ts @@ -75,11 +75,7 @@ export { } from './src/statement'; /** Options that can be passed to the Sass parsers to control their behavior. */ -export interface SassParserOptions - extends Pick { - /** The logger that's used to log messages encountered during parsing. */ - logger?: sassApi.Logger; -} +export type SassParserOptions = Pick; /** A PostCSS syntax for parsing a particular Sass syntax. */ export interface Syntax extends postcss.Syntax { @@ -105,7 +101,7 @@ class _Syntax implements Syntax { return new Root( undefined, - sassInternal.parse(css.toString(), this.#syntax, opts?.from, opts?.logger) + sassInternal.parse(css.toString(), this.#syntax, opts?.from) ); } diff --git a/pkg/sass-parser/lib/src/sass-internal.ts b/pkg/sass-parser/lib/src/sass-internal.ts index f48fbcd06..3ae2064ba 100644 --- a/pkg/sass-parser/lib/src/sass-internal.ts +++ b/pkg/sass-parser/lib/src/sass-internal.ts @@ -28,12 +28,7 @@ export interface SourceFile { // There may be a better way to declare this, but I can't figure it out. // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace SassInternal { - function parse( - css: string, - syntax: Syntax, - path?: string, - logger?: sass.Logger - ): Stylesheet; + function parse(css: string, syntax: Syntax, path?: string): Stylesheet; class StatementVisitor { private _fakePropertyToMakeThisAUniqueType1: T; diff --git a/pkg/sass-parser/package.json b/pkg/sass-parser/package.json index 23408b94c..f3c720e27 100644 --- a/pkg/sass-parser/package.json +++ b/pkg/sass-parser/package.json @@ -1,6 +1,6 @@ { "name": "sass-parser", - "version": "0.3.2", + "version": "0.4.0", "description": "A PostCSS-compatible wrapper of the official Sass parser", "repository": "sass/sass", "author": "Google Inc.", diff --git a/pkg/sass_api/CHANGELOG.md b/pkg/sass_api/CHANGELOG.md index 5d7f7666b..0621a0d79 100644 --- a/pkg/sass_api/CHANGELOG.md +++ b/pkg/sass_api/CHANGELOG.md @@ -1,18 +1,37 @@ +## 14.0.0 + +* **Breaking change:** Warnings are no longer emitted during parsing, so the + `logger` parameter has been removed from the following: + + * `ArgumentDeclaration.parse()`, `ComplexSelector.parse()`, + `CompoundSelector.parse()`, `Expression.parse()`, `SelectorList.parse()`, + and `SimpleSelector.parse()`. + + * `Stylesheet.parse()`, `Stylesheet.parseCss()`, `Stylesheet.parseSass()`, + and `Stylesheet.parseScss()`. + + * The `AsyncImportCache` and `ImportCache` constructors. + + Additionally, the `quiet` parameter has been removed from + `AsyncImportCache.importCanonical()` and `ImportCache.importCanonical()`, and + `AsyncImportCache.wrapLogger()` and `ImportCache.wrapLogger()` have been + removed entirely. + ## 13.1.2 * No user-visible changes. ## 13.1.1 -* Make `AsyncImporterCache.wrapLogger()` and `ImporterCache.wrapLogger()` always +* Make `AsyncImportCache.wrapLogger()` and `ImportCache.wrapLogger()` always limit the repetition of deprecations. this is unlikely to be the long-term behavior, but it's necessary to avoid flooding users with deprecations in the short term. ## 13.1.0 -* Add `AsyncImporterCache.wrapLogger()` and `ImporterCache.wrapLogger()` - methods, which wrap a given logger to apply deprecation options to it. +* Add `AsyncImportCache.wrapLogger()` and `ImportCache.wrapLogger()` methods, + which wrap a given logger to apply deprecation options to it. ## 13.0.1 diff --git a/pkg/sass_api/lib/sass_api.dart b/pkg/sass_api/lib/sass_api.dart index b0369f908..7190a49fd 100644 --- a/pkg/sass_api/lib/sass_api.dart +++ b/pkg/sass_api/lib/sass_api.dart @@ -7,7 +7,6 @@ library sass; // ignore_for_file: implementation_imports -import 'package:sass/sass.dart'; import 'package:sass/src/parse/parser.dart'; export 'package:sass/sass.dart'; @@ -36,11 +35,9 @@ export 'package:sass/src/visitor/statement_search.dart'; /// Throws a [SassFormatException] if parsing fails. /// /// {@category Parsing} -String parseIdentifier(String text) => - Parser.parseIdentifier(text, logger: Logger.quiet); +String parseIdentifier(String text) => Parser.parseIdentifier(text); /// Returns whether [text] is a valid CSS identifier. /// /// {@category Parsing} -bool isIdentifier(String text) => - Parser.isIdentifier(text, logger: Logger.quiet); +bool isIdentifier(String text) => Parser.isIdentifier(text); diff --git a/pkg/sass_api/pubspec.yaml b/pkg/sass_api/pubspec.yaml index 93d17af15..653bdb91f 100644 --- a/pkg/sass_api/pubspec.yaml +++ b/pkg/sass_api/pubspec.yaml @@ -2,7 +2,7 @@ name: sass_api # Note: Every time we add a new Sass AST node, we need to bump the *major* # version because it's a breaking change for anyone who's implementing the # visitor interface(s). -version: 13.1.2 +version: 14.0.0 description: Additional APIs for Dart Sass. homepage: https://github.com/sass/dart-sass @@ -10,7 +10,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - sass: 1.80.2 + sass: 1.80.3 dev_dependencies: dartdoc: ^8.0.14 diff --git a/pubspec.yaml b/pubspec.yaml index 448495da0..e63fe63e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.80.2 +version: 1.80.3-dev description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass diff --git a/test/dart_api/logger_test.dart b/test/dart_api/logger_test.dart index 2c069a693..dbd3f205e 100644 --- a/test/dart_api/logger_test.dart +++ b/test/dart_api/logger_test.dart @@ -76,7 +76,7 @@ void main() { }); }); - test("with a parser warning passes the message and span", () { + test("with a parser warning passes the message, span, and trace", () { var mustBeCalled = expectAsync0(() {}); compileString('a {b: c && d}', logger: _TestLogger.withWarn((message, {span, trace, deprecation = false}) { @@ -87,7 +87,7 @@ void main() { expect(span.end.line, equals(0)); expect(span.end.column, equals(10)); - expect(trace, isNull); + expect(trace!.frames.first.member, equals('root stylesheet')); expect(deprecation, isFalse); mustBeCalled(); })); diff --git a/test/deprecations_test.dart b/test/deprecations_test.dart index 9b3594c09..5841ea323 100644 --- a/test/deprecations_test.dart +++ b/test/deprecations_test.dart @@ -17,7 +17,7 @@ void main() { // Deprecated in 1.3.2 test("elseIf is violated by using @elseif instead of @else if", () { - _expectDeprecation("@if false {} @elseif {}", Deprecation.elseif); + _expectDeprecation("@if false {} @elseif false {}", Deprecation.elseif); }); // Deprecated in 1.7.2 diff --git a/test/embedded/protocol_test.dart b/test/embedded/protocol_test.dart index 1744f754e..c7473ac69 100644 --- a/test/embedded/protocol_test.dart +++ b/test/embedded/protocol_test.dart @@ -368,12 +368,13 @@ void main() { var logEvent = await getLogEvent(process); expect( logEvent.formatted, - equals('WARNING on line 1, column 13: \n' - 'In Sass, "&&" means two copies of the parent selector. You probably want to use "and" instead.\n' + equals('WARNING: In Sass, "&&" means two copies of the parent ' + 'selector. You probably want to use "and" instead.\n\n' ' ,\n' '1 | a {@debug a && b}\n' ' | ^^\n' - ' \'\n')); + ' \'\n' + ' - 1:13 root stylesheet\n')); await process.kill(); }); }); @@ -394,7 +395,7 @@ void main() { expect(logEvent.span.start, equals(location(12, 0, 12))); expect(logEvent.span.end, equals(location(19, 0, 19))); expect(logEvent.span.context, equals("@if true {} @elseif true {}")); - expect(logEvent.stackTrace, isEmpty); + expect(logEvent.stackTrace, "- 1:13 root stylesheet\n"); await process.kill(); });