@@ -35,6 +35,12 @@ class ModuleMigrator extends Migrator {
3535 final argParser = ArgParser ()
3636 ..addFlag ('built-in-only' ,
3737 help: 'Migrates global functions without migrating @import.' )
38+ ..addMultiOption ('safe-at-rule' ,
39+ help: 'CSS at-rules (without the @) used by postprocessing tools '
40+ 'that should not be considered to be emitting CSS.' )
41+ ..addFlag ('unsafe-hoist' ,
42+ help: 'Allow the migrator to hoist late imports to the top of the '
43+ 'file even when they emit CSS.' )
3844 ..addMultiOption ('remove-prefix' ,
3945 abbr: 'p' ,
4046 help: 'Removes PREFIX from all migrated member names.\n '
@@ -94,14 +100,16 @@ class ModuleMigrator extends Migrator {
94100 'which prefixed members to forward.' );
95101 }
96102
97- var references = References (importCache, stylesheet, importer);
103+ var references = References (importCache, stylesheet, importer,
104+ safeAtRules: argResults! ['safe-at-rule' ] as List <String >);
98105 var visitor = _ModuleMigrationVisitor (
99106 importCache, references, globalResults! ['load-path' ] as List <String >,
100107 migrateDependencies: migrateDependencies,
101108 builtInOnly: builtInOnly,
102109 prefixesToRemove: (argResults! ['remove-prefix' ] as List <String >)
103110 .map ((prefix) => prefix.replaceAll ('_' , '-' )),
104- forwards: forwards);
111+ forwards: forwards,
112+ unsafeHoist: argResults! ['unsafe-hoist' ] as bool );
105113 var migrated = visitor.run (stylesheet, importer);
106114 _filesWithRenamedDeclarations.addAll (
107115 {for (var member in visitor.renamedMembers.keys) member.sourceUrl});
@@ -156,6 +164,11 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
156164 assertInStylesheet (__builtInUseRules, '_builtInUseRules' );
157165 Set <String >? __builtInUseRules;
158166
167+ /// Set of `@use` rules hoisted from `@import` rules later in the stylesheet.
168+ Set <String > get _hoistedUseRules =>
169+ assertInStylesheet (__hoistedUseRules, '_hoistedUseRules' );
170+ Set <String >? __hoistedUseRules;
171+
159172 /// Set of additional `@use` rules for stylesheets at a load path.
160173 Set <String > get _additionalLoadPathUseRules => assertInStylesheet (
161174 __additionalLoadPathUseRules, '_additionalLoadPathUseRules' );
@@ -184,6 +197,9 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
184197 /// from any member visible at the entrypoint.
185198 var _needsImportOnly = false ;
186199
200+ /// The stylesheet currently being migrated.
201+ late Stylesheet _currentStylesheet;
202+
187203 /// Set of variables declared outside the current stylesheet that overrode
188204 /// `!default` variables within the current stylesheet.
189205 Set <MemberDeclaration <VariableDeclaration >> get _configuredVariables =>
@@ -211,6 +227,10 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
211227 /// Whether to migrate only global functions, leaving `@import` rules as-is.
212228 final bool builtInOnly;
213229
230+ /// Whether to allow hoisting imports to the top of the file even when they
231+ /// emit CSS.
232+ final bool unsafeHoist;
233+
214234 /// Constructs a new module migration visitor.
215235 ///
216236 /// [importCache] must be the same one used by [references] .
@@ -226,6 +246,7 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
226246 super .importCache, this .references, List <String > loadPaths,
227247 {required super .migrateDependencies,
228248 required this .builtInOnly,
249+ required this .unsafeHoist,
229250 Iterable <String > prefixesToRemove = const [],
230251 this .forwards = const {}})
231252 : loadPaths = List .unmodifiable (
@@ -421,10 +442,12 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
421442 /// visiting it and restores the per-file state afterwards.
422443 @override
423444 void visitStylesheet (Stylesheet node) {
445+ _currentStylesheet = node;
424446 var oldNamespaces = __namespaces;
425447 var oldForwardedUrls = __forwardedUrls;
426448 var oldUsedUrls = __usedUrls;
427449 var oldBuiltInUseRules = __builtInUseRules;
450+ var oldHoistedUseRules = __hoistedUseRules;
428451 var oldLoadPathUseRules = __additionalLoadPathUseRules;
429452 var oldRelativeUseRules = __additionalRelativeUseRules;
430453 var oldBeforeFirstImport = _beforeFirstImport;
@@ -438,10 +461,11 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
438461 ? .$2 ??
439462 rule.url: rule.namespace
440463 };
464+ __usedUrls = _namespaces.keys.toSet ();
441465 _determineNamespaces (node.span.sourceUrl! , _namespaces);
442- __usedUrls = {};
443466 __forwardedUrls = {};
444467 __builtInUseRules = {};
468+ __hoistedUseRules = {};
445469 __additionalLoadPathUseRules = {};
446470 __additionalRelativeUseRules = {};
447471 _beforeFirstImport = null ;
@@ -452,6 +476,7 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
452476 __forwardedUrls = oldForwardedUrls;
453477 __usedUrls = oldUsedUrls;
454478 __builtInUseRules = oldBuiltInUseRules;
479+ __hoistedUseRules = oldHoistedUseRules;
455480 __additionalLoadPathUseRules = oldLoadPathUseRules;
456481 __additionalRelativeUseRules = oldRelativeUseRules;
457482 _beforeFirstImport = oldBeforeFirstImport;
@@ -474,7 +499,8 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
474499 useRulesToString (_builtInUseRules)),
475500 beforeExisting: true );
476501 }
477- var extras = useRulesToString (_additionalLoadPathUseRules) +
502+ var extras = useRulesToString (_hoistedUseRules) +
503+ useRulesToString (_additionalLoadPathUseRules) +
478504 useRulesToString (_additionalRelativeUseRules) +
479505 _getAdditionalForwardRules ();
480506 if (extras == '' ) return ;
@@ -795,7 +821,8 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
795821 }
796822 String rulesText;
797823
798- var migratedRules = < String > [];
824+ var inPlaceUseRules = < String > [];
825+ var loadCssRules = < String > [];
799826
800827 var indent = ' ' * node.span.start.column;
801828
@@ -816,23 +843,35 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
816843 }
817844 }
818845 if (ruleUrl != null ) {
846+ var canonicalUrl = importCache
847+ .canonicalize (ruleUrl, baseImporter: importer, baseUrl: currentUrl)!
848+ .$2;
849+ var isNested = ! _currentStylesheet.children.contains (node);
819850 if (builtInOnly) {
820851 if (migrateDependencies) {
821852 _upstreamStylesheets.add (currentUrl);
822853 visitDependency (ruleUrl, import.span);
823854 _upstreamStylesheets.remove (currentUrl);
824855 }
825856 } else if (_useAllowed) {
826- migratedRules.addAll (_migrateImportToRules (ruleUrl, import.span));
857+ inPlaceUseRules.addAll (_migrateImportToRules (ruleUrl, import.span));
858+ } else if (! isNested &&
859+ (! (references.fileEmitsCss[canonicalUrl] ?? true ) ||
860+ (unsafeHoist &&
861+ references.anyMemberReferenced (
862+ canonicalUrl, currentUrl)))) {
863+ _hoistedUseRules.addAll (_migrateImportToRules (ruleUrl, import.span));
827864 } else {
828- migratedRules.add (_migrateImportToLoadCss (ruleUrl, import.span)
829- .replaceAll ('\n ' , '\n $indent ' ));
865+ loadCssRules.add (
866+ _migrateImportToLoadCss (ruleUrl, import.span, isNested)
867+ .replaceAll ('\n ' , '\n $indent ' ));
830868 }
831869 }
832870 }
833871 if (builtInOnly) return ;
834872
835- rulesText = migratedRules.join ('$semicolon \n $indent ' );
873+ rulesText =
874+ [...inPlaceUseRules, ...loadCssRules].join ('$semicolon \n $indent ' );
836875 if (rulesText.isEmpty) {
837876 var span = node.span.extendIfMatches (RegExp (' *$semicolon \n ?' ));
838877 addPatch (patchDelete (span));
@@ -894,8 +933,9 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
894933 if ((rules.isEmpty && normalForwardRules == null ) ||
895934 withClause.isNotEmpty ||
896935 references.anyMemberReferenced (canonicalUrl, currentUrl)) {
897- _usedUrls.add (canonicalUrl);
898- rules.add ('@use $quotedUrl $asClause $withClause ' );
936+ if (_usedUrls.add (canonicalUrl)) {
937+ rules.add ('@use $quotedUrl $asClause $withClause ' );
938+ }
899939 }
900940 if (normalForwardRules != null ) rules.addAll (normalForwardRules);
901941 return rules;
@@ -906,7 +946,7 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
906946 ///
907947 /// This is used for migrating `@import` rules that are nested or appear after
908948 /// some other rules.
909- String _migrateImportToLoadCss (Uri ruleUrl, FileSpan context) {
949+ String _migrateImportToLoadCss (Uri ruleUrl, FileSpan context, bool isNested ) {
910950 var oldUnreferencable = _unreferencable;
911951 _unreferencable = UnreferencableMembers (_unreferencable);
912952 for (var declaration in references.allDeclarations) {
@@ -927,7 +967,11 @@ class _ModuleMigrationVisitor extends MigrationVisitor {
927967 _unreferencable = oldUnreferencable;
928968 for (var declaration in references.allDeclarations) {
929969 if (declaration.sourceUrl != canonicalUrl) continue ;
930- _unreferencable.add (declaration, UnreferencableType .fromNestedImport);
970+ _unreferencable.add (
971+ declaration,
972+ isNested
973+ ? UnreferencableType .fromNestedImport
974+ : UnreferencableType .fromLateImport);
931975 }
932976
933977 var meta = _findOrAddBuiltInNamespace ('meta' );
0 commit comments