@@ -25,21 +25,24 @@ class ModuleMigrator extends Migrator {
2525 final description = "Migrates stylesheets to the new module system." ;
2626
2727 /// Global variables defined at any time during the migrator run.
28- final _variables = normalizedMap <VariableDeclaration >();
28+ final _variables = normalizedMap <List < VariableDeclaration > >();
2929
3030 /// Global mixins defined at any time during the migrator run.
3131 final _mixins = normalizedMap <MixinRule >();
3232
3333 /// Global functions defined at any time during the migrator run.
3434 final _functions = normalizedMap <FunctionRule >();
3535
36+ /// Global variables declared with !default that could be configured.
37+ ///final _configurableVariables = normalizedMap<VariableDeclaration>();
38+
3639 /// Runs the module migrator on [entrypoint] and its dependencies and returns
3740 /// a map of migrated contents.
3841 ///
3942 /// If [migrateDependencies] is false, the migrator will still be run on
4043 /// dependencies, but they will be excluded from the resulting map.
4144 Map <Uri , String > migrateFile (Uri entrypoint) {
42- var migrated = _ModuleMigrationVisitor (this , entrypoint ).run ();
45+ var migrated = _ModuleMigrationVisitor (this ).run (entrypoint );
4346 if (! migrateDependencies) {
4447 migrated.removeWhere ((url, contents) => url != entrypoint);
4548 }
@@ -50,46 +53,61 @@ class ModuleMigrator extends Migrator {
5053class _ModuleMigrationVisitor extends MigrationVisitor {
5154 final ModuleMigrator migrator;
5255
53- /// Constructs a new module migration visitor.
54- ///
55- /// Note: We always set [migratedDependencies] to true since the module
56- /// migrator needs to always run on dependencies. The `migrateFile` method of
57- /// the module migrator will filter out the dependencies' migration results.
58- _ModuleMigrationVisitor (this .migrator, Uri url, {Map <Uri , String > migrated})
59- : super (url, true , migrated: migrated);
60-
61- _ModuleMigrationVisitor newInstance (Uri url) =>
62- _ModuleMigrationVisitor (migrator, url, migrated: migrated);
63-
6456 /// Namespaces of modules used in this stylesheet.
65- final _namespaces = < Uri , String > {};
57+ Map <Uri , String > _namespaces = {};
6658
6759 /// Set of additional use rules necessary for referencing members of
6860 /// implicit dependencies / built-in modules.
6961 ///
7062 /// This set contains the path provided in the use rule, not the canonical
7163 /// path (e.g. "a" rather than "dir/a.scss").
72- final _additionalUseRules = Set < String > ();
64+ Set < String > _additionalUseRules = Set ();
7365
74- /// Global variables declared with !default that could be configured.
75- final _configurableVariables = normalizedSet ();
66+ /// The URL of the current stylesheet.
67+ Uri _currentUrl;
68+
69+ /// The URL of the last stylesheet that was completely migrated.
70+ Uri _lastUrl;
7671
7772 /// Local variables, mixins, and functions for this migration.
7873 ///
7974 /// When at the top level of the stylesheet, this will be null.
8075 LocalScope _localScope;
8176
77+ /// Constructs a new module migration visitor.
78+ ///
79+ /// Note: We always set [migratedDependencies] to true since the module
80+ /// migrator needs to always run on dependencies. The `migrateFile` method of
81+ /// the module migrator will filter out the dependencies' migration results.
82+ _ModuleMigrationVisitor (this .migrator) : super (migrateDependencies: true );
83+
8284 /// Returns the migrated contents of this stylesheet, based on [patches] and
8385 /// [_additionalUseRules] , or null if the stylesheet does not change.
8486 @override
8587 String getMigratedContents () {
8688 var results = super .getMigratedContents ();
8789 if (results == null ) return null ;
88- var semicolon = stylesheet.span.file.url .path.endsWith ('.sass' ) ? "" : ";" ;
90+ var semicolon = _currentUrl .path.endsWith ('.sass' ) ? "" : ";" ;
8991 var uses = _additionalUseRules.map ((use) => '@use "$use "$semicolon \n ' );
9092 return uses.join ("" ) + results;
9193 }
9294
95+ /// Stores per-file state before visiting [node] and restores it afterwards.
96+ @override
97+ void visitStylesheet (Stylesheet node) {
98+ var oldNamespaces = _namespaces;
99+ var oldAdditionalUseRules = _additionalUseRules;
100+ var oldUrl = _currentUrl;
101+ _namespaces = {};
102+ _additionalUseRules = Set ();
103+ _currentUrl = node.span.sourceUrl;
104+ super .visitStylesheet (node);
105+ _namespaces = oldNamespaces;
106+ _additionalUseRules = oldAdditionalUseRules;
107+ _lastUrl = _currentUrl;
108+ _currentUrl = oldUrl;
109+ }
110+
93111 /// Visits the children of [node] with a local scope.
94112 ///
95113 /// Note: The children of a stylesheet are at the root, so we should not add
@@ -187,20 +205,34 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
187205 return ;
188206 }
189207 // TODO(jathak): Confirm that this import appears before other rules
190- var url = resolveRelativeUrl (Uri .parse (import.url));
191- var importMigration = newInstance (url)..run ();
192- _namespaces[importMigration.stylesheet.span.sourceUrl] =
193- namespaceForPath (import.url);
208+
209+ visitDependency (Uri .parse (import.url), _currentUrl);
210+ _namespaces[_lastUrl] = namespaceForPath (import.url);
194211
195212 var overrides = [];
196- for (var variable in importMigration._configurableVariables) {
197- if (migrator._variables.containsKey (variable)) {
198- var declaration = migrator._variables[variable];
199- if (namespaceForNode (declaration) == null ) {
200- overrides.add ("\$ ${declaration .name }: ${declaration .expression }" );
213+ for (var name in migrator._variables.keys) {
214+ var declarations = migrator._variables[name];
215+ VariableDeclaration lastNonDefault = declarations[0 ];
216+ for (var i = 1 ; i < declarations.length; i++ ) {
217+ var declaration = declarations[i];
218+ if (! declaration.isGuarded) {
219+ lastNonDefault = declaration;
220+ } else if (declaration.span.sourceUrl == _lastUrl) {
221+ if (lastNonDefault.span.sourceUrl == _currentUrl) {
222+ // This configurable variable was set within the current stylesheet,
223+ // so add to overrides.
224+ overrides.add ("\$ $name : ${lastNonDefault .expression }" );
225+ } else if (lastNonDefault.span.sourceUrl != _lastUrl) {
226+ // A downstream stylesheet configures this variable, so forward it.
227+ var semicolon = _currentUrl.path.endsWith ('.sass' ) ? '' : ';' ;
228+ addPatch (patchBefore (
229+ node, '@forward ${import .span .text } show \$ $name $semicolon \n ' ));
230+ // Add a fake variable declaration so that downstream stylesheets
231+ // configure this module instead of the one we forwarded.
232+ declarations.insert (i + 1 ,
233+ VariableDeclaration (name, null , node.span, guarded: true ));
234+ }
201235 }
202- // TODO(jathak): Remove this declaration from the current stylesheet if
203- // it's not referenced before this point.
204236 }
205237 }
206238 var config = "" ;
@@ -245,7 +277,9 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
245277 return ;
246278 }
247279 if (! migrator._variables.containsKey (node.name)) return ;
248- var namespace = namespaceForNode (migrator._variables[node.name]);
280+ var lastRealDeclaration = migrator._variables[node.name]
281+ .lastWhere ((node) => node.expression != null );
282+ var namespace = namespaceForNode (lastRealDeclaration);
249283 if (namespace == null ) return ;
250284 addPatch (Patch (node.span, "\$ $namespace .${node .name }" ));
251285 }
@@ -261,14 +295,16 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
261295 /// it exists, or as a global variable otherwise.
262296 void declareVariable (VariableDeclaration node) {
263297 if (_localScope == null || node.isGlobal) {
264- if (node.isGuarded) {
265- _configurableVariables. add ( node.name) ;
298+ /* if (node.isGuarded) {
299+ migrator._configurableVariables[ node.name] = node ;
266300
267301 // Don't override if variable already exists.
268302 migrator._variables.putIfAbsent(node.name, () => node);
269303 } else {
270304 migrator._variables[node.name] = node;
271- }
305+ }*/
306+ migrator._variables.putIfAbsent (node.name, () => []);
307+ migrator._variables[node.name].add (node);
272308 } else {
273309 _localScope.variables.add (node.name);
274310 }
@@ -297,11 +333,11 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
297333 /// Finds the namespace for the stylesheet containing [node] , adding a new use
298334 /// rule if necessary.
299335 String namespaceForNode (SassNode node) {
300- if (node.span.sourceUrl == stylesheet.span.sourceUrl ) return null ;
336+ if (node.span.sourceUrl == _currentUrl ) return null ;
301337 if (! _namespaces.containsKey (node.span.sourceUrl)) {
302338 /// Add new use rule for indirect dependency
303339 var relativePath = p.relative (node.span.sourceUrl.path,
304- from: p.dirname (stylesheet.span.sourceUrl .path));
340+ from: p.dirname (_currentUrl .path));
305341 var basename = p.basenameWithoutExtension (relativePath);
306342 if (basename.startsWith ('_' )) basename = basename.substring (1 );
307343 var simplePath = p.relative (p.join (p.dirname (relativePath), basename));
0 commit comments