Skip to content

Commit 4d70df3

Browse files
committed
Update template + add docs
1 parent 138c456 commit 4d70df3

File tree

2 files changed

+83
-64
lines changed

2 files changed

+83
-64
lines changed

polaris-migrator/README.md

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,18 @@ Be aware that this may also create additional code changes in your codebase, we
235235
npx @shopify/polaris-migrator replace-sass-spacing <path>
236236
```
237237

238-
## Creating a migration
238+
## Creating Migrations
239239

240-
### Setup
240+
Sometimes referred to as "codemods", migrations are JavaScript functions which modify some code from one form to another (eg; to move between breaking versions of `@shopify/polaris`). ASTs (Abstract Syntax Trees) are used to "walk" through the code in discreet, strongly typed steps, called "nodes". All changes made to nodes (and thus the AST) are then written out as the new/"migrated" version of the code.
241241

242-
Run `yarn new-migration` to generate a new migration from a template.
242+
`polaris-migrator` supports two types of migrations:
243+
244+
- SASS Migrations
245+
- Typescript Migrations
246+
247+
### Creating a SASS migration
248+
249+
Run `yarn new-migration` to generate a new migration from the `sass-migration` template:
243250

244251
```sh
245252
❯ yarn new-migration
@@ -250,7 +257,7 @@ $ plop
250257
typescript-migration
251258
```
252259

253-
We will use the `sass-migration` and call our migration `replace-sass-function` for this example. Provide the name of your migration:
260+
Next, provide the name of your migration. For example; `replace-sass-function`:
254261

255262
```sh
256263
? [PLOP] Please choose a generator. sass-migration
@@ -269,45 +276,50 @@ migrations
269276
└── replace-sass-function.test.ts
270277
```
271278

272-
### Writing migration function
279+
#### The SASS migration function
273280

274-
A migration is simply a javascript function which serves as the entry-point for your codemod. The `replace-sass-function.ts` file defines a "migration" function. This function is named the same as the provided migration name, `replace-sass-function`, and is the default export of the file.
281+
Each migrator has a default export adhering to the [Stylelint Rule API](https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md). A PostCSS AST is passed as the `root` and can be mutated inline, or emit warning/error reports.
275282

276-
Some example code has been provided for each template. You can make any migration code adjustments in the migration function. For Sass migrations, a [PostCSS plugin](https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md) is used to parse and transform the source code provided by the [jscodeshift](https://github.com/facebook/jscodeshift).
283+
Continuing the example, here is what the migration may look like if our goal is to replace the Sass function `hello()` with `world()`.
277284

278285
```ts
279286
// polaris-migrator/src/migrations/replace-sass-function/replace-sass-function.ts
280-
281-
import type {FileInfo} from 'jscodeshift';
282-
import postcss, {Plugin} from 'postcss';
283-
import valueParser from 'postcss-value-parser';
284-
285-
const plugin = (): Plugin => ({
286-
postcssPlugin: 'replace-sass-function',
287-
Declaration(decl) {
288-
// const prop = decl.prop;
289-
const parsedValue = valueParser(decl.value);
290-
291-
parsedValue.walk((node) => {
292-
if (!(node.type === 'function' && node.value === 'hello')) return;
293-
294-
node.value = 'world';
287+
import {
288+
isSassFunction,
289+
createStylelintRule,
290+
} from '../../utilities/sass';
291+
292+
function relaceSassFunction(_, {methods, options}, context) {
293+
return (root) => {
294+
methods.walkDecls(root, (decl, parsedValue) => {
295+
parsedValue.walk((node) => {
296+
if (isSassFunction('hello', node)) {
297+
if (context.fix) {
298+
node.value = 'world';
299+
} else {
300+
methods.report({
301+
node: decl,
302+
severity: 'error',
303+
message: 'Method hello() is no longer supported. Please migrate to world().'
304+
});
305+
}
306+
307+
return StopWalkingFunctionNodes;
308+
}
309+
});
295310
});
311+
};
312+
};
296313

297-
decl.value = parsedValue.toString();
298-
},
299-
});
300-
301-
export default function replaceSassFunction(fileInfo: FileInfo) {
302-
return postcss(plugin()).process(fileInfo.source, {
303-
syntax: require('postcss-scss'),
304-
}).css;
305-
}
314+
export default createStylelintRule(
315+
'replace-sass-function',
316+
relaceSassFunction
317+
);
306318
```
307319

308-
This example migration will replace the Sass function `hello()` with `world()`.
320+
A more complete example can be seen in [`replace-spacing-lengths.ts`](https://github.com/Shopify/polaris/blob/main/polaris-migrator/src/migrations/replace-spacing-lengths/replace-spacing-lengths.ts).
309321

310-
### Testing
322+
#### Testing
311323

312324
The template will also generate starting test files you can use to test your migration. In your migrations `tests` folder, you can see 3 files:
313325

@@ -317,6 +329,8 @@ The template will also generate starting test files you can use to test your mig
317329

318330
The main test file will load the input/output fixtures to test your migration against. You can configure additional fixtures and test migration options (see the `replace-sass-spacing.test.ts` as an example).
319331

332+
## Running Migrations
333+
320334
Run tests locally from workspace root by filtering to the migrations package:
321335

322336
```sh
Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1-
import type {FileInfo} from 'jscodeshift';
2-
import postcss, {Plugin} from 'postcss';
3-
import valueParser from 'postcss-value-parser';
4-
5-
const processed = Symbol('processed');
6-
7-
const plugin = (): Plugin => ({
8-
postcssPlugin: '{{kebabCase migrationName}}',
9-
Declaration(decl) {
10-
// @ts-expect-error - Skip if processed so we don't process it again
11-
if (decl[processed]) return;
12-
13-
// const prop = decl.prop;
14-
const parsedValue = valueParser(decl.value);
15-
16-
parsedValue.walk((node) => {
17-
if (!(node.type === 'function' && node.value === 'hello')) return;
18-
19-
node.value = 'world';
1+
import { isSassFunction, createStylelintRule } from '../../utilities/sass';
2+
3+
// options will be passed in from cli / config.
4+
// Use context.fix to change behaviour based on if the user has passed the
5+
// `--fix` flag (always true for `polaris-migrator` CLI).
6+
function {{camelCase migrationName}}(_, {methods, options}, context) {
7+
return (root) => {
8+
methods.walkDecls(root, (decl, parsedValue) => {
9+
// Using the parsedValue allows easy detection of individual functions and
10+
// properties. Particularly useful when dealing with shorthand
11+
// declarations such as `border`, `padding`, etc.
12+
parsedValue.walk((node) => {
13+
if (isSassFunction('hello', node)) {
14+
if (context.fix) {
15+
// When fixing, we mutate the node directly.
16+
node.value = 'world';
17+
} else {
18+
// When not fixing, emit a report which will be aggregated and shown
19+
// to the user once all migrations are run.
20+
methods.report({
21+
node: decl,
22+
severity: 'error',
23+
message: 'Method hello() is no longer supported. Please migrate to world().'
24+
});
25+
}
26+
27+
// We do not want to recursively walk the function's arguments.
28+
return StopWalkingFunctionNodes;
29+
}
30+
});
2031
});
32+
};
33+
};
2134

22-
decl.value = parsedValue.toString();
23-
24-
// @ts-expect-error - Mark the declaration as processed
25-
decl[processed] = true;
26-
},
27-
});
28-
29-
export default function {{camelCase migrationName}}(fileInfo: FileInfo) {
30-
return postcss(plugin()).process(fileInfo.source, {
31-
syntax: require('postcss-scss'),
32-
}).css;
33-
}
35+
export default createStylelintRule(
36+
'{{kebabCase migrationName}}',
37+
{{camelCase migrationName}}
38+
);

0 commit comments

Comments
 (0)