Skip to content

Commit 7dabad1

Browse files
committed
Add codemods for breaking API changes.
1 parent 28c1f7a commit 7dabad1

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ error states, without assumptions about the shape of your data or the type of re
5050

5151
[abortable fetch]: https://developers.google.com/web/updates/2017/09/abortable-fetch
5252

53-
> ## Upgrading to v6
53+
> ## Upgrading to v8
5454
>
55-
> Version 6 comes with a breaking change. See [Upgrading](#upgrading) for details.
55+
> Version 8 comes with breaking changes. See [Upgrading](#upgrading) for details.
56+
> A [codemod](https://github.com/ghengeveld/react-async/tree/master/codemods) is available.
5657
5758
# Table of Contents
5859

@@ -121,11 +122,26 @@ yarn add react-async
121122
122123
### Upgrading
123124

125+
#### Upgrade to v8
126+
127+
All standalone helper components were renamed to avoid import naming collision.
128+
129+
- `<Initial>` was renamed to `<IfInitial>`.
130+
- `<Pending>` was renamed to `<IfPending>`.
131+
- `<Fulfilled>` was renamed to `<IfFulfilled>`.
132+
- `<Rejected>` was renamed to `<IfRejected`.
133+
- `<Settled>` was renamed to `<IfSettled>`.
134+
135+
> A [codemod](https://github.com/ghengeveld/react-async/tree/master/codemods) is available to automate the upgrade.
136+
124137
#### Upgrade to v6
125138

126139
- `<Async.Pending>` was renamed to `<Async.Initial>`.
140+
- Some of the other helpers were also renamed, but the old ones remain as alias.
127141
- Don't forget to deal with any custom instances of `<Async>` when upgrading.
128142

143+
> A [codemod](https://github.com/ghengeveld/react-async/tree/master/codemods) is available to automate the upgrade.
144+
129145
#### Upgrade to v4
130146

131147
- `deferFn` now receives an `args` array as the first argument, instead of arguments to `run` being spread at the front

codemods/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# React Async codemods
2+
3+
These codemods enable you to automatically upgrade your codebase to handle breaking changes in
4+
React Async's API.
5+
6+
## Warning
7+
8+
Be aware: **codemods transform your source code in place**. Make sure that your files are in
9+
version control before running a codemod.
10+
11+
These codemods come without warranty. They will work fine most of the time, but you should always
12+
verify their output. Also, **do not run a codemod more than once.**
13+
14+
## Running a codemod
15+
16+
These codemods are based on [jscodeshift](https://github.com/facebook/jscodeshift). Refer to their
17+
docs for specifics.
18+
19+
```bash
20+
npx jscodeshift <target_dir> -t <transform_script>
21+
```
22+
23+
Where `<target_dir>` should be replaced with the path to your project's source directory and
24+
`<transform_script>` should be replaced by the URL of the codemod.
25+
26+
For example:
27+
28+
```bash
29+
npx jscodeshift . -t https://raw.githubusercontent.com/ghengeveld/react-async/master/codemods/v6.js
30+
```
31+
32+
This will apply the codemod for [v6](https://github.com/ghengeveld/react-async/blob/master/codemods/v6.js)
33+
to the current working directory (`.`).

codemods/v6.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* This renames:
3+
* - <Async.Pending> to <Async.Initial>
4+
* - <Async.Loading> to <Async.Pending>
5+
* - <Async.Resolved> to <Async.Fulfilled>
6+
*
7+
* This includes any custom instances created with createInstance().
8+
*/
9+
10+
export default function transform({ path, source }, api) {
11+
if (path.includes("/node_modules/")) return
12+
13+
const j = api.jscodeshift
14+
const root = j(source)
15+
16+
const renameJsxMembers = parentName => {
17+
root
18+
.find(j.JSXMemberExpression, { object: { name: parentName }, property: { name: "Pending" } })
19+
.forEach(node => (node.value.property.name = "Initial"))
20+
root
21+
.find(j.JSXMemberExpression, { object: { name: parentName }, property: { name: "Loading" } })
22+
.forEach(node => (node.value.property.name = "Pending"))
23+
root
24+
.find(j.JSXMemberExpression, { object: { name: parentName }, property: { name: "Resolved" } })
25+
.forEach(node => (node.value.property.name = "Fulfilled"))
26+
}
27+
28+
// Rename instances using default import
29+
root
30+
.find(j.ImportDeclaration, { source: { value: "react-async" } })
31+
.find(j.ImportDefaultSpecifier)
32+
.forEach(node => renameJsxMembers(node.value.local.name))
33+
34+
// Rename instances using named `Async` import
35+
root
36+
.find(j.ImportDeclaration, { source: { value: "react-async" } })
37+
.find(j.ImportSpecifier, { imported: { name: "Async" } })
38+
.forEach(node => renameJsxMembers(node.value.local.name))
39+
40+
// Rename instances created with `createInstance`
41+
root
42+
.find(j.ImportDeclaration, { source: { value: "react-async" } })
43+
.find(j.ImportSpecifier, { imported: { name: "createInstance" } })
44+
.forEach(node => {
45+
const createInstance = node.value.local.name
46+
root
47+
.find(j.VariableDeclarator)
48+
.filter(node => node.value.init.type === "CallExpression")
49+
.filter(node => node.value.init.callee.name === createInstance)
50+
.forEach(node => renameJsxMembers(node.value.id.name))
51+
})
52+
53+
return root.toSource()
54+
}

codemods/v8.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* This renames the standalone helper components:
3+
* - <Initial> to <IfInitial>
4+
* - <Pending> to <IfPending>
5+
* - <Fulfilled> to <IfFulfilled>
6+
* - <Rejected> to <IfRejected>
7+
* - <Settled> to <IfSettled>
8+
*/
9+
10+
const helperNames = ["Initial", "Pending", "Fulfilled", "Rejected", "Settled"]
11+
12+
export default function transform({ path, source }, api) {
13+
if (path.includes("/node_modules/")) return
14+
15+
const j = api.jscodeshift
16+
const root = j(source)
17+
18+
// Rename imports
19+
root
20+
.find(j.ImportDeclaration, { source: { value: "react-async" } })
21+
.find(j.ImportSpecifier)
22+
.filter(node => helperNames.includes(node.value.imported.name))
23+
.forEach(node => (node.value.imported.name = `If${node.value.imported.name}`))
24+
25+
// Rename JSX elements
26+
root
27+
.find(j.JSXIdentifier)
28+
.filter(node => helperNames.includes(node.value.name))
29+
.filter(node => node.parentPath.value.type !== "JSXMemberExpression")
30+
.forEach(node => (node.value.name = `If${node.value.name}`))
31+
32+
return root.toSource()
33+
}

0 commit comments

Comments
 (0)