From d6f319f5b455b6da97b2fe9d9e5deb33569e5869 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Sat, 30 Mar 2019 16:44:06 -0700 Subject: [PATCH] Clone CSS after importing it --- lib/src/async_environment.dart | 5 +++++ lib/src/async_module.dart | 6 ++++++ lib/src/environment.dart | 7 ++++++- lib/src/extend/extender.dart | 1 + lib/src/module.dart | 8 +++++++- lib/src/visitor/async_evaluate.dart | 26 +++++++++++++++++--------- lib/src/visitor/evaluate.dart | 28 ++++++++++++++++++---------- 7 files changed, 60 insertions(+), 21 deletions(-) diff --git a/lib/src/async_environment.dart b/lib/src/async_environment.dart index df5d8852..7bc7e824 100644 --- a/lib/src/async_environment.dart +++ b/lib/src/async_environment.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; import 'ast/css.dart'; @@ -621,6 +622,8 @@ class AsyncEnvironment { /// A module that represents the top-level members defined in an [Environment]. class _EnvironmentModule implements AsyncModule { + Uri get url => css.span.sourceUrl; + final List upstream; final Map variables; final Map variableNodes; @@ -666,4 +669,6 @@ class _EnvironmentModule implements AsyncModule { return _EnvironmentModule( _environment, newCssAndExtender.item1, newCssAndExtender.item2); } + + String toString() => p.prettyUri(css.span.sourceUrl); } diff --git a/lib/src/async_module.dart b/lib/src/async_module.dart index c3e55f04..70b9a26e 100644 --- a/lib/src/async_module.dart +++ b/lib/src/async_module.dart @@ -12,6 +12,12 @@ import 'value.dart'; /// The interface for a Sass module. abstract class AsyncModule { + /// The canonical URL for this module's source file. + /// + /// This may be `null` if the module was loaded from a string without a URL + /// provided. + Uri get url; + /// Modules that this module uses. List get upstream; diff --git a/lib/src/environment.dart b/lib/src/environment.dart index 293339cc..7d4eb35d 100644 --- a/lib/src/environment.dart +++ b/lib/src/environment.dart @@ -5,10 +5,11 @@ // DO NOT EDIT. This file was generated from async_environment.dart. // See tool/synchronize.dart for details. // -// Checksum: 36876e7e932a30409d59d0fa256ad02d25934aab +// Checksum: 097eb94cd15103bf4bef739a61e61414db4b55b1 // // ignore_for_file: unused_import +import 'package:path/path.dart' as p; import 'package:source_span/source_span.dart'; import 'ast/css.dart'; @@ -623,6 +624,8 @@ class Environment { /// A module that represents the top-level members defined in an [Environment]. class _EnvironmentModule implements Module { + Uri get url => css.span.sourceUrl; + final List upstream; final Map variables; final Map variableNodes; @@ -668,4 +671,6 @@ class _EnvironmentModule implements Module { return _EnvironmentModule( _environment, newCssAndExtender.item1, newCssAndExtender.item2); } + + String toString() => p.prettyUri(css.span.sourceUrl); } diff --git a/lib/src/extend/extender.dart b/lib/src/extend/extender.dart index 5a158946..40af8a73 100644 --- a/lib/src/extend/extender.dart +++ b/lib/src/extend/extender.dart @@ -398,6 +398,7 @@ class Extender { Map> newExtensions; for (var extender in extenders) { + if (extender.isEmpty) continue; extender._extensions.forEach((target, newSources) { // Private selectors can't be extended across module boundaries. if (target is PlaceholderSelector && target.isPrivate) return; diff --git a/lib/src/module.dart b/lib/src/module.dart index f8da968b..5fb0fc53 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_module.dart. // See tool/synchronize.dart for details. // -// Checksum: e85903dbb32318c558ca4132a78dca55ee617280 +// Checksum: 895440529b78f1ef2830cf105af00dba0755e947 // // ignore_for_file: unused_import @@ -19,6 +19,12 @@ import 'value.dart'; /// The interface for a Sass module. abstract class Module { + /// The canonical URL for this module's source file. + /// + /// This may be `null` if the module was loaded from a string without a URL + /// provided. + Uri get url; + /// Modules that this module uses. List get upstream; diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index 1894e020..0e9179c3 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -439,7 +439,10 @@ class _EvaluateVisitor /// modules transitively used by [root]. /// /// This also applies each module's extensions to its upstream modules. - CssStylesheet _combineCss(AsyncModule root) { + /// + /// If [clone] is `true`, this will copy the modules before extending them so + /// that they don't modify [root] or its dependencies. + CssStylesheet _combineCss(AsyncModule root, {bool clone = false}) { // TODO(nweiz): short-circuit if no upstream modules (transitively) include // any CSS. if (root.upstream.isEmpty) { @@ -454,6 +457,9 @@ class _EvaluateVisitor } 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 @@ -476,11 +482,11 @@ class _EvaluateVisitor /// Extends the selectors in each module with the extensions defined in /// downstream modules. void _extendModules(List sortedModules) { - // All the extenders directly downstream of a given module. It's important - // that we create this in topological order, so that by the time we're - // processing a module we've already filled in all its downstream extenders - // and we can use them to extend that module. - var downstreamExtenders = >{}; + // All the extenders 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 in all + // its downstream extenders and we can use them to extend that module. + var downstreamExtenders = >{}; /// Extensions that haven't yet been satisfied by some upstream module. This /// adds extensions when they're defined but not satisfied, and removes them @@ -498,13 +504,13 @@ class _EvaluateVisitor unsatisfiedExtensions.addAll(module.extender.extensionsWhereTarget( (target) => !originalSelectors.contains(target))); - var extenders = downstreamExtenders[module]; + var extenders = downstreamExtenders[module.url]; if (extenders != null) module.extender.addExtensions(extenders); if (module.extender.isEmpty) continue; for (var upstream in module.upstream) { downstreamExtenders - .putIfAbsent(upstream, () => []) + .putIfAbsent(upstream.url, () => []) .add(module.extender); } @@ -1026,7 +1032,9 @@ class _EvaluateVisitor // the CSS from modules used by [stylesheet]. var module = environment.toModule( CssStylesheet(const [], stylesheet.span), Extender.empty); - if (module.transitivelyContainsCss) await _combineCss(module).accept(this); + if (module.transitivelyContainsCss) { + await _combineCss(module, clone: true).accept(this); + } var visitor = _ImportedCssVisitor(this); for (var child in children) { diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index e5a671ef..92a0e919 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/synchronize.dart for details. // -// Checksum: c7d7b5c49b3b2d09edd180440fc10582f53fdafb +// Checksum: 0f4f00db38a7f2a60771d3d50652cbfdeb322e52 // // ignore_for_file: unused_import @@ -447,7 +447,10 @@ class _EvaluateVisitor /// modules transitively used by [root]. /// /// This also applies each module's extensions to its upstream modules. - CssStylesheet _combineCss(Module root) { + /// + /// If [clone] is `true`, this will copy the modules before extending them so + /// that they don't modify [root] or its dependencies. + CssStylesheet _combineCss(Module root, {bool clone = false}) { // TODO(nweiz): short-circuit if no upstream modules (transitively) include // any CSS. if (root.upstream.isEmpty) { @@ -462,6 +465,9 @@ class _EvaluateVisitor } 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 @@ -484,11 +490,11 @@ class _EvaluateVisitor /// Extends the selectors in each module with the extensions defined in /// downstream modules. void _extendModules(List sortedModules) { - // All the extenders directly downstream of a given module. It's important - // that we create this in topological order, so that by the time we're - // processing a module we've already filled in all its downstream extenders - // and we can use them to extend that module. - var downstreamExtenders = >{}; + // All the extenders 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 in all + // its downstream extenders and we can use them to extend that module. + var downstreamExtenders = >{}; /// Extensions that haven't yet been satisfied by some upstream module. This /// adds extensions when they're defined but not satisfied, and removes them @@ -506,13 +512,13 @@ class _EvaluateVisitor unsatisfiedExtensions.addAll(module.extender.extensionsWhereTarget( (target) => !originalSelectors.contains(target))); - var extenders = downstreamExtenders[module]; + var extenders = downstreamExtenders[module.url]; if (extenders != null) module.extender.addExtensions(extenders); if (module.extender.isEmpty) continue; for (var upstream in module.upstream) { downstreamExtenders - .putIfAbsent(upstream, () => []) + .putIfAbsent(upstream.url, () => []) .add(module.extender); } @@ -1029,7 +1035,9 @@ class _EvaluateVisitor // the CSS from modules used by [stylesheet]. var module = environment.toModule( CssStylesheet(const [], stylesheet.span), Extender.empty); - if (module.transitivelyContainsCss) _combineCss(module).accept(this); + if (module.transitivelyContainsCss) { + _combineCss(module, clone: true).accept(this); + } var visitor = _ImportedCssVisitor(this); for (var child in children) {