-
Notifications
You must be signed in to change notification settings - Fork 10
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
Code review for existing code #4
Conversation
Also adds some tests and better comments.
The default is now to only migrate files explicitly passed as entrypoints. Passing the `-r` flag will migrate all dependencies recursively. We still need to look at all dependencies even if we're not migrating them. To make this cleaner, the migration now has two passes. First, we build the dependency graph by parsing the StylesheetApi (consisting of the variables, functions, mixins, and imports) of every sheet in the graph. We then run the actual migration of the entrypoints (and, if desired, their dependencies).
Renames the package to better match the GitHub repo name. Also removes unnecessary dependencies and upgrades the Sass version.
Errors while migrating now throw instead of just returning false through the visitor.
Each set of source files to migrate is now represented as an HRX archive (see test/migrations/README.md for more information).
The path resolver now follows the actual algorithm that Sass uses, so it should properly handle partials and relative paths now.
When built-in functions are used within a source file, the migrator will now add an import for the corresponding built-in module and add a namespace to the function call. As part of this change, the migrator now finishes the file it is on before starting the migration of any dependencies. This eliminates the need to store in-progress migration data for more than one file at a time.
Checks the analyzer, formatting, and tests
When an imported variable is shadowed by a local variable, we should avoid namespacing it.
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.
This is looking really good! I have a bunch of comments, but most of them are small stylistic stuff. The only big thing is the possibility of switching from StylesheetApi
to something more like global scope tracking.
test/migrations/variables.hrx
Outdated
} | ||
|
||
<==> expected/four.scss | ||
$c: 4px; |
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.
At some point, it would be good to remove variable declarations that are used to configure an import. Maybe file an issue for that?
test/patch_test.dart
Outdated
import 'package:test/test.dart'; | ||
|
||
void main() { | ||
test("single patch applied", () { |
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.
Generally speaking, test names should be full sentences that succinctly describe the expected behavior. For example, "a single patch replaces the original text".
Thank you for such a thorough review! I'll probably work through these in stages as I have time. |
Highlights: - The migrator now extends RecursiveStatementVisitor, eliminating the need for BaseVisitor - Migration state for each stylesheet is now stored in StylesheetMigration, which keeps track of member scope, namespaces, and patches. - The migrator now always runs on all dependencies, with the command-line options now only used to determine which migrations are written to disk. This simplifies the migrator logic. - Since dependencies are migrated at the time the import rule is migrated, the global members of the dependency are forwarded onto the StylesheetMigration, which allows the namespacing of implicit dependencies to avoid the complicated logic of examining the entire dependency graph each time. - The tests now use test_descriptor to avoid the need for mocks.
I refactored away I believe I've addressed most of your comments, aside from more tightly-scoped tests, which I'll work on in a separate branch. Let me know if anything needs clarification. Thanks! |
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.
Really great improvements!
lib/src/local_scope.dart
Outdated
final LocalScope parent; | ||
|
||
/// Variables defined in this scope. | ||
final Set<String> variables = normalizedSet(); |
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.
You can omit Set<String>
here; it'll be inferred from normalizedSet()
.
lib/src/migrator.dart
Outdated
|
||
class _Migrator extends RecursiveStatementVisitor implements ExpressionVisitor { | ||
/// List of all migrations for files touched by this run. | ||
p.PathMap<StylesheetMigration> _migrations = p.PathMap(); |
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.
p.PathMap<StylesheetMigration> _migrations = p.PathMap(); | |
final _migrations = p.PathMap<StylesheetMigration>(); |
You can avoid redundantly declaring p.PathMap
by putting the type annotation on the right, and it's always a good idea to make instance variables final
if they aren't ever reassigned. Same below.
lib/src/migrator.dart
Outdated
/// List of all migrations for files touched by this run. | ||
p.PathMap<StylesheetMigration> _migrations = p.PathMap(); | ||
|
||
/// List of migrations in progress. The last item in the current migration. |
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.
/// List of migrations in progress. The last item in the current migration. | |
/// List of migrations in progress. The last item is the current migration. |
/// List of migrations in progress. The last item in the current migration. | ||
List<StylesheetMigration> _activeMigrations = []; | ||
|
||
StylesheetMigration get _currentMigration => |
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.
Nit: document this.
lib/src/migrator.dart
Outdated
_currentMigration.namespaces[importMigration.path] = | ||
namespaceForPath(import.url); | ||
|
||
// Ensure that references to members transient dependencies can be |
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.
// Ensure that references to members transient dependencies can be | |
// Ensure that references to members' transient dependencies can be |
lib/src/migrator.dart
Outdated
// namespaced. | ||
_currentMigration.variables.addEntries(importMigration.variables.entries); | ||
_currentMigration.mixins.addEntries(importMigration.mixins.entries); | ||
_currentMigration.functions.addEntries(importMigration.functions.entries); |
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.
Are these necessary to track now that you're also tracking the global scope? In fact, won't they be misleading, since once the current stylesheet is migrated to @use
it won't make these members transitively visible?
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.
By adding the global members from imports to the current stylesheet, it lets us find existing references to transitively-visible members that need to be namespaced with the transitive dependency, not the direct one. This allows namespaceForNode
to return the correct namespace (adding it if it hasn't been @use
-d) by looking at the source path of the VariableDeclaration
, MixinRule
, or FunctionRule
in one of these dictionaries.
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.
Oh, because the scope is bound to a StylesheetMigration
? Maybe it should be a member of _Migrator
instead... after all, the same global scope is shared across all stylesheets.
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 don't think a single global scope would work when the migrator is running on multiple entrypoints. The scope would need to be cleared between each entrypoint, but the stored global members for each dependency need to remain available in case a future entrypoint also re-uses them.
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 suspect that you'll end up needing to abandon shared state across entrypoints. The way names are resolved in an individual file is highly dependent on the global state at the time it's loaded, so I suspect you'll run into lots of nasty edge cases if you don't treat each entrypoint as an entirely separate run.
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.
Oh, okay. That makes sense. I'll change it so the scoping information is just stored in _Migrator
.
What should I do in the case where the user wants to migrate two entrypoints that share a dependency, but the dependency would be migrated differently depending on which entrypoint is loading it?
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.
Probably just throw an error.
lib/src/migrator.dart
Outdated
_currentMigration.localScope = _currentMigration.localScope.parent; | ||
} | ||
|
||
/// Adds a namespace to any function calls that require them. |
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.
/// Adds a namespace to any function calls that require them. | |
/// Adds a namespace to any function call that requires it. |
(Also below)
lib/src/stylesheet_migration.dart
Outdated
/// Finds the namespace for the stylesheet containing [node], adding a new use | ||
/// rule if necessary. | ||
String namespaceForNode(SassNode node) { | ||
var nodePath = node.span.sourceUrl.path; |
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.
var nodePath = node.span.sourceUrl.path; | |
var nodePath = p.fromUri(node.span.sourceUrl); |
A file:
URL's path component isn't always identical to a filesystem path.
Addressed remaining comments. See my explanation of why we need to copy the global members of an import into the current stylesheet's member maps here |
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.
Don't forget my comments on the tests!
lib/src/migrator.dart
Outdated
// namespaced. | ||
_currentMigration.variables.addEntries(importMigration.variables.entries); | ||
_currentMigration.mixins.addEntries(importMigration.mixins.entries); | ||
_currentMigration.functions.addEntries(importMigration.functions.entries); |
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.
Oh, because the scope is bound to a StylesheetMigration
? Maybe it should be a member of _Migrator
instead... after all, the same global scope is shared across all stylesheets.
lib/src/migrator.dart
Outdated
|
||
class _Migrator extends RecursiveStatementVisitor implements ExpressionVisitor { | ||
/// List of all migrations for files touched by this run. | ||
final _migrations = p.PathMap(); |
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.
final _migrations = p.PathMap(); | |
final _migrations = p.PathMap<StylesheetMigration>(); |
lib/src/migrator.dart
Outdated
final _migrations = p.PathMap(); | ||
|
||
/// List of migrations in progress. The last item is the current migration. | ||
List<StylesheetMigration> _activeMigrations = []; |
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.
List<StylesheetMigration> _activeMigrations = []; | |
final _activeMigrations = <StylesheetMigration>[]; |
lib/src/migrator.dart
Outdated
: p.join(p.dirname(_currentMigration.path), path)); | ||
return _migrations.putIfAbsent(path, () { | ||
var migration = StylesheetMigration(path); | ||
_migrations[path] = migration; |
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.
This is no longer necessary; putIfAbsent
takes care of adding the migration once the block is complete.
Previously, when adding a new use rule for an indirect reference to a member of a partial, the underscore would be removed from the rule's URL, but its namespace was calculated based on the full path.
I changed the tests to be more tightly scoped and addressed your remaining comments. Let me know if there's anything else I should do before merging #8. |
No description provided.