-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrate to null safety #183
Changes from 5 commits
d2ecb16
62357d5
e4c6527
57d645d
4b99ae1
d834e0e
4b03025
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -20,6 +20,7 @@ import 'package:sass/src/visitor/recursive_ast.dart'; | |||||||||||
|
||||||||||||
import 'exception.dart'; | ||||||||||||
import 'patch.dart'; | ||||||||||||
import 'utils.dart'; | ||||||||||||
|
||||||||||||
/// A visitor that migrates a stylesheet. | ||||||||||||
/// | ||||||||||||
|
@@ -50,21 +51,21 @@ abstract class MigrationVisitor extends RecursiveAstVisitor { | |||||||||||
|
||||||||||||
/// The patches to be applied to the stylesheet being migrated. | ||||||||||||
@protected | ||||||||||||
List<Patch> get patches => UnmodifiableListView(_patches); | ||||||||||||
List<Patch> _patches; | ||||||||||||
List<Patch> get patches => UnmodifiableListView(assertNotNull(_patches)); | ||||||||||||
List<Patch>? _patches; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you do sometimes need to access
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||||||||
|
||||||||||||
/// URL of the stylesheet currently being migrated. | ||||||||||||
@protected | ||||||||||||
Uri get currentUrl => _currentUrl; | ||||||||||||
Uri _currentUrl; | ||||||||||||
Uri get currentUrl => assertNotNull(_currentUrl); | ||||||||||||
Uri? _currentUrl; | ||||||||||||
|
||||||||||||
/// The importer that's being used to resolve relative imports. | ||||||||||||
/// | ||||||||||||
/// If this is `null`, relative imports aren't supported in the current | ||||||||||||
/// stylesheet. | ||||||||||||
@protected | ||||||||||||
Importer get importer => _importer; | ||||||||||||
Importer _importer; | ||||||||||||
late Importer _importer; | ||||||||||||
|
||||||||||||
MigrationVisitor(this.importCache, this.migrateDependencies); | ||||||||||||
|
||||||||||||
|
@@ -87,7 +88,7 @@ abstract class MigrationVisitor extends RecursiveAstVisitor { | |||||||||||
var oldPatches = _patches; | ||||||||||||
var oldUrl = _currentUrl; | ||||||||||||
_patches = []; | ||||||||||||
_currentUrl = node.span.sourceUrl; | ||||||||||||
_currentUrl = node.span.sourceUrl!; | ||||||||||||
super.visitStylesheet(node); | ||||||||||||
beforePatch(node); | ||||||||||||
var results = patches.isNotEmpty | ||||||||||||
|
@@ -102,7 +103,7 @@ abstract class MigrationVisitor extends RecursiveAstVisitor { | |||||||||||
"it's loaded."); | ||||||||||||
} | ||||||||||||
|
||||||||||||
_migrated[_currentUrl] = results; | ||||||||||||
_migrated[currentUrl] = results; | ||||||||||||
} | ||||||||||||
|
||||||||||||
_patches = oldPatches; | ||||||||||||
|
@@ -139,7 +140,7 @@ abstract class MigrationVisitor extends RecursiveAstVisitor { | |||||||||||
_importer = oldImporter; | ||||||||||||
} else { | ||||||||||||
_missingDependencies.putIfAbsent( | ||||||||||||
context.sourceUrl.resolveUri(dependency), () => context); | ||||||||||||
context.sourceUrl!.resolveUri(dependency), () => context); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
@@ -151,9 +152,9 @@ abstract class MigrationVisitor extends RecursiveAstVisitor { | |||||||||||
@protected | ||||||||||||
void addPatch(Patch patch, {bool beforeExisting = false}) { | ||||||||||||
if (beforeExisting) { | ||||||||||||
_patches.insert(0, patch); | ||||||||||||
_patches!.insert(0, patch); | ||||||||||||
} else { | ||||||||||||
_patches.add(patch); | ||||||||||||
_patches!.add(patch); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import 'package:sass/src/import_cache.dart'; | |
|
||
import 'package:args/command_runner.dart'; | ||
import 'package:glob/glob.dart'; | ||
import 'package:glob/list_local_fs.dart'; | ||
import 'package:meta/meta.dart'; | ||
import 'package:path/path.dart' as p; | ||
import 'package:sass_migrator/src/util/node_modules_importer.dart'; | ||
|
@@ -36,7 +37,7 @@ import 'utils.dart'; | |
/// | ||
/// Most migrators will want to create a subclass of [MigrationVisitor] and | ||
/// implement [migrateFile] with `MyMigrationVisitor(this, entrypoint).run()`. | ||
abstract class Migrator extends Command<Map<Uri, String>> { | ||
abstract class Migrator extends Command<Map<Uri, String >> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a couple places here where a space is added after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops. My editor auto-formats on save, so I didn't think to run it manually (and I guess the migrator doesn't format after running). It looks like the formatter check got dropped from the CI for both this repo and Dart Sass during the switch to GitHub Actions. Added it to the CI config. |
||
String get invocation => super | ||
.invocation | ||
.replaceFirst("[arguments]", "[options] <entrypoints.scss...>"); | ||
|
@@ -45,7 +46,7 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
"See also https://sass-lang.com/documentation/cli/migrator#$name"; | ||
|
||
/// If true, dependencies will be migrated in addition to the entrypoints. | ||
bool get migrateDependencies => globalResults['migrate-deps'] as bool; | ||
bool get migrateDependencies => globalResults!['migrate-deps'] as bool; | ||
|
||
/// Map of missing dependency URLs to the spans that import/use them. | ||
/// | ||
|
@@ -61,7 +62,7 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
/// Files that did not require any changes, even if touched by the migrator, | ||
/// should not be included map of results. | ||
@protected | ||
Map<Uri, String> migrateFile( | ||
Map<Uri, String > migrateFile( | ||
ImportCache importCache, Stylesheet stylesheet, Importer importer); | ||
|
||
/// Runs this migrator. | ||
|
@@ -72,14 +73,15 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
/// | ||
/// Entrypoints and dependencies that did not require any changes will not be | ||
/// included in the results. | ||
Map<Uri, String> run() { | ||
Map<Uri, String > run() { | ||
var allMigrated = <Uri, String>{}; | ||
var importer = FilesystemImporter('.'); | ||
var importCache = ImportCache([NodeModulesImporter()], | ||
loadPaths: globalResults['load-path']); | ||
var importCache = ImportCache( | ||
importers: [NodeModulesImporter()], | ||
loadPaths: globalResults!['load-path']); | ||
|
||
var entrypoints = [ | ||
for (var argument in argResults.rest) | ||
for (var argument in argResults!.rest) | ||
for (var entry in Glob(argument).listSync()) | ||
if (entry is File) entry.path | ||
]; | ||
|
@@ -91,15 +93,14 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
} | ||
|
||
var migrated = migrateFile(importCache, tuple.item2, tuple.item1); | ||
for (var file in migrated.keys) { | ||
if (allMigrated.containsKey(file) && | ||
migrated[file] != allMigrated[file]) { | ||
migrated.forEach((file, contents) { | ||
if (allMigrated.containsKey(file) && contents != allMigrated[file]) { | ||
throw MigrationException( | ||
"The migrator has found multiple possible migrations for $file, " | ||
"depending on the context in which it's loaded."); | ||
} | ||
allMigrated[file] = migrated[file]; | ||
} | ||
allMigrated[file] = contents; | ||
}); | ||
} | ||
|
||
if (missingDependencies.isNotEmpty) _warnForMissingDependencies(); | ||
|
@@ -114,7 +115,7 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
/// In verbose mode, this instead prints a full warning with the source span | ||
/// for each missing dependency. | ||
void _warnForMissingDependencies() { | ||
if (globalResults['verbose'] as bool) { | ||
if (globalResults!['verbose'] as bool) { | ||
for (var uri in missingDependencies.keys) { | ||
emitWarning("Could not find Sass file at '${p.prettyUri(uri)}'.", | ||
missingDependencies[uri]); | ||
|
@@ -123,11 +124,10 @@ abstract class Migrator extends Command<Map<Uri, String>> { | |
var count = missingDependencies.length; | ||
emitWarning( | ||
"$count dependenc${count == 1 ? 'y' : 'ies'} could not be found."); | ||
for (var uri in missingDependencies.keys) { | ||
var context = missingDependencies[uri]; | ||
printStderr(' ${p.prettyUri(uri)} ' | ||
missingDependencies.forEach((url, context) { | ||
printStderr(' ${p.prettyUri(url)} ' | ||
'@${p.prettyUri(context.sourceUrl)}:${context.start.line + 1}'); | ||
} | ||
}); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,7 @@ More info: https://sass-lang.com/d/slash-div"""; | |
help: "Only migrate / expressions that are unambiguously division.", | ||
negatable: false); | ||
|
||
bool get isPessimistic => argResults['pessimistic'] as bool; | ||
bool get isPessimistic => argResults!['pessimistic'] as bool; | ||
|
||
@override | ||
Map<Uri, String> migrateFile( | ||
|
@@ -143,16 +143,16 @@ class _DivisionMigrationVisitor extends MigrationVisitor { | |
return false; | ||
} | ||
|
||
ListExpression channels; | ||
ListExpression? channels; | ||
if (node.arguments.positional.length == 1 && | ||
node.arguments.named.isEmpty && | ||
node.arguments.positional.first is ListExpression) { | ||
channels = node.arguments.positional.first; | ||
channels = node.arguments.positional.first as ListExpression?; | ||
} else if (node.arguments.positional.isEmpty && | ||
node.arguments.named.containsKey(r'$channels') && | ||
node.arguments.named.length == 1 && | ||
node.arguments.named[r'$channels'] is ListExpression) { | ||
channels = node.arguments.named[r'$channels']; | ||
channels = node.arguments.named[r'$channels'] as ListExpression?; | ||
} | ||
if (channels == null || | ||
channels.hasBrackets || | ||
|
@@ -170,7 +170,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor { | |
_patchOperatorToComma(last); | ||
} | ||
_withContext(() { | ||
channels.contents[0].accept(this); | ||
channels!.contents[0].accept(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment referencing https://github.com/dart-lang/sdk/issues/45348 so it's clear why this non-null assertion is necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
channels.contents[1].accept(this); | ||
last.left.accept(this); | ||
}, isDivisionAllowed: true); | ||
|
@@ -326,7 +326,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor { | |
/// ParenthesizedExpression. | ||
void _patchParensIfAny(SassNode node) { | ||
if (node is! ParenthesizedExpression) return; | ||
var expression = (node as ParenthesizedExpression).expression; | ||
var expression = node.expression; | ||
if (expression is BinaryOperationExpression && | ||
expression.operator == BinaryOperator.dividedBy) { | ||
return; | ||
|
@@ -337,7 +337,7 @@ class _DivisionMigrationVisitor extends MigrationVisitor { | |
|
||
/// Runs [operation] with the given context. | ||
void _withContext(void operation(), | ||
{bool isDivisionAllowed, bool expectsNumericResult}) { | ||
{bool? isDivisionAllowed, bool? expectsNumericResult}) { | ||
var previousDivisionAllowed = _isDivisionAllowed; | ||
var previousNumericResult = _expectsNumericResult; | ||
if (isDivisionAllowed != null) _isDivisionAllowed = isDivisionAllowed; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's generally better to do this slightly awkward two-step check than to include runtime non-null assertions where possible. Even though it's more verbose, it helps preserve the signal that every
!
indicates something weird going on and needs extra attention and care.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done