Skip to content
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

Closed
wants to merge 42 commits into from
Closed

Code review for existing code #4

wants to merge 42 commits into from

Conversation

jathak
Copy link
Member

@jathak jathak commented Feb 4, 2019

No description provided.

jathak and others added 18 commits November 9, 2018 17:52
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.
@jathak jathak requested a review from nex3 February 4, 2019 21:08
Copy link
Contributor

@nex3 nex3 left a 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.

.travis.yml Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
bin/sass_module_migrator.dart Outdated Show resolved Hide resolved
lib/src/base_visitor.dart Outdated Show resolved Hide resolved
lib/src/base_visitor.dart Outdated Show resolved Hide resolved
}

<==> expected/four.scss
$c: 4px;
Copy link
Contributor

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/migrator_test.dart Outdated Show resolved Hide resolved
test/migrator_test.dart Outdated Show resolved Hide resolved
test/patch_test.dart Show resolved Hide resolved
import 'package:test/test.dart';

void main() {
test("single patch applied", () {
Copy link
Contributor

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".

@jathak
Copy link
Member Author

jathak commented Feb 7, 2019

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.
@jathak
Copy link
Member Author

jathak commented Feb 12, 2019

I refactored away BaseVisitor and StylesheetApi, changing the migrator itself to run a single pass on all dependencies, with each stylesheet's migration state represented by a new StylesheetMigration class. This also allowed the scoping and implicit import tracking to be simplified in my mind.

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!

Copy link
Contributor

@nex3 nex3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really great improvements!

final LocalScope parent;

/// Variables defined in this scope.
final Set<String> variables = normalizedSet();
Copy link
Contributor

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 Show resolved Hide resolved

class _Migrator extends RecursiveStatementVisitor implements ExpressionVisitor {
/// List of all migrations for files touched by this run.
p.PathMap<StylesheetMigration> _migrations = p.PathMap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

/// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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 =>
Copy link
Contributor

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 Show resolved Hide resolved
_currentMigration.namespaces[importMigration.path] =
namespaceForPath(import.url);

// Ensure that references to members transient dependencies can be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Ensure that references to members transient dependencies can be
// Ensure that references to members' transient dependencies can be

// namespaced.
_currentMigration.variables.addEntries(importMigration.variables.entries);
_currentMigration.mixins.addEntries(importMigration.mixins.entries);
_currentMigration.functions.addEntries(importMigration.functions.entries);
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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.

_currentMigration.localScope = _currentMigration.localScope.parent;
}

/// Adds a namespace to any function calls that require them.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Adds a namespace to any function calls that require them.
/// Adds a namespace to any function call that requires it.

(Also below)

/// 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

@jathak
Copy link
Member Author

jathak commented Feb 13, 2019

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

Copy link
Contributor

@nex3 nex3 left a 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!

// namespaced.
_currentMigration.variables.addEntries(importMigration.variables.entries);
_currentMigration.mixins.addEntries(importMigration.mixins.entries);
_currentMigration.functions.addEntries(importMigration.functions.entries);
Copy link
Contributor

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.


class _Migrator extends RecursiveStatementVisitor implements ExpressionVisitor {
/// List of all migrations for files touched by this run.
final _migrations = p.PathMap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
final _migrations = p.PathMap();
final _migrations = p.PathMap<StylesheetMigration>();

final _migrations = p.PathMap();

/// List of migrations in progress. The last item is the current migration.
List<StylesheetMigration> _activeMigrations = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
List<StylesheetMigration> _activeMigrations = [];
final _activeMigrations = <StylesheetMigration>[];

: p.join(p.dirname(_currentMigration.path), path));
return _migrations.putIfAbsent(path, () {
var migration = StylesheetMigration(path);
_migrations[path] = migration;
Copy link
Contributor

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.

@jathak
Copy link
Member Author

jathak commented Mar 2, 2019

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.

jathak added a commit that referenced this pull request Mar 8, 2019
Changes based on review for #4
@jathak jathak closed this Mar 8, 2019
@jathak jathak deleted the review branch March 8, 2019 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants