diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee952d6d..aa6f72533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.64.0 + +* Comments that appear before or between `@use` and `@forward` rules are now + emitted in source order as much as possible, instead of always being emitted + after the CSS of all module dependencies. + ## 1.63.6 ### JavaScript API diff --git a/lib/src/ast/css/modifiable/node.dart b/lib/src/ast/css/modifiable/node.dart index 057040975..8cfd85579 100644 --- a/lib/src/ast/css/modifiable/node.dart +++ b/lib/src/ast/css/modifiable/node.dart @@ -81,4 +81,13 @@ abstract class ModifiableCssParentNode extends ModifiableCssNode child._indexInParent = _children.length; _children.add(child); } + + /// Destructively removes all elements from [children]. + void clearChildren() { + for (var child in _children) { + child._parent = null; + child._indexInParent = null; + } + _children.clear(); + } } diff --git a/lib/src/async_environment.dart b/lib/src/async_environment.dart index e6865b0ad..3df9de9ff 100644 --- a/lib/src/async_environment.dart +++ b/lib/src/async_environment.dart @@ -802,11 +802,14 @@ class AsyncEnvironment { } /// Returns a module that represents the top-level members defined in [this], - /// that contains [css] as its CSS tree, which can be extended using - /// [extensionStore]. - Module toModule(CssStylesheet css, ExtensionStore extensionStore) { + /// that contains [css] and [preModuleComments] as its CSS, which can be + /// extended using [extensionStore]. + Module toModule( + CssStylesheet css, + Map> preModuleComments, + ExtensionStore extensionStore) { assert(atRoot); - return _EnvironmentModule(this, css, extensionStore, + return _EnvironmentModule(this, css, preModuleComments, extensionStore, forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); } @@ -821,6 +824,7 @@ class AsyncEnvironment { this, CssStylesheet(const [], SourceFile.decoded(const [], url: "").span(0)), + const {}, ExtensionStore.empty, forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); } @@ -902,6 +906,7 @@ class _EnvironmentModule implements Module { final Map mixins; final ExtensionStore extensionStore; final CssStylesheet css; + final Map> preModuleComments; final bool transitivelyContainsCss; final bool transitivelyContainsExtensions; @@ -916,13 +921,20 @@ class _EnvironmentModule implements Module { /// defined at all. final Map _modulesByVariable; - factory _EnvironmentModule(AsyncEnvironment environment, CssStylesheet css, + factory _EnvironmentModule( + AsyncEnvironment environment, + CssStylesheet css, + Map> preModuleComments, ExtensionStore extensionStore, {Set? forwarded}) { forwarded ??= const {}; return _EnvironmentModule._( environment, css, + Map.unmodifiable({ + for (var entry in preModuleComments.entries) + entry.key: List.unmodifiable(entry.value) + }), extensionStore, _makeModulesByVariable(forwarded), _memberMap(environment._variables.first, @@ -934,6 +946,7 @@ class _EnvironmentModule implements Module { _memberMap(environment._mixins.first, forwarded.map((module) => module.mixins)), transitivelyContainsCss: css.children.isNotEmpty || + preModuleComments.isNotEmpty || environment._allModules .any((module) => module.transitivelyContainsCss), transitivelyContainsExtensions: !extensionStore.isEmpty || @@ -981,6 +994,7 @@ class _EnvironmentModule implements Module { _EnvironmentModule._( this._environment, this.css, + this.preModuleComments, this.extensionStore, this._modulesByVariable, this.variables, @@ -1020,6 +1034,7 @@ class _EnvironmentModule implements Module { return _EnvironmentModule._( _environment, newCssAndExtensionStore.item1, + preModuleComments, newCssAndExtensionStore.item2, _modulesByVariable, variables, diff --git a/lib/src/environment.dart b/lib/src/environment.dart index 128bd3285..83cd9122c 100644 --- a/lib/src/environment.dart +++ b/lib/src/environment.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_environment.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: 38c688423116df1e489aa6eafc16de1bf9bc2bf5 +// Checksum: 487c34d7387b6f368ed60ff2db6a748483aba2a1 // // ignore_for_file: unused_import @@ -808,11 +808,14 @@ class Environment { } /// Returns a module that represents the top-level members defined in [this], - /// that contains [css] as its CSS tree, which can be extended using - /// [extensionStore]. - Module toModule(CssStylesheet css, ExtensionStore extensionStore) { + /// that contains [css] and [preModuleComments] as its CSS, which can be + /// extended using [extensionStore]. + Module toModule( + CssStylesheet css, + Map, List> preModuleComments, + ExtensionStore extensionStore) { assert(atRoot); - return _EnvironmentModule(this, css, extensionStore, + return _EnvironmentModule(this, css, preModuleComments, extensionStore, forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); } @@ -827,6 +830,7 @@ class Environment { this, CssStylesheet(const [], SourceFile.decoded(const [], url: "").span(0)), + const {}, ExtensionStore.empty, forwarded: _forwardedModules.andThen((modules) => MapKeySet(modules))); } @@ -909,6 +913,7 @@ class _EnvironmentModule implements Module { final Map mixins; final ExtensionStore extensionStore; final CssStylesheet css; + final Map, List> preModuleComments; final bool transitivelyContainsCss; final bool transitivelyContainsExtensions; @@ -924,12 +929,19 @@ class _EnvironmentModule implements Module { final Map> _modulesByVariable; factory _EnvironmentModule( - Environment environment, CssStylesheet css, ExtensionStore extensionStore, + Environment environment, + CssStylesheet css, + Map, List> preModuleComments, + ExtensionStore extensionStore, {Set>? forwarded}) { forwarded ??= const {}; return _EnvironmentModule._( environment, css, + Map.unmodifiable({ + for (var entry in preModuleComments.entries) + entry.key: List.unmodifiable(entry.value) + }), extensionStore, _makeModulesByVariable(forwarded), _memberMap(environment._variables.first, @@ -941,6 +953,7 @@ class _EnvironmentModule implements Module { _memberMap(environment._mixins.first, forwarded.map((module) => module.mixins)), transitivelyContainsCss: css.children.isNotEmpty || + preModuleComments.isNotEmpty || environment._allModules .any((module) => module.transitivelyContainsCss), transitivelyContainsExtensions: !extensionStore.isEmpty || @@ -989,6 +1002,7 @@ class _EnvironmentModule implements Module { _EnvironmentModule._( this._environment, this.css, + this.preModuleComments, this.extensionStore, this._modulesByVariable, this.variables, @@ -1028,6 +1042,7 @@ class _EnvironmentModule implements Module { return _EnvironmentModule._( _environment, newCssAndExtensionStore.item1, + preModuleComments, newCssAndExtensionStore.item2, _modulesByVariable, variables, diff --git a/lib/src/module.dart b/lib/src/module.dart index 8d570ade6..b3f6baa16 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -53,6 +53,10 @@ abstract class Module { /// The module's CSS tree. CssStylesheet get css; + /// A map from modules in [upstream] to loud comments written in this module + /// that should be emitted before the given module. + Map, List> get preModuleComments; + /// Whether this module *or* any modules in [upstream] contain any CSS. bool get transitivelyContainsCss; diff --git a/lib/src/module/built_in.dart b/lib/src/module/built_in.dart index 1c5a56ed1..82821ad81 100644 --- a/lib/src/module/built_in.dart +++ b/lib/src/module/built_in.dart @@ -23,6 +23,7 @@ class BuiltInModule implements Module { Map get variableNodes => const {}; ExtensionStore get extensionStore => ExtensionStore.empty; CssStylesheet get css => CssStylesheet.empty(url: url); + Map, List> get preModuleComments => const {}; bool get transitivelyContainsCss => false; bool get transitivelyContainsExtensions => false; diff --git a/lib/src/module/forwarded_view.dart b/lib/src/module/forwarded_view.dart index 4540702a9..90bc80532 100644 --- a/lib/src/module/forwarded_view.dart +++ b/lib/src/module/forwarded_view.dart @@ -25,6 +25,8 @@ class ForwardedModuleView implements Module { List> get upstream => _inner.upstream; ExtensionStore get extensionStore => _inner.extensionStore; CssStylesheet get css => _inner.css; + Map, List> get preModuleComments => + _inner.preModuleComments; bool get transitivelyContainsCss => _inner.transitivelyContainsCss; bool get transitivelyContainsExtensions => _inner.transitivelyContainsExtensions; diff --git a/lib/src/module/shadowed_view.dart b/lib/src/module/shadowed_view.dart index cee4ef0d7..bb9bb2d0e 100644 --- a/lib/src/module/shadowed_view.dart +++ b/lib/src/module/shadowed_view.dart @@ -22,6 +22,8 @@ class ShadowedModuleView implements Module { List> get upstream => _inner.upstream; ExtensionStore get extensionStore => _inner.extensionStore; CssStylesheet get css => _inner.css; + Map, List> get preModuleComments => + _inner.preModuleComments; bool get transitivelyContainsCss => _inner.transitivelyContainsCss; bool get transitivelyContainsExtensions => _inner.transitivelyContainsExtensions; diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 1f2e1e1f2..8901abe60 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -3,6 +3,7 @@ // https://opensource.org/licenses/MIT. import 'dart:async'; +import 'dart:collection'; import 'dart:math' as math; import 'package:charcode/charcode.dart'; @@ -306,6 +307,13 @@ class _EvaluateVisitor /// stylesheet. List? _outOfOrderImports; + /// A map from modules loaded by the current module to loud comments written + /// in this module that should appear before the loaded module. + /// + /// This is `null` unless there are any pre-module comments in the current + /// stylesheet. + Map>? _preModuleComments; + /// The extension store that tracks extensions and style rules for the current /// module. ExtensionStore get _extensionStore => @@ -498,7 +506,7 @@ class _EvaluateVisitor } await _loadModule(url, "load-css()", callableNode, - (module) => _combineCss(module, clone: true).accept(this), + (module, _) => _combineCss(module, clone: true).accept(this), baseUrl: callableNode.span.sourceUrl, configuration: configuration, namesInErrors: true); @@ -579,7 +587,9 @@ class _EvaluateVisitor } } - /// Loads the module at [url] and passes it to [callback]. + /// Loads the module at [url] and passes it to [callback], along with a + /// boolean indicating whether this is the first time the module has been + /// loaded. /// /// This first tries loading [url] relative to [baseUrl], which defaults to /// `_stylesheet.span.sourceUrl`. @@ -596,7 +606,7 @@ class _EvaluateVisitor /// The [stackFrame] and [nodeWithSpan] are used for the name and location of /// the stack frame for the duration of the [callback]. Future _loadModule(Uri url, String stackFrame, AstNode nodeWithSpan, - FutureOr callback(Module module), + FutureOr callback(Module module, bool firstLoad), {Uri? baseUrl, Configuration? configuration, bool namesInErrors = false}) async { @@ -610,7 +620,10 @@ class _EvaluateVisitor configuration.nodeWithSpan.span); } - await _addExceptionSpanAsync(nodeWithSpan, () => callback(builtInModule)); + // Always consider built-in stylesheets to be "already loaded", since they + // never require additional execution to load and never produce CSS. + await _addExceptionSpanAsync( + nodeWithSpan, () => callback(builtInModule, false)); return; } @@ -633,6 +646,7 @@ class _EvaluateVisitor } if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan; + var firstLoad = !_modules.containsKey(canonicalUrl); var oldInDependency = _inDependency; _inDependency = result.isDependency; Module module; @@ -646,7 +660,8 @@ class _EvaluateVisitor _inDependency = oldInDependency; } - await _addExceptionSpanAsync(nodeWithSpan, () => callback(module), + await _addExceptionSpanAsync( + nodeWithSpan, () => callback(module, firstLoad), addStackFrame: false); }); } @@ -695,11 +710,13 @@ class _EvaluateVisitor var environment = AsyncEnvironment(); late CssStylesheet css; + late Map>? preModuleComments; var extensionStore = ExtensionStore(); await _withEnvironment(environment, () async { var oldImporter = _importer; var oldStylesheet = __stylesheet; var oldRoot = __root; + var oldPreModuleComments = _preModuleComments; var oldParent = __parent; var oldEndOfImports = __endOfImports; var oldOutOfOrderImports = _outOfOrderImports; @@ -730,10 +747,12 @@ class _EvaluateVisitor css = _outOfOrderImports == null ? root : CssStylesheet(_addOutOfOrderImports(), stylesheet.span); + preModuleComments = _preModuleComments; _importer = oldImporter; __stylesheet = oldStylesheet; __root = oldRoot; + _preModuleComments = oldPreModuleComments; __parent = oldParent; __endOfImports = oldEndOfImports; _outOfOrderImports = oldOutOfOrderImports; @@ -747,7 +766,8 @@ class _EvaluateVisitor _configuration = oldConfiguration; }); - var module = environment.toModule(css, extensionStore); + var module = environment.toModule( + css, preModuleComments ?? const {}, extensionStore); if (url != null) { _modules[url] = module; _moduleConfigurations[url] = _configuration; @@ -789,12 +809,6 @@ class _EvaluateVisitor return root.css; } - var sortedModules = _topologicalModules(root); - if (clone) { - sortedModules = sortedModules.map((module) => module.cloneCss()).toList(); - } - _extendModules(sortedModules); - // The imports (and comments between them) that should be included at the // beginning of the final document. var imports = []; @@ -802,19 +816,47 @@ class _EvaluateVisitor // The CSS statements in the final document. var css = []; - for (var module in sortedModules.reversed) { + /// The modules in reverse topological order. + var sorted = Queue(); + + /// The modules that have been visited so far. Note that if [cloneCss] is + /// true, this contains the original modules, not the copies. + var seen = {}; + + void visitModule(Module module) { + if (!seen.add(module)) return; + if (clone) module = module.cloneCss(); + + for (var upstream in module.upstream) { + if (upstream.transitivelyContainsCss) { + var comments = module.preModuleComments[upstream]; + if (comments != null) { + // Intermix the top-level comments with plain CSS `@import`s until we + // start to have actual CSS defined, at which point start treating it as + // normal CSS. + (css.isEmpty ? imports : css).addAll(comments); + } + visitModule(upstream); + } + } + + sorted.addFirst(module); var statements = module.css.children; var index = _indexAfterImports(statements); imports.addAll(statements.getRange(0, index)); css.addAll(statements.getRange(index, statements.length)); } + visitModule(root); + + if (root.transitivelyContainsExtensions) _extendModules(sorted); + return CssStylesheet(imports + css, root.css.span); } - /// Extends the selectors in each module with the extensions defined in - /// downstream modules. - void _extendModules(List sortedModules) { + /// Destructively updates the selectors in each module with the extensions + /// defined in downstream modules. + void _extendModules(Iterable sortedModules) { // All the [ExtensionStore]s directly downstream of a given module (indexed // by its canonical URL). It's important that we create this in topological // order, so that by the time we're processing a module we've already filled @@ -872,33 +914,6 @@ class _EvaluateVisitor extension.span); } - /// Returns all modules transitively used by [root] in topological order, - /// ignoring modules that contain no CSS. - List _topologicalModules(Module root) { - // Construct a topological ordering using depth-first traversal, as in - // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search. - var seen = {}; - var sorted = QueueList(); - - void visitModule(Module module) { - // Each module is added to the beginning of [sorted], which means the - // returned list contains sibling modules in the opposite order of how - // they appear in the document. Then when the list is reversed to generate - // the CSS, they're put back in their original order. - for (var upstream in module.upstream) { - if (upstream.transitivelyContainsCss && seen.add(upstream)) { - visitModule(upstream); - } - } - - sorted.addFirst(module); - } - - visitModule(root); - - return sorted; - } - /// Returns the index of the first node in [statements] that comes after all /// static imports. int _indexAfterImports(List statements) { @@ -1347,7 +1362,8 @@ class _EvaluateVisitor var newConfiguration = await _addForwardConfiguration(adjustedConfiguration, node); - await _loadModule(node.url, "@forward", node, (module) { + await _loadModule(node.url, "@forward", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }, configuration: newConfiguration); @@ -1371,7 +1387,8 @@ class _EvaluateVisitor _assertConfigurationIsEmpty(newConfiguration); } else { _configuration = adjustedConfiguration; - await _loadModule(node.url, "@forward", node, (module) { + await _loadModule(node.url, "@forward", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }); _configuration = oldConfiguration; @@ -1409,6 +1426,20 @@ class _EvaluateVisitor } } + /// Adds any comments in [_root.children] to [_preModuleComments] for + /// [module]. + void _registerCommentsForModule(Module module) { + // If we're not in a module (for example, we're evaluating a line of code + // for the repl), there's nothing to register comments for. + if (__root == null) return; + if (_root.children.isEmpty || !module.transitivelyContainsCss) return; + (_preModuleComments ??= {}) + .putIfAbsent(module, () => []) + .addAll(_root.children.cast()); + _root.clearChildren(); + _endOfImports = 0; + } + /// Remove configured values from [upstream] that have been removed from /// [downstream], unless they match a name in [except]. void _removeUsedConfiguration( @@ -2118,7 +2149,8 @@ class _EvaluateVisitor configuration = ExplicitConfiguration(values, node); } - await _loadModule(node.url, "@use", node, (module) { + await _loadModule(node.url, "@use", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.addModule(module, node, namespace: node.namespace); }, configuration: configuration); _assertConfigurationIsEmpty(configuration); diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index 0226cb01e..893aed174 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,13 +5,14 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/grind/synchronize.dart for details. // -// Checksum: fa9a95be772a0bb1e769db23a3d794c6c55148d4 +// Checksum: 0f0705b9773f694816ebf00cc91f43146d8c46ab // // ignore_for_file: unused_import import 'async_evaluate.dart' show EvaluateResult; export 'async_evaluate.dart' show EvaluateResult; +import 'dart:collection'; import 'dart:math' as math; import 'package:charcode/charcode.dart'; @@ -314,6 +315,13 @@ class _EvaluateVisitor /// stylesheet. List? _outOfOrderImports; + /// A map from modules loaded by the current module to loud comments written + /// in this module that should appear before the loaded module. + /// + /// This is `null` unless there are any pre-module comments in the current + /// stylesheet. + Map, List>? _preModuleComments; + /// The extension store that tracks extensions and style rules for the current /// module. ExtensionStore get _extensionStore => @@ -503,7 +511,7 @@ class _EvaluateVisitor } _loadModule(url, "load-css()", callableNode, - (module) => _combineCss(module, clone: true).accept(this), + (module, _) => _combineCss(module, clone: true).accept(this), baseUrl: callableNode.span.sourceUrl, configuration: configuration, namesInErrors: true); @@ -583,7 +591,9 @@ class _EvaluateVisitor } } - /// Loads the module at [url] and passes it to [callback]. + /// Loads the module at [url] and passes it to [callback], along with a + /// boolean indicating whether this is the first time the module has been + /// loaded. /// /// This first tries loading [url] relative to [baseUrl], which defaults to /// `_stylesheet.span.sourceUrl`. @@ -600,7 +610,7 @@ class _EvaluateVisitor /// The [stackFrame] and [nodeWithSpan] are used for the name and location of /// the stack frame for the duration of the [callback]. void _loadModule(Uri url, String stackFrame, AstNode nodeWithSpan, - void callback(Module module), + void callback(Module module, bool firstLoad), {Uri? baseUrl, Configuration? configuration, bool namesInErrors = false}) { @@ -614,7 +624,9 @@ class _EvaluateVisitor configuration.nodeWithSpan.span); } - _addExceptionSpan(nodeWithSpan, () => callback(builtInModule)); + // Always consider built-in stylesheets to be "already loaded", since they + // never require additional execution to load and never produce CSS. + _addExceptionSpan(nodeWithSpan, () => callback(builtInModule, false)); return; } @@ -637,6 +649,7 @@ class _EvaluateVisitor } if (canonicalUrl != null) _activeModules[canonicalUrl] = nodeWithSpan; + var firstLoad = !_modules.containsKey(canonicalUrl); var oldInDependency = _inDependency; _inDependency = result.isDependency; Module module; @@ -650,7 +663,7 @@ class _EvaluateVisitor _inDependency = oldInDependency; } - _addExceptionSpan(nodeWithSpan, () => callback(module), + _addExceptionSpan(nodeWithSpan, () => callback(module, firstLoad), addStackFrame: false); }); } @@ -699,11 +712,13 @@ class _EvaluateVisitor var environment = Environment(); late CssStylesheet css; + late Map, List>? preModuleComments; var extensionStore = ExtensionStore(); _withEnvironment(environment, () { var oldImporter = _importer; var oldStylesheet = __stylesheet; var oldRoot = __root; + var oldPreModuleComments = _preModuleComments; var oldParent = __parent; var oldEndOfImports = __endOfImports; var oldOutOfOrderImports = _outOfOrderImports; @@ -734,10 +749,12 @@ class _EvaluateVisitor css = _outOfOrderImports == null ? root : CssStylesheet(_addOutOfOrderImports(), stylesheet.span); + preModuleComments = _preModuleComments; _importer = oldImporter; __stylesheet = oldStylesheet; __root = oldRoot; + _preModuleComments = oldPreModuleComments; __parent = oldParent; __endOfImports = oldEndOfImports; _outOfOrderImports = oldOutOfOrderImports; @@ -751,7 +768,8 @@ class _EvaluateVisitor _configuration = oldConfiguration; }); - var module = environment.toModule(css, extensionStore); + var module = environment.toModule( + css, preModuleComments ?? const {}, extensionStore); if (url != null) { _modules[url] = module; _moduleConfigurations[url] = _configuration; @@ -793,12 +811,6 @@ class _EvaluateVisitor return root.css; } - var sortedModules = _topologicalModules(root); - if (clone) { - sortedModules = sortedModules.map((module) => module.cloneCss()).toList(); - } - _extendModules(sortedModules); - // The imports (and comments between them) that should be included at the // beginning of the final document. var imports = []; @@ -806,19 +818,47 @@ class _EvaluateVisitor // The CSS statements in the final document. var css = []; - for (var module in sortedModules.reversed) { + /// The modules in reverse topological order. + var sorted = Queue>(); + + /// The modules that have been visited so far. Note that if [cloneCss] is + /// true, this contains the original modules, not the copies. + var seen = >{}; + + void visitModule(Module module) { + if (!seen.add(module)) return; + if (clone) module = module.cloneCss(); + + for (var upstream in module.upstream) { + if (upstream.transitivelyContainsCss) { + var comments = module.preModuleComments[upstream]; + if (comments != null) { + // Intermix the top-level comments with plain CSS `@import`s until we + // start to have actual CSS defined, at which point start treating it as + // normal CSS. + (css.isEmpty ? imports : css).addAll(comments); + } + visitModule(upstream); + } + } + + sorted.addFirst(module); var statements = module.css.children; var index = _indexAfterImports(statements); imports.addAll(statements.getRange(0, index)); css.addAll(statements.getRange(index, statements.length)); } + visitModule(root); + + if (root.transitivelyContainsExtensions) _extendModules(sorted); + return CssStylesheet(imports + css, root.css.span); } - /// Extends the selectors in each module with the extensions defined in - /// downstream modules. - void _extendModules(List> sortedModules) { + /// Destructively updates the selectors in each module with the extensions + /// defined in downstream modules. + void _extendModules(Iterable> sortedModules) { // All the [ExtensionStore]s directly downstream of a given module (indexed // by its canonical URL). It's important that we create this in topological // order, so that by the time we're processing a module we've already filled @@ -876,33 +916,6 @@ class _EvaluateVisitor extension.span); } - /// Returns all modules transitively used by [root] in topological order, - /// ignoring modules that contain no CSS. - List> _topologicalModules(Module root) { - // Construct a topological ordering using depth-first traversal, as in - // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search. - var seen = >{}; - var sorted = QueueList>(); - - void visitModule(Module module) { - // Each module is added to the beginning of [sorted], which means the - // returned list contains sibling modules in the opposite order of how - // they appear in the document. Then when the list is reversed to generate - // the CSS, they're put back in their original order. - for (var upstream in module.upstream) { - if (upstream.transitivelyContainsCss && seen.add(upstream)) { - visitModule(upstream); - } - } - - sorted.addFirst(module); - } - - visitModule(root); - - return sorted; - } - /// Returns the index of the first node in [statements] that comes after all /// static imports. int _indexAfterImports(List statements) { @@ -1348,7 +1361,8 @@ class _EvaluateVisitor var newConfiguration = _addForwardConfiguration(adjustedConfiguration, node); - _loadModule(node.url, "@forward", node, (module) { + _loadModule(node.url, "@forward", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }, configuration: newConfiguration); @@ -1372,7 +1386,8 @@ class _EvaluateVisitor _assertConfigurationIsEmpty(newConfiguration); } else { _configuration = adjustedConfiguration; - _loadModule(node.url, "@forward", node, (module) { + _loadModule(node.url, "@forward", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.forwardModule(module, node); }); _configuration = oldConfiguration; @@ -1409,6 +1424,20 @@ class _EvaluateVisitor } } + /// Adds any comments in [_root.children] to [_preModuleComments] for + /// [module]. + void _registerCommentsForModule(Module module) { + // If we're not in a module (for example, we're evaluating a line of code + // for the repl), there's nothing to register comments for. + if (__root == null) return; + if (_root.children.isEmpty || !module.transitivelyContainsCss) return; + (_preModuleComments ??= {}) + .putIfAbsent(module, () => []) + .addAll(_root.children.cast()); + _root.clearChildren(); + _endOfImports = 0; + } + /// Remove configured values from [upstream] that have been removed from /// [downstream], unless they match a name in [except]. void _removeUsedConfiguration( @@ -2108,7 +2137,8 @@ class _EvaluateVisitor configuration = ExplicitConfiguration(values, node); } - _loadModule(node.url, "@use", node, (module) { + _loadModule(node.url, "@use", node, (module, firstLoad) { + if (firstLoad) _registerCommentsForModule(module); _environment.addModule(module, node, namespace: node.namespace); }, configuration: configuration); _assertConfigurationIsEmpty(configuration); diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 1b8aeefb4..f7e54a209 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -1401,6 +1401,7 @@ class _SerializeVisitor // (shespanigans?), since we're compressing all whitespace anyway. if (_isCompressed) return false; if (node is! CssComment) return false; + if (node.span.sourceUrl != previous.span.sourceUrl) return false; if (!previous.span.contains(node.span)) { return node.span.start.line == previous.span.end.line; diff --git a/pubspec.yaml b/pubspec.yaml index 6876e8df5..689e345f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.63.6 +version: 1.64.0-dev description: A Sass implementation in Dart. homepage: https://github.com/sass/dart-sass