Skip to content

Commit 8c19896

Browse files
committed
Utilities to handle the Partial Fix case in SASS Migrations
1 parent ba57649 commit 8c19896

File tree

5 files changed

+127
-36
lines changed

5 files changed

+127
-36
lines changed

.changeset/odd-pans-poke.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/polaris-migrator': minor
3+
---
4+
5+
Expose utilities for SASS Migrations to leverage the Suggestion-on-partial-fix pattern

polaris-migrator/README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ import type {PolarisMigrator} from '../../utilities/sass';
293293

294294
const replaceHelloWorld: PolarisMigrator = (_, {methods}, context) => {
295295
return (root) => {
296-
root.walkDecls((decl) => {
296+
methods.walkDecls(root, (decl) => {
297297
const parsedValue = valueParser(decl.value);
298298
parsedValue.walk((node) => {
299299
if (isSassFunction('hello', node)) {
@@ -311,12 +311,9 @@ const replaceHelloWorld: PolarisMigrator = (_, {methods}, context) => {
311311
return StopWalkingFunctionNodes;
312312
}
313313
});
314-
315314
if (context.fix) {
316315
decl.value = parsedValue.toString();
317316
}
318-
319-
methods.flushReports();
320317
});
321318
};
322319
};

polaris-migrator/src/migrations/replace-spacing-lengths/replace-spacing-lengths.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,17 @@ export default createSassMigrator(
1818
const namespacedRem = namespace('rem', options);
1919

2020
return (root) => {
21-
root.walkDecls((decl) => {
21+
methods.walkDecls(root, (decl) => {
2222
if (!spaceProps.has(decl.prop)) return;
2323

2424
const parsedValue = valueParser(decl.value);
2525

2626
handleSpaceProps();
2727

28-
const newValue = parsedValue.toString();
29-
30-
if (context.fix && newValue !== decl.value) {
31-
if (methods.getReportsForNode(decl)) {
32-
// The "partial fix" case: When there's a new value AND a report.
33-
methods.report({
34-
node: decl,
35-
severity: 'suggestion',
36-
message: `${decl.prop}: ${parsedValue.toString()}`,
37-
});
38-
} else {
39-
decl.value = parsedValue.toString();
40-
}
28+
if (context.fix) {
29+
decl.value = parsedValue.toString();
4130
}
4231

43-
methods.flushReports();
44-
4532
function handleSpaceProps() {
4633
parsedValue.walk((node) => {
4734
if (isNumericOperator(node)) {

polaris-migrator/src/utilities/sass.ts

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import type {FileInfo, API, Options} from 'jscodeshift';
2-
import postcss, {Root, Result, Plugin, Node as PostCSSNode} from 'postcss';
2+
import postcss, {
3+
Root,
4+
Result,
5+
Plugin,
6+
Container,
7+
Declaration,
8+
Node as PostCSSNode,
9+
Rule as PostCSSRule,
10+
Comment as PostCSSComment,
11+
AtRule,
12+
} from 'postcss';
313
import valueParser, {
414
Node,
515
ParsedValue,
@@ -259,7 +269,7 @@ interface PluginOptions extends Options, NamespaceOptions {}
259269

260270
interface Report {
261271
node: PostCSSNode;
262-
severity: 'warning' | 'error' | 'suggestion';
272+
severity: 'warning' | 'error';
263273
message: string;
264274
}
265275

@@ -286,14 +296,32 @@ type StylelintRule<P = any, S = any> = StylelintRuleBase<P, S> & {
286296
};
287297
// End: Extracted from stylelint
288298

299+
type Walker<N extends PostCSSNode> = (node: N) => false | void;
300+
289301
export type PolarisMigrator = (
290302
primaryOption: true,
291303
secondaryOptions: {
292304
options: {[key: string]: any};
293305
methods: {
294306
report: (report: Report) => void;
295-
flushReports: () => void;
296-
getReportsForNode: (node: PostCSSNode) => Report[] | undefined;
307+
each: <T extends Container>(root: T, walker: Walker<PostCSSNode>) => void;
308+
walk: <T extends Container>(root: T, walker: Walker<PostCSSNode>) => void;
309+
walkComments: <T extends Container>(
310+
root: T,
311+
walker: Walker<PostCSSComment>,
312+
) => void;
313+
walkAtRules: <T extends Container>(
314+
root: T,
315+
atRuleWalker: Walker<AtRule>,
316+
) => void;
317+
walkDecls: <T extends Container>(
318+
root: T,
319+
declWalker: Walker<Declaration>,
320+
) => void;
321+
walkRules: <T extends Container>(
322+
root: T,
323+
ruleWalker: Walker<PostCSSRule>,
324+
) => void;
297325
};
298326
},
299327
context: PluginContext,
@@ -376,18 +404,49 @@ export function createSassMigrator(name: string, ruleFn: PolarisMigrator) {
376404

377405
for (const report of reportsForNode) {
378406
node.before(
379-
report.severity === 'suggestion'
380-
? createInlineComment(report.message)
381-
: createInlineComment(`${report.severity}: ${report.message}`, {
382-
prose: true,
383-
}),
407+
createInlineComment(`${report.severity}: ${report.message}`, {
408+
prose: true,
409+
}),
384410
);
385411
}
386412
}
387413
reports.clear();
388414
};
389415

390-
const getReportsForNode = (node: PostCSSNode) => reports.get(node);
416+
function createWalker<T extends PostCSSNode>(args: {
417+
walker: (node: T) => false | void;
418+
serialiseSuggestion: (node: T) => string;
419+
}): (node: T) => false | void {
420+
const {walker, serialiseSuggestion} = args;
421+
422+
return (node: T) => {
423+
let oldNode: T;
424+
if (context.fix) {
425+
oldNode = node.clone();
426+
}
427+
428+
const result = walker(node);
429+
430+
const isPartialFix =
431+
context.fix &&
432+
reports.has(node) &&
433+
node.toString() !== oldNode!.toString();
434+
435+
flushReportsAsComments();
436+
437+
// Our migrations have an opinion on partial fixes (when multiple
438+
// issues are found in a single node, and some but not all can be
439+
// fixed): We dump out the partial fix result as a comment
440+
// immediately above the node.
441+
if (isPartialFix) {
442+
node.before(createInlineComment(serialiseSuggestion(node)));
443+
// Undo changes
444+
node.replaceWith(oldNode!);
445+
}
446+
447+
return result;
448+
};
449+
}
391450

392451
return ruleFn(
393452
primary,
@@ -399,8 +458,54 @@ export function createSassMigrator(name: string, ruleFn: PolarisMigrator) {
399458
options: secondaryOptions,
400459
methods: {
401460
report: addDedupedReport,
402-
flushReports: flushReportsAsComments,
403-
getReportsForNode,
461+
each(root, walker) {
462+
root.each(
463+
createWalker({
464+
walker,
465+
serialiseSuggestion: (node) => node.toString(),
466+
}),
467+
);
468+
},
469+
walk(root, walker) {
470+
root.walk(
471+
createWalker({
472+
walker,
473+
serialiseSuggestion: (node) => node.toString(),
474+
}),
475+
);
476+
},
477+
walkAtRules(root, walker) {
478+
root.walkAtRules(
479+
createWalker({
480+
walker,
481+
serialiseSuggestion: (node) => `@${node.name} ${node.params}`,
482+
}),
483+
);
484+
},
485+
walkComments(root, walker) {
486+
root.walkComments(
487+
createWalker({
488+
walker,
489+
serialiseSuggestion: (node) => node.text,
490+
}),
491+
);
492+
},
493+
walkDecls(root, walker) {
494+
root.walkDecls(
495+
createWalker({
496+
walker,
497+
serialiseSuggestion: (node) => `${node.prop}: ${node.value}`,
498+
}),
499+
);
500+
},
501+
walkRules(root, walker) {
502+
root.walkRules(
503+
createWalker({
504+
walker,
505+
serialiseSuggestion: (node) => node.selector,
506+
}),
507+
);
508+
},
404509
},
405510
},
406511
context,

polaris-migrator/templates/sass-migration/{{kebabCase migrationName}}/{{kebabCase migrationName}}.ts.hbs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const {{camelCase migrationName}}: PolarisMigrator = (
1616
context,
1717
) => {
1818
return (root) => {
19-
root.walkDecls((decl) => {
19+
methods.walkDecls(root, (decl) => {
2020
// Using the parsedValue allows easy detection of individual functions and
2121
// properties. Particularly useful when dealing with shorthand
2222
// declarations such as `border`, `padding`, etc.
@@ -45,9 +45,6 @@ const {{camelCase migrationName}}: PolarisMigrator = (
4545
if (context.fix) {
4646
decl.value = parsedValue.toString();
4747
}
48-
49-
// Ensure all generated reports are flushed to the appropriate output
50-
methods.flushReports();
5148
});
5249
};
5350
};

0 commit comments

Comments
 (0)