Support import-only files with include rules#300
Conversation
|
#301 should fix the CI here |
This enables migration of some nested/late imports that the migrator couldn't previously handle by first wrapping the file that's depended on via nested import in a mixin and then `@include`ing that mixin in the import-only file.
094755e to
c2f4ecf
Compare
There was a problem hiding this comment.
The tests that are exercising nested imports should probably mention that in their titles.
| throw MigrationSourceSpanException( | ||
| "Found a second @include rule in an import-only file. " | ||
| "Import-only files should contain at most one @include.", | ||
| node.span); |
There was a problem hiding this comment.
Would it be possible to support multiple rules if we stored _importOnlyIncludes' values as lists?
There was a problem hiding this comment.
Yeah, I initially avoided it because I thought it would be complicated turning one @import rule into multiple @include rules, but I forgot we already had logic for turning one rule into multiple because an @import rule can contain multiple imports. Done.
| '${_namespaceForDeclaration(references.variables[arg]!)}.\$$name', | ||
| VariableExpression(:var name) => '\$$name', | ||
| _ => throw MigrationSourceSpanException( | ||
| 'Import-only @include rules may only pass variables as arguments.', |
There was a problem hiding this comment.
Why this restriction? It seems like at the very least it wouldn't hurt to allow literal values, and really the only semantically difficult argument I can think of would be user-defined functions (which function more or less like variables anyway).
There was a problem hiding this comment.
I initially restricted it to avoid having to recursively serialize all expressions (making sure to add any additional @use rules as necessary), but I ended up writing a serializer that can add/change namespaces as necessary, so that eliminates the need for this restriction.
This adds a `NamespacedSerializer` utility class, which allows us to copy the include rules with arbitrary arguments while still ensuring that we use the right namespaces and add any additional use rules when migrating.
jathak
left a comment
There was a problem hiding this comment.
Added NamespacedSerializer utility class to allow for arbitrary arguments to be copied from the @include rule in the import-only file. It does copy a fair bit of logic from toString() methods on some of the AST nodes, so let me know if there's a better way to implement this to avoid the duplicated code.
| throw MigrationSourceSpanException( | ||
| "Found a second @include rule in an import-only file. " | ||
| "Import-only files should contain at most one @include.", | ||
| node.span); |
There was a problem hiding this comment.
Yeah, I initially avoided it because I thought it would be complicated turning one @import rule into multiple @include rules, but I forgot we already had logic for turning one rule into multiple because an @import rule can contain multiple imports. Done.
| '${_namespaceForDeclaration(references.variables[arg]!)}.\$$name', | ||
| VariableExpression(:var name) => '\$$name', | ||
| _ => throw MigrationSourceSpanException( | ||
| 'Import-only @include rules may only pass variables as arguments.', |
There was a problem hiding this comment.
I initially restricted it to avoid having to recursively serialize all expressions (making sure to add any additional @use rules as necessary), but I ended up writing a serializer that can add/change namespaces as necessary, so that eliminates the need for this restriction.
There was a problem hiding this comment.
Consider publicly exposing a function (or several functions) from this file rather than a class, since in practice the class is only ever used to call a single method and then discarded.
| /// A utility for serializing Sass expressions while adding or changing | ||
| /// namespaces of Sass references. |
There was a problem hiding this comment.
This means that the migrator now effectively has two different ways of modifying an expression: one by patching, and now this which serializes from scratch. Is it possible to consolidate these back down into a patch? For example, could you represent this as one patch that adds the original text of the @include from the import-only file, followed by additional patches that adjust its namespaces (possibly using something like Dart Sass's interpolation maps to realign spans from the original import-only file to the new @include location)? My two practical concerns here are:
- This is duplicating a lot of the Sass inspect/serialization logic, which means more work you'll have to do any time the Sass AST changes.
- This throws away any formatting/comments/etc from the original source.
If you think this distinction makes more sense than trying to make it all work via patching, I do think it's probably worth including in the documentation some discussion of when this is appropriate relative to the original patch-based approach.
There was a problem hiding this comment.
As is, the migrator won't allow patches like this, since the patch offsets are always based on the original file, so after patching in the original text, it's not possible to add additional patches within it, but I do agree that the patch-based approach makes more sense if at all possible, so I'll look into adding something that can do the inverse: first patch the span of the original @include (without actually patching the underlying source file) and then apply that patch that patched span into the import-only file.
| [for (var item in contents) serialize(item)].join( | ||
| switch (separator) { | ||
| .comma => ', ', | ||
| .space => ' ', |
There was a problem hiding this comment.
| .space => ' ', | |
| .space || .undecided => ' ', |
There was a problem hiding this comment.
As before, since this is testing nested includes it should mention that in its title.
jathak
left a comment
There was a problem hiding this comment.
Updated the logic here to use patching instead of serialization.
This enables migration of some nested/late imports that the migrator couldn't previously handle by first wrapping the file that's depended on via nested import in a mixin and then
@includeing that mixin in the import-only file.