Skip to content

Commit e1b4339

Browse files
committed
📚 General documentation upgrades
1 parent 12cc69a commit e1b4339

File tree

7 files changed

+180
-38
lines changed

7 files changed

+180
-38
lines changed

website/docs/authoring.mdx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,8 @@ Example:
6060
import { hasImportDeclaration } from '@codeshift/utils';
6161
import updateBorderWidth from './motions/update-border-width';
6262

63-
export default function transformer(
64-
fileInfo: FileInfo,
65-
{ jscodeshift: j }: API,
66-
options: Options,
67-
) {
68-
const source = j(fileInfo.source);
63+
export default function transformer(file, { jscodeshift: j }, options) {
64+
const source = j(file.source);
6965

7066
if (hasImportDeclaration(j, source, '@atlaskit/avatar')) {
7167
// Checks if the file needs to be modified
@@ -74,13 +70,13 @@ export default function transformer(
7470
return source.toSource(options.printOptions || { quote: 'single' }); // Writes modified AST to file
7571
}
7672

77-
return fileInfo.source; // Writes original untouched file
73+
return file.source; // Writes original untouched file
7874
}
7975
```
8076

8177
## Motions
8278

83-
A motion is what we call specific actions performed within a codemod. For example, `updateBorderWidth` or `removeDeprecatedProps`.
79+
A motion (aka migration) is what we call specific actions performed within a codemod. For example, `updateBorderWidth` or `removeDeprecatedProps`.
8480
They can be simply thought of a functions that are responsible for a single action within a codemod. This is not required but can help to isolate more complicated parts of your codemod into a single place.
8581

8682
Example:

website/docs/ecosystem.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The ecosystem is full of amazing resources and examples, please check them out.
3232
- [Getting Started with Codemods](https://www.sitepoint.com/getting-started-with-codemods/)
3333
- [Codemods: Effective, Automated Refactoring](https://www.sitepen.com/blog/codemods-effective-automated-refactoring)
3434
- [Creating a custom transform for jscodeshift](https://skovy.dev/jscodeshift-custom-transform/)
35+
- [Building AST nodes from source code](https://dev.to/rajasegar/building-ast-nodes-from-source-code-3p49)
3536

3637
## Misc
3738

website/docs/guides/your-first-codemod.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ export default function transform(file, { jscodeshift: j }, options) {
132132
const source = j(file.source);
133133

134134
source
135-
.find(j.JSXIdentifier) // (1) Find all JSX props
136-
.filter(path => path.node.name === 'isDisabled') // (2) Filter by name `isDisabled`
135+
.find(j.JSXAttribute) // (1) Find all JSX props
136+
.filter(path => path.node.name.name === 'isDisabled') // (2) Filter by name `isDisabled`
137137
.remove(); // (3) We remove any `isDisabled` prop from the AST
138138

139139
return source.toSource(options.printOptions);
@@ -168,8 +168,8 @@ export default function transform(file, { jscodeshift: j }, options) {
168168
source
169169
+ .find(j.JSXElement)
170170
+ .filter(path => path.value.openingElement.name.name === 'Button')
171-
.find(j.JSXIdentifier) // (1) Find all JSX props
172-
.filter(path => path.node.name === 'isDisabled') // (2) Filter by name `isDisabled`
171+
.find(j.JSXAttribute) // (1) Find all JSX props
172+
.filter(path => path.node.name.name === 'isDisabled') // (2) Filter by name `isDisabled`
173173
.remove(); // (3) We remove any `isDisabled` prop from the AST
174174

175175
return source.toSource(options.printOptions);
@@ -217,8 +217,8 @@ To avoid formatting issues and to speed up running transforms across large codeb
217217
```ts
218218
export default function transform(file, { jscodeshift: j }, options) {
219219
const hasIsDisabledProp = !!source
220-
.find(j.JSXIdentifier)
221-
.filter(path.node.name === 'isDisabled')
220+
.find(j.JSXAttribute)
221+
.filter(path.node.name.name === 'isDisabled')
222222
.length
223223

224224
if (!hasIsDisabledProp) {

website/docs/guiding-principles.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ slug: /guiding-principles
66

77
There are several guiding principles we follow in this project.
88

9-
## Target a package & version
9+
## Codemod should target a version of package
1010

1111
- APIs are always changing
1212
- Codemods written at a specific point of time aren't always going to work into the future
@@ -18,6 +18,8 @@ This gives us a lot of benefits,
1818
2. Codemods can be 'playable in a sequence', for example migrating from an older version of `@atlaskit/button` to the latest is possible for example `v14 -> v15 -> v16`.
1919
3. TODO`
2020

21+
## Codemods should be playable in sequence
22+
2123
## Everyone should ship codemods
2224

2325
Codemods aren't complicated, anyone who has writtin anything in jQuery should be able to write a codemod with relative ease.

website/docs/recipes/react.mdx

Lines changed: 149 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,33 @@ slug: /react
1414
export default function transformer(file, { jscodeshift: j }, options) {
1515
const source = j(file.source);
1616

17+
source
18+
.find(j.JSXElement)
19+
// Find all components called button
20+
.filter(path => path.value.openingElement.name.name === 'button')
21+
.forEach(element => {
22+
const newComponent = j.jsxElement(
23+
j.jsxOpeningElement(element.node.openingElement.name, [
24+
...element.node.openingElement.attributes,
25+
// build and insert our new prop
26+
j.jsxAttribute(j.jsxIdentifier('disabled'), j.stringLiteral('true')),
27+
]),
28+
element.node.closingElement,
29+
element.node.children,
30+
);
31+
32+
// Replace our original component with our modified one
33+
j(element).replaceWith(newComponent);
34+
});
35+
1736
return source.toSource();
1837
}
1938
```
2039

2140
**Input:**
2241

2342
```jsx
24-
import React, { useEffect } from 'react';
43+
import React from 'react';
2544

2645
const Button = props => {
2746
return <button className="button">Submit</button>;
@@ -31,11 +50,11 @@ const Button = props => {
3150
**Output:**
3251

3352
```diff
34-
import React, { useEffect } from 'react';
53+
import React from 'react';
3554

3655
const Button = props => {
3756
- return <button className="button">Submit</button>;
38-
+ return <button className="button" onClick={() => console.log('Hello, World!')}>Submit</button>;
57+
+ return <button className="button" disabled="true">Submit</button>;
3958
};
4059
```
4160

@@ -47,28 +66,39 @@ const Button = props => {
4766
export default function transformer(file, { jscodeshift: j }, options) {
4867
const source = j(file.source);
4968

69+
source
70+
.find(j.JSXElement)
71+
.filter(path => path.value.openingElement.name.name === 'button') // Find all button jsx elements
72+
.find(j.JSXAttribute) // Find all attributes (props) on the button
73+
.filter(path => path.node.name.name === 'onClick') // Filter to only props called onClick
74+
.remove(); // Remove everything that matched
75+
5076
return source.toSource();
5177
}
5278
```
5379

5480
**Input:**
5581

5682
```jsx
57-
import React, { useEffect } from 'react';
83+
import React from 'react';
5884

5985
const Button = props => {
60-
return <button className="button">Submit</button>;
86+
return (
87+
<button className="button" onClick={() => console.log('Hello, World!')}>
88+
Submit
89+
</button>
90+
);
6191
};
6292
```
6393

6494
**Output:**
6595

6696
```diff
67-
import React, { useEffect } from 'react';
97+
import React from 'react';
6898

6999
const Button = props => {
70-
- return <button className="button">Submit</button>;
71-
+ return <button className="button" onClick={() => console.log('Hello, World!')}>Submit</button>;
100+
- return <button className="button" onClick={() => console.log('Hello, World!')}>Submit</button>;
101+
+ return <button className="button">Submit</button>;
72102
};
73103
```
74104

@@ -87,7 +117,7 @@ export default function transformer(file, { jscodeshift: j }, options) {
87117
**Input:**
88118

89119
```jsx
90-
import React, { useEffect } from 'react';
120+
import React from 'react';
91121

92122
const Button = props => {
93123
return <button className="button">Submit</button>;
@@ -97,7 +127,7 @@ const Button = props => {
97127
**Output:**
98128

99129
```diff
100-
import React, { useEffect } from 'react';
130+
import React from 'react';
101131

102132
const Button = props => {
103133
- return <button className="button">Submit</button>;
@@ -132,28 +162,69 @@ export default function transformer(file, { jscodeshift: j }, options) {
132162

133163
### Wrapping components
134164

165+
Wrapping react components with react components is a fairly common opperation.
166+
167+
Simply follow this fairly simple set of steps:
168+
169+
1. Find the component you want to wrap
170+
2. Create a new component and pass the component to be wrapped in as a child node
171+
3. Replace the original component with a wrapped version of itself
172+
135173
**Transform:**
136174

137175
```javascript
138176
export default function transformer(file, { jscodeshift: j }, options) {
139177
const source = j(file.source);
140178

179+
// Find all components named "Avatar"
180+
source.findJSXElements('Avatar').forEach(element => {
181+
/**
182+
* Create a new JSXElement called "Tooltip" and use the original Avatar component as children
183+
*/
184+
const wrappedAvatar = j.jsxElement(
185+
j.jsxOpeningElement(j.jsxIdentifier('Tooltip'), [
186+
// Create a prop on the tooltip so it works as expected
187+
j.jsxAttribute(
188+
j.jsxIdentifier('content'),
189+
j.stringLiteral('Hello, there!'),
190+
),
191+
]),
192+
j.jsxClosingElement(j.jsxIdentifier('Tooltip')),
193+
[element.value], // Pass in the original component as children
194+
);
195+
196+
j(element).replaceWith(wrappedAvatar);
197+
});
198+
141199
return source.toSource();
142200
}
143201
```
144202

145203
**Input:**
146204

147205
```jsx
206+
import { Avatar, Tooltip } from 'component-lib';
207+
208+
const App = () => {
209+
return <Avatar name="foo" />;
210+
};
148211
```
149212

150213
**Output:**
151214

152215
```diff
153-
216+
import {Avatar, Tooltip } from 'component-lib';
217+
218+
const App = () => {
219+
return (
220+
+ <Tooltip content="foo">
221+
<Avatar name="foo" />
222+
+ </Tooltip>
223+
);
224+
}
154225
```
155226

156-
### Render props
227+
### Inserting children nodes
157228

158229
**Transform:**
159230

@@ -176,25 +247,90 @@ export default function transformer(file, { jscodeshift: j }, options) {
176247

177248
```
178249

179-
### Inserting children nodes
250+
### Render props
251+
252+
Moving between different types of React composition strategies, like for example, from component props to [render props](https://reactjs.org/docs/render-props.html#using-props-other-than-render) is could be something you want to do between major versions.
253+
This might seem difficult on the surface, but think about it like every other codemod. First we need to find the component, replace it with a modified copy of itself and finally insert a function as children.
180254

181255
**Transform:**
182256

183257
```javascript
258+
import { getJSXAttributesByName } from '@codeshift/utils';
259+
184260
export default function transformer(file, { jscodeshift: j }, options) {
185261
const source = j(file.source);
186262

263+
source.findJSXElements('Avatar').forEach(element => {
264+
// Find props all JSXAttributes with a prop called "component"
265+
// (Using the getJSXAttributeByName util here for simplicity)
266+
const componentProp = getJSXAttributesByName(j, element, 'component').get();
267+
// Grabs the name of the component passed into the "component" prop
268+
const componentName = j(componentProp)
269+
.find(j.JSXExpressionContainer)
270+
.find(j.Expression)
271+
.get().value.name;
272+
273+
// Remove it since it's no longer required on the wrapping component
274+
j(componentProp).remove();
275+
276+
// Create a new child component based on the component prop and spread props onto it
277+
const customComponent = j.jsxElement(
278+
j.jsxOpeningElement(
279+
j.jsxIdentifier(componentName),
280+
[j.jsxSpreadAttribute(j.identifier('props'))],
281+
true,
282+
),
283+
);
284+
285+
/**
286+
* Here's where it gets interesting.
287+
* We create a render prop function and pass in `customComponent` as the return value
288+
*/
289+
const childrenExpression = j.jsxExpressionContainer(
290+
j.arrowFunctionExpression([j.identifier('props')], customComponent),
291+
);
292+
293+
/**
294+
* Then finally, we replace our original component with the following.
295+
* Taking properties from the original component and combining them with our new render prop function
296+
*/
297+
j(element).replaceWith(
298+
j.jsxElement(
299+
j.jsxOpeningElement(
300+
element.value.openingElement.name,
301+
element.value.openingElement.attributes,
302+
false,
303+
),
304+
j.jsxClosingElement(element.value.openingElement.name),
305+
[childrenExpression],
306+
),
307+
);
308+
});
309+
187310
return source.toSource();
188311
}
189312
```
190313

191314
**Input:**
192315

193316
```jsx
317+
import Avatar from '@component-lib/avatar';
318+
319+
const App = () => {
320+
return <Avatar name="Dan" component={CustomAvatar} />;
321+
};
194322
```
195323

196324
**Output:**
197325

198326
```diff
199327

328+
import Avatar from '@component-lib/avatar';
329+
330+
const App = () => {
331+
return (
332+
- <Avatar name="Dan" component={CustomAvatar} />
333+
+ <Avatar name="Dan">{props => <CustomAvatar {...props} />}</Avatar>
334+
+ );
335+
}
200336
```

0 commit comments

Comments
 (0)