Skip to content

Commit a9eae1d

Browse files
committed
📚 Updates authoring docs
1 parent 1e9a685 commit a9eae1d

File tree

3 files changed

+108
-30
lines changed

3 files changed

+108
-30
lines changed

website/docs/api/codeshift-test-utils.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ it('should wrap avatar in a tooltip if name is defined', () => {
5454
return <Tooltip content=\\"foo\\"><Avatar name=\\"foo\\" /></Tooltip>;
5555
}"
5656
`);
57+
});
5758
```

website/docs/api/codeshift-utils.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ const App = () => <Button primary>Say hello</Button>;
388388

389389
### `applyMotions(j, source, motions)`
390390

391-
Applies an array of motions to your AST
391+
A helper function to apply an array of motions in sequence.
392392

393393
**Returns**
394394

website/docs/authoring.mdx

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ The file structure of your codemod will look like this:
2828

2929
```
3030
community/[package-name]/[version]
31-
/transform.ts // main entrypoint (should contain a transform)
31+
/codeshift.config.js // main entrypoint containing configuration and references to your transforms
32+
/transform.ts // main logic (should contain a transformer)
3233
/transform.spec.ts // main tests
3334
/motions // different operations that make up the codemod
3435
/[motion-name].ts // motion
@@ -39,13 +40,35 @@ community/[package-name]/[version]
3940

4041
```
4142
community/react-cool-library/18.0.0
43+
/codeshift.config.js
4244
/transform.ts
4345
/transform.spec.ts
4446
/motions
4547
/remove-ref-usage.ts
4648
/remove-ref-usage.spec.ts
4749
```
4850

51+
## Configuration
52+
53+
Each codemod package should be coupled with a `codeshift.config.js` file.
54+
The config file is the entry-point of your codemod and is responsible for holding all of the relevant
55+
metadata about it, as well as references to the transformer functions themselves.
56+
57+
They typically look like this:
58+
59+
```js
60+
export default {
61+
maintainers: ['danieldelcore'],
62+
transforms: {
63+
'18.0.0': require('./18.0.0/transform'),
64+
'19.0.0': require('./19.0.0/transform'),
65+
},
66+
};
67+
```
68+
69+
- `maintainers`: Github usernames of the people that maintain that codemod, they will be notified on PRs etc.
70+
- `transforms`: A key value pair of transforms organized by semver-compatible versions
71+
4972
## Versioning
5073

5174
You might wonder why we require that codemods are named by a semver version like `react-cool-library/18.0.0`.
@@ -62,7 +85,7 @@ Patched codemods will then be automatically published when merged to the repo, e
6285

6386
## Transformers
6487

65-
Transformers are the main entrypoint to your codemod, they are responsible for accepting a raw file and applying the appropriate modifications to it.
88+
Transformers are the main entrypoint to your codemod, they are responsible for accepting a raw file, applying the appropriate modifications to it and finally outputting the resulting AST to the original file.
6689

6790
**Example:**
6891

@@ -73,53 +96,107 @@ import updateBorderWidth from './motions/update-border-width';
7396
export default function transformer(file, { jscodeshift: j }, options) {
7497
const source = j(file.source);
7598

76-
if (hasImportDeclaration(j, source, '@atlaskit/avatar')) {
77-
// Checks if the file needs to be modified
78-
updateBorderWidth(j, source); // Execute individual motions
79-
80-
return source.toSource(options.printOptions || { quote: 'single' }); // Writes modified AST to file
99+
if (!hasImportDeclaration(j, source, 'my')) {
100+
return file.source; // Writes original untouched file
81101
}
82102

83-
return file.source; // Writes original untouched file
103+
// Checks if the file needs to be modified
104+
updateBorderWidth(j, source); // Execute individual motions
105+
106+
return source.toSource(options.printOptions); // Writes modified AST to file
84107
}
85108
```
86109

87110
## Motions
88111

89112
A motion (aka migration) is what we call specific actions performed within a codemod. For example, `updateBorderWidth` or `removeDeprecatedProps`.
90-
They can be simply thought of a functions that are responsible for a single action within a codemod. It is not required but they are a helpful design pattern to isolate more complicated parts of your codemod into discrete pieces.
113+
They can be simply thought of a functions that are responsible for a single action within a codemod. It is not required but they are highly recommended as
114+
a helpful design pattern to isolate more complicated parts of your codemod into discrete pieces.
91115

92116
**Example:**
93117

94118
```ts
95-
function removeDeprecatedProps(
96-
j: core.JSCodeshift,
97-
source: ReturnType<typeof j>,
98-
) {
99-
// TODO:
119+
function removeDeprecatedProps(j, source) {
120+
// Some logic here
100121
}
101122
```
102123

103-
## Testing
124+
Motions can then be applied from the main transform, just like any other function.
104125

105-
It's very likely that consumers will run into all sorts of edge-cases when running your transform. That's why it's important to start by writing some tests to assert it's behavior. Luckily, [jscodeshift provides some testing utilities](https://github.com/facebook/jscodeshift#unit-testing).
126+
```ts
127+
import { hasImportDeclaration } from '@codeshift/utils';
128+
import removeDeprecatedProps from './motions/remove-deprecated-props';
129+
import restructureImports from './motions/restructure-imports';
130+
131+
export default function transformer(file, { jscodeshift: j }, options) {
132+
const source = j(file.source);
106133

107-
When creating a codemod, it's best to always try to write your tests first (TDD style). Think about the start and end state and how you might be able to achieve that. Also, make sure to consider as many edge-cases as you possibly can.
134+
// Execute individual motions
135+
removeDeprecatedProps(j, source);
136+
restructureImports(j, source);
108137

109-
**Example:**
138+
return source.toSource(options.printOptions); // Writes modified AST to file
139+
}
140+
```
141+
142+
Each motion receives a reference to the AST (`source`) which it can then manipulate as required.
143+
144+
Alternatively, you can use the utility function [applyMotions](./utils#applymotionsj-source-motions) to run motions in sequence.
110145

111146
```ts
112-
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
113-
const transform = require('../myTransform');
114-
const transformOptions = {};
115-
116-
defineInlineTest(
117-
transform,
118-
transformOptions,
119-
'input',
120-
'expected output',
121-
'test name (optional)',
122-
);
147+
import { applyMotions } from '@codeshift/utils';
148+
import removeDeprecatedProps from './motions/remove-deprecated-props';
149+
import restructureImports from './motions/restructure-imports';
150+
151+
export default function transformer(file, { jscodeshift: j }, options) {
152+
const source = j(file.source);
153+
154+
// Execute a series of motions in order
155+
applyMotions(j, source, [removeDeprecatedProps, restructureImports]);
156+
157+
return source.toSource(options.printOptions);
158+
}
123159
```
124160

161+
## Testing
162+
163+
It's very likely that consumers will run into all sorts of edge-cases when running your transform.
164+
That's why it's important to start by writing some tests to assert it's behavior. Luckily, both [CodeshiftCommunity](./test-utils) & [jscodeshift](https://github.com/facebook/jscodeshift#unit-testing) provides testing utilities to help.
165+
166+
When creating a codemod, it's best to always try to write your tests first (TDD style).
167+
Think about the start and end state and how you might be able to achieve that. Also, make sure to consider as many edge-cases as you possibly can.
168+
125169
For more information, please see the [testing docs](testing).
170+
171+
**Example:**
172+
173+
```ts
174+
import { applyTransform } from '@codeshift/test-utils';
175+
176+
import * as transformer from '../transform';
177+
178+
describe('MyTransform', () => {
179+
it('should wrap component in a tooltip if name is defined', () => {
180+
const result = applyTransform(
181+
transformer,
182+
`
183+
import MyComponent from 'my-component';
184+
185+
const App = () => {
186+
return <MyComponent name="foo" />;
187+
}
188+
`,
189+
{ parser: 'tsx' },
190+
);
191+
192+
expect(result).toMatchInlineSnapshot(`
193+
"import Tooltip from 'my-tooltip';
194+
import MyComponent from 'my-component';
195+
196+
const App = () => {
197+
return <Tooltip content=\\"foo\\"><MyComponent name=\\"foo\\" /></Tooltip>;
198+
}"
199+
`);
200+
});
201+
});
202+
```

0 commit comments

Comments
 (0)