Skip to content

Commit 7285d15

Browse files
authored
Refactor how @import rules are migrated (#172)
Fixes #170. This refactors the migration of imports so that the entire rule is migrated in one patch, as opposed to patching each import within a rule separately. Migration for nested imports is also now factored out into its own helper (`_migrateImportToLoadCss`). As part of this refactor, I also made three behavior changes: - For import-only files without corresponding regular files, the regular file they actually forward will now be treated as a replacement, allowing path-changes from import-only files to be migrated in place (and avoiding the issue of adjacent imports being removed that caused #170) - Midstream files that both forward upstream variables for downstream files to configure and configure upstream variables themselves would previously be migrated to a broken result. These cases should now be migrated to a `@forward ... with` rule, which didn't exist the last time I updated the configurable variable code. - `@import` rules that are migrated to both a `@use` and `@forward` rule would previously only migrate one in-place (adding the extra at after all existing rules). Now, both should be migrated in place.
1 parent ddc6aaf commit 7285d15

19 files changed

+343
-146
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@
55
* Fix a bug where generated import-only files for index files would contain
66
invalid forwards.
77

8+
* Better handling for import-only files without corresponding regular files,
9+
including fixing a crash when `@import` rules for two files like this are
10+
adjacent to each other.
11+
12+
* Midstream files that both forward configurable variables and configure other
13+
variables themselves should now be properly migrated.
14+
15+
* When an `@import` rule is migrated to both a `@use` rule and a `@forward`
16+
rule, both rules will now be migrated in-place (previously, the `@use` rule
17+
would replace the `@import` rule and the `@forward` rule would be added after
18+
all other dependencies).
19+
820
## 1.2.5
921

1022
### Module Migrator

lib/src/migrators/module.dart

+165-130
Large diffs are not rendered by default.

lib/src/migrators/module/references.dart

+17-7
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ class References {
9090
/// map to the [ReferenceSource] for the `sass:meta` module).
9191
final Map<SassNode, ReferenceSource> sources;
9292

93-
/// The set of import-only files that do not directly depend on their regular
94-
/// counterparts.
95-
final Set<Uri> orphanImportOnlyFiles;
93+
/// Map of import-only files that do not directly depend on their regular
94+
/// counterparts to the last forward appearing within it (or null, if no
95+
/// regular file is forwarded by the import-only file).
96+
final Map<Uri, ForwardRule> orphanImportOnlyFiles;
9697

9798
/// An iterable of all member declarations.
9899
Iterable<MemberDeclaration> get allDeclarations =>
@@ -154,7 +155,7 @@ class References {
154155
Set<MemberDeclaration> globalDeclarations,
155156
Map<MemberDeclaration, Set<Uri>> libraries,
156157
Map<SassNode, ReferenceSource> sources,
157-
Set<Uri> orphanImportOnlyFiles)
158+
Map<Uri, ForwardRule> orphanImportOnlyFiles)
158159
: variables = UnmodifiableBidirectionalMapView(variables),
159160
variableReassignments =
160161
UnmodifiableBidirectionalMapView(variableReassignments),
@@ -170,7 +171,7 @@ class References {
170171
entry.key: UnmodifiableSetView(entry.value)
171172
}),
172173
sources = UnmodifiableMapView(sources),
173-
orphanImportOnlyFiles = UnmodifiableSetView(orphanImportOnlyFiles);
174+
orphanImportOnlyFiles = UnmodifiableMapView(orphanImportOnlyFiles);
174175

