Skip to content

Commit b4fc3d4

Browse files
authored
Migrate to null safety (#183)
1 parent e47ce5f commit b4fc3d4

26 files changed

+499
-364
lines changed

.github/workflows/ci.yml

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ jobs:
8484
- run: dart pub get
8585
- name: Analyze dart
8686
run: dartanalyzer --fatal-warnings --fatal-infos lib tool test
87+
- name: Check formatting
88+
run: dartfmt -n --set-exit-if-changed .
8789

8890
sanity_checks:
8991
name: Sanity checks

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.3.8
2+
3+
* No user-visible changes.
4+
15
## 1.3.7
26

37
### Module Migrator

bin/sass_migrator.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:sass_migrator/src/runner.dart';
1212
// We can't declare args as a List<String> or Iterable<String> beacause of
1313
// dart-lang/sdk#36627.
1414
main(Iterable args) {
15-
if (process.argv != null) args = process.argv.skip(2);
15+
var argv = process.argv;
16+
if (argv != null) args = argv.skip(2);
1617
MigratorRunner().execute(args.cast<String>());
1718
}

lib/src/migration_visitor.dart

+22-10
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,21 @@ abstract class MigrationVisitor extends RecursiveAstVisitor {
5151
/// The patches to be applied to the stylesheet being migrated.
5252
@protected
5353
List<Patch> get patches => UnmodifiableListView(_patches);
54-
List<Patch> _patches;
54+
List<Patch> get _patches => assertInStylesheet(__patches, 'patches');
55+
List<Patch>? __patches;
5556

5657
/// URL of the stylesheet currently being migrated.
5758
@protected
58-
Uri get currentUrl => _currentUrl;
59-
Uri _currentUrl;
59+
Uri get currentUrl => assertInStylesheet(_currentUrl, 'currentUrl');
60+
Uri? _currentUrl;
6061

6162
/// The importer that's being used to resolve relative imports.
6263
///
6364
/// If this is `null`, relative imports aren't supported in the current
6465
/// stylesheet.
6566
@protected
6667
Importer get importer => _importer;
67-
Importer _importer;
68+
late Importer _importer;
6869

6970
MigrationVisitor(this.importCache, this.migrateDependencies);
7071

@@ -84,10 +85,10 @@ abstract class MigrationVisitor extends RecursiveAstVisitor {
8485
/// file's state before calling the super method and restore it afterwards.
8586
@override
8687
void visitStylesheet(Stylesheet node) {
87-
var oldPatches = _patches;
88+
var oldPatches = __patches;
8889
var oldUrl = _currentUrl;
89-
_patches = [];
90-
_currentUrl = node.span.sourceUrl;
90+
__patches = [];
91+
_currentUrl = node.span.sourceUrl!;
9192
super.visitStylesheet(node);
9293
beforePatch(node);
9394
var results = patches.isNotEmpty
@@ -102,10 +103,10 @@ abstract class MigrationVisitor extends RecursiveAstVisitor {
102103
"it's loaded.");
103104
}
104105

105-
_migrated[_currentUrl] = results;
106+
_migrated[currentUrl] = results;
106107
}
107108

108-
_patches = oldPatches;
109+
__patches = oldPatches;
109110
_currentUrl = oldUrl;
110111
}
111112

@@ -139,7 +140,7 @@ abstract class MigrationVisitor extends RecursiveAstVisitor {
139140
_importer = oldImporter;
140141
} else {
141142
_missingDependencies.putIfAbsent(
142-
context.sourceUrl.resolveUri(dependency), () => context);
143+
context.sourceUrl!.resolveUri(dependency), () => context);
143144
}
144145
}
145146

@@ -180,4 +181,15 @@ abstract class MigrationVisitor extends RecursiveAstVisitor {
180181
visitDependency(node.url, node.span);
181182
}
182183
}
184+
185+
/// Asserts that [value] is not `null` and returns it.
186+
///
187+
/// This is used for fields that are set whenever the migrator is visiting
188+
/// a stylesheet, which means they should be non-null almost all the time
189+
/// during a call to [run].
190+
@protected
191+
T assertInStylesheet<T>(T? value, String name) {
192+
if (value != null) return value;
193+
throw StateError("Can't access $name when not visiting a stylesheet.");
194+
}
183195
}

lib/src/migrator.dart

+14-14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:sass/src/import_cache.dart';
1414

1515
import 'package:args/command_runner.dart';
1616
import 'package:glob/glob.dart';
17+
import 'package:glob/list_local_fs.dart';
1718
import 'package:meta/meta.dart';
1819
import 'package:path/path.dart' as p;
1920
import 'package:sass_migrator/src/util/node_modules_importer.dart';
@@ -45,7 +46,7 @@ abstract class Migrator extends Command<Map<Uri, String>> {
4546
"See also https://sass-lang.com/documentation/cli/migrator#$name";
4647

4748
/// If true, dependencies will be migrated in addition to the entrypoints.
48-
bool get migrateDependencies => globalResults['migrate-deps'] as bool;
49+
bool get migrateDependencies => globalResults!['migrate-deps'] as bool;
4950

5051
/// Map of missing dependency URLs to the spans that import/use them.
5152
///
@@ -75,11 +76,12 @@ abstract class Migrator extends Command<Map<Uri, String>> {
7576
Map<Uri, String> run() {
7677
var allMigrated = <Uri, String>{};
7778
var importer = FilesystemImporter('.');
78-
var importCache = ImportCache([NodeModulesImporter()],
79-
loadPaths: globalResults['load-path']);
79+
var importCache = ImportCache(
80+
importers: [NodeModulesImporter()],
81+
loadPaths: globalResults!['load-path']);
8082

8183
var entrypoints = [
82-
for (var argument in argResults.rest)
84+
for (var argument in argResults!.rest)
8385
for (var entry in Glob(argument).listSync())
8486
if (entry is File) entry.path
8587
];
@@ -91,15 +93,14 @@ abstract class Migrator extends Command<Map<Uri, String>> {
9193
}
9294

9395
var migrated = migrateFile(importCache, tuple.item2, tuple.item1);
94-
for (var file in migrated.keys) {
95-
if (allMigrated.containsKey(file) &&
96-
migrated[file] != allMigrated[file]) {
96+
migrated.forEach((file, contents) {
97+
if (allMigrated.containsKey(file) && contents != allMigrated[file]) {
9798
throw MigrationException(
9899
"The migrator has found multiple possible migrations for $file, "
99100
"depending on the context in which it's loaded.");
100101
}
101-
allMigrated[file] = migrated[file];
102-
}
102+
allMigrated[file] = contents;
103+
});
103104
}
104105

105106
if (missingDependencies.isNotEmpty) _warnForMissingDependencies();
@@ -114,7 +115,7 @@ abstract class Migrator extends Command<Map<Uri, String>> {
114115
/// In verbose mode, this instead prints a full warning with the source span
115116
/// for each missing dependency.
116117
void _warnForMissingDependencies() {
117-
if (globalResults['verbose'] as bool) {
118+
if (globalResults!['verbose'] as bool) {
118119
for (var uri in missingDependencies.keys) {
119120
emitWarning("Could not find Sass file at '${p.prettyUri(uri)}'.",
120121
missingDependencies[uri]);
@@ -123,11 +124,10 @@ abstract class Migrator extends Command<Map<Uri, String>> {
123124
var count = missingDependencies.length;
124125
emitWarning(
125126
"$count dependenc${count == 1 ? 'y' : 'ies'} could not be found.");
126-
for (var uri in missingDependencies.keys) {
127-
var context = missingDependencies[uri];
128-
printStderr(' ${p.prettyUri(uri)} '
127+
missingDependencies.forEach((url, context) {
128+
printStderr(' ${p.prettyUri(url)} '
129129
'@${p.prettyUri(context.sourceUrl)}:${context.start.line + 1}');
130-
}
130+
});
131131
}
132132
}
133133
}

lib/src/migrators/division.dart

+8-7
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ More info: https://sass-lang.com/d/slash-div""";
3838
help: "Only migrate / expressions that are unambiguously division.",
3939
negatable: false);
4040

41-
bool get isPessimistic => argResults['pessimistic'] as bool;
41+
bool get isPessimistic => argResults!['pessimistic'] as bool;
4242

4343
@override
4444
Map<Uri, String> migrateFile(
@@ -143,16 +143,16 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
143143
return false;
144144
}
145145

146-
ListExpression channels;
146+
ListExpression? channels;
147147
if (node.arguments.positional.length == 1 &&
148148
node.arguments.named.isEmpty &&
149149
node.arguments.positional.first is ListExpression) {
150-
channels = node.arguments.positional.first;
150+
channels = node.arguments.positional.first as ListExpression?;
151151
} else if (node.arguments.positional.isEmpty &&
152152
node.arguments.named.containsKey(r'$channels') &&
153153
node.arguments.named.length == 1 &&
154154
node.arguments.named[r'$channels'] is ListExpression) {
155-
channels = node.arguments.named[r'$channels'];
155+
channels = node.arguments.named[r'$channels'] as ListExpression?;
156156
}
157157
if (channels == null ||
158158
channels.hasBrackets ||
@@ -170,7 +170,8 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
170170
_patchOperatorToComma(last);
171171
}
172172
_withContext(() {
173-
channels.contents[0].accept(this);
173+
// Non-null assertion is required because of dart-lang/language#1536.
174+
channels!.contents[0].accept(this);
174175
channels.contents[1].accept(this);
175176
last.left.accept(this);
176177
}, isDivisionAllowed: true);
@@ -326,7 +327,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
326327
/// ParenthesizedExpression.
327328
void _patchParensIfAny(SassNode node) {
328329
if (node is! ParenthesizedExpression) return;
329-
var expression = (node as ParenthesizedExpression).expression;
330+
var expression = node.expression;
330331
if (expression is BinaryOperationExpression &&
331332
expression.operator == BinaryOperator.dividedBy) {
332333
return;
@@ -337,7 +338,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor {
337338

338339
/// Runs [operation] with the given context.
339340
void _withContext(void operation(),
340-
{bool isDivisionAllowed, bool expectsNumericResult}) {
341+
{bool? isDivisionAllowed, bool? expectsNumericResult}) {
341342
var previousDivisionAllowed = _isDivisionAllowed;
342343
var previousNumericResult = _expectsNumericResult;
343344
if (isDivisionAllowed != null) _isDivisionAllowed = isDivisionAllowed;

0 commit comments

Comments
 (0)