175176
/// Constructs a new [References] object based on a [stylesheet] (imported by
176177
/// [importer]) and its dependencies.
@@ -196,7 +197,7 @@ class _ReferenceVisitor extends RecursiveAstVisitor {
196197
final _globalDeclarations = <MemberDeclaration>{};
197198
final _libraries = <MemberDeclaration, Set<Uri>>{};
198199
final _sources = <SassNode, ReferenceSource>{};
199-
final _orphanImportOnlyFiles = <Uri>{};
200+
final _orphanImportOnlyFiles = <Uri, ForwardRule>{};
200201

201202
/// The current global scope.
202203
///
@@ -261,6 +262,9 @@ class _ReferenceVisitor extends RecursiveAstVisitor {
261262
/// Cache used to load stylesheets.
262263
ImportCache importCache;
263264

265+
/// The last `@forward` rule to be visited that was not an import-only file.
266+
ForwardRule _lastRegularForward;
267+
264268
_ReferenceVisitor(this.importCache);
265269

266270
/// Constructs a new References object based on a [stylesheet] (imported by
@@ -342,7 +346,12 @@ class _ReferenceVisitor extends RecursiveAstVisitor {
342346
_currentUrl = node.span.sourceUrl;
343347
_isOrphanImportOnly = isImportOnlyFile(_currentUrl);
344348
super.visitStylesheet(node);
345-
if (_isOrphanImportOnly) _orphanImportOnlyFiles.add(_currentUrl);
349+
if (_isOrphanImportOnly) {
350+
_orphanImportOnlyFiles[_currentUrl] =
351+
_lastRegularForward.span.sourceUrl == _currentUrl
352+
? _lastRegularForward
353+
: null;
354+
}
346355
_isOrphanImportOnly = oldOrphaned;
347356
_namespaces = oldNamespaces;
348357
_currentUrl = oldUrl;
@@ -459,6 +468,7 @@ class _ReferenceVisitor extends RecursiveAstVisitor {
459468
if (_isOrphanImportOnly && _currentUrl == getImportOnlyUrl(canonicalUrl)) {
460469
_isOrphanImportOnly = false;
461470
}
471+
if (!isImportOnlyFile(canonicalUrl)) _lastRegularForward = node;
462472
var moduleScope = _moduleScopes[canonicalUrl];
463473
for (var declaration in moduleScope.variables.values) {
464474
if (declaration.member is! VariableDeclaration) {

lib/src/utils.dart

+41
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,47 @@ FileSpan extendForward(FileSpan span, String text) {
7676
return span.file.span(span.start.offset, end + text.length);
7777
}
7878

79+
/// Returns the next location after [import] that it would be safe to insert
80+
/// a `@use` or `@forward` rule.
81+
///
82+
/// This is generally the start of the next line, but may vary if there's any
83+
/// non-whitespace, non-comment code on the same line.
84+
FileLocation afterImport(ImportRule import, {bool shouldHaveSemicolon = true}) {
85+
var loc = import.span.end;
86+
var textAfter = loc.file.getText(loc.offset);
87+
var inLineComment = false;
88+
var inBlockComment = false;
89+
var i = 0;
90+
for (; i < textAfter.length; i++) {
91+
var char = textAfter.codeUnitAt(i);
92+
if (inBlockComment) {
93+
if (char == $asterisk && textAfter.codeUnitAt(i + 1) == $slash) {
94+
i++;
95+
inBlockComment = false;
96+
}
97+
} else if (char == $lf && !shouldHaveSemicolon) {
98+
i++;
99+
break;
100+
} else if (inLineComment) {
101+
continue;
102+
} else if (char == $slash) {
103+
var next = textAfter.codeUnitAt(i + 1);
104+
if (next == $slash) {
105+
inLineComment = true;
106+
} else if (next == $asterisk) {
107+
inBlockComment = true;
108+
} else {
109+
break;
110+
}
111+
} else if (shouldHaveSemicolon && char == $semicolon) {
112+
shouldHaveSemicolon = false;
113+
} else if (!isWhitespace(char)) {
114+
break;
115+
}
116+
}
117+
return loc.file.location(loc.offset + i);
118+
}
119+
79120
/// Extends [span] backward if it's preceded by exactly [text].
80121
///
81122
/// If [span] is preceded by anything other than [text], returns `null`.

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: sass_migrator
2-
version: 1.2.6-dev
2+
version: 1.2.6
33
description: A tool for running migrations on Sass files
44
author: Jennifer Thakar <[email protected]>
55
homepage: https://github.com/sass/migrator
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> input/entrypoint.scss
5+
$indirect: red;
6+
@import "midstream";
7+
8+
<==> input/_midstream.scss
9+
$direct: blue;
10+
@import "upstream";
11+
12+
<==> input/_upstream.scss
13+
$direct: green !default;
14+
$indirect: green !default;
15+
16+
<==> output/entrypoint.scss
17+
@use "midstream" with ($indirect: red);
18+
19+
<==> output/_midstream.scss
20+
@forward "upstream" show $indirect with ($direct: blue);

test/migrators/module/configuration/indirect.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@ $config: green !default;
1616

1717
<==> output/_direct.scss
1818
@forward "indirect" show $config;
19-
@use "indirect";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> README
5+
This test makes sure that comments after an `@import` are not mangled when an
6+
additional `@use` rule is inserted.
7+
8+
<==> input/entrypoint.scss
9+
@import "direct"; // comment for direct
10+
11+
a {color: $variable;}
12+
13+
<==> input/_direct.scss
14+
@import "indirect";
15+
16+
<==> input/_indirect.scss
17+
$variable: blue;
18+
19+
<==> output/entrypoint.scss
20+
@use "direct"; // comment for direct
21+
@use "indirect";
22+
23+
a {color: indirect.$variable;}
24+
25+
<==> output/_direct.scss
26+
@use "indirect";

test/migrators/module/dependency_order/full_order.hrx

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ $load-path: yellow;
4343
@use "sass:color";
4444
@forward "forwarded";
4545
@use "library";
46+
@forward "library";
4647
@use "load-path";
4748
@use "relative";
4849

4950
@forward "load-path";
50-
@forward "library";
5151
@forward "relative";
5252

5353
a {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> README
5+
This test makes sure that inserting an additional `@use` rule still works if an
6+
`@import` is followed by additional code on the same line.
7+
8+
<==> input/entrypoint.scss
9+
@import "direct";a {color: $variable;}
10+
11+
<==> input/_direct.scss
12+
@import "indirect";
13+
14+
<==> input/_indirect.scss
15+
$variable: blue;
16+
17+
<==> output/entrypoint.scss
18+
@use "direct";
19+
@use "indirect";
20+
21+
a {color: indirect.$variable;}
22+
23+
<==> output/_direct.scss
24+
@use "indirect";

test/migrators/module/forward_flag/pseudoprivate.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ $_private: green;
1515

1616
<==> output/entrypoint.scss
1717
@use "dependency";
18-
1918
@forward "dependency" hide $pseudoprivate;
2019

2120
a {

test/migrators/module/partial_migration/changed_path.hrx

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
<==> input/entrypoint.scss
55
@import "old";
6+
67
a {
78
b: $lib-variable;
89
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<==> arguments
2+
--migrate-deps
3+
4+
<==> input/entrypoint.scss
5+
@import "old1";
6+
@import "old2";
7+
8+
a {
9+
b: $lib1-variable;
10+
c: $lib2-variable;
11+
}
12+
13+
<==> input/_old1.import.scss
14+
@forward "new1" as lib1-*;
15+
16+
<==> input/_new1.scss
17+
$variable: green;
18+
19+
<==> input/_old2.import.scss
20+
@forward "new2" as lib2-*;
21+
22+
<==> input/_new2.scss
23+
$variable: green;
24+
25+
<==> output/entrypoint.scss
26+
@use "new1";
27+
@use "new2";
28+
29+
a {
30+
b: new1.$variable;
31+
c: new2.$variable;
32+
}

test/migrators/module/partial_migration/changed_path_no_regular.hrx

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
<==> input/entrypoint.scss
55
@import "old";
6+
67
a {
78
b: $lib-variable;
89
}

test/migrators/module/partial_migration/changed_path_split.hrx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
<==> input/entrypoint.scss
55
@import "old";
6+
67
a {
78
b: $lib-variable1;
89
c: $lib-variable2;
@@ -19,8 +20,8 @@ $variable1: green;
1920
$variable2: blue;
2021

2122
<==> output/entrypoint.scss
22-
@use "a";
2323
@use "b";
24+
@use "a";
2425

2526
a {
2627
b: a.$variable1;

test/migrators/module/partial_migration/forward_already_unprefixed.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ $variable: green;
1616

1717
<==> output/entrypoint.scss
1818
@use "library";
19-
2019
@forward "library";
2120

2221
a {

test/migrators/module/partial_migration/remove_multiple_subprefixes.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ $var2: blue;
1919

2020
<==> output/entrypoint.scss
2121
@use "dependency";
22-
2322
@forward "dependency" as a-* hide $a-var2;
2423
@forward "dependency" as b-* hide $b-var1;
2524

test/migrators/module/partial_migration/remove_subprefix.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ $variable: green;
1616

1717
<==> output/entrypoint.scss
1818
@use "button";
19-
2019
@forward "button" as button-*;
2120

2221
a {

test/migrators/module/remove_prefix_flag/pseudoprivate_members.hrx

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ $_lib-pseudoprivate: blue;
1515

1616
<==> output/entrypoint.scss
1717
@use "dependency";
18-
1918
@forward "dependency" hide $pseudoprivate;
2019

2120
a {

0 commit comments

Comments
 (0)