@openrewrite/recipes-react 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +130 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/change-component-prop-value.d.ts +20 -0
- package/dist/migration/change-component-prop-value.d.ts.map +1 -0
- package/dist/migration/change-component-prop-value.js +217 -0
- package/dist/migration/change-component-prop-value.js.map +1 -0
- package/dist/react-native/view-prop-types.d.ts +8 -0
- package/dist/react-native/view-prop-types.d.ts.map +1 -0
- package/dist/react-native/view-prop-types.js +65 -0
- package/dist/react-native/view-prop-types.js.map +1 -0
- package/dist/react16/error-boundaries.d.ts +8 -0
- package/dist/react16/error-boundaries.d.ts.map +1 -0
- package/dist/react16/error-boundaries.js +42 -0
- package/dist/react16/error-boundaries.js.map +1 -0
- package/dist/react16/find-dom-node.d.ts +8 -0
- package/dist/react16/find-dom-node.d.ts.map +1 -0
- package/dist/react16/find-dom-node.js +48 -0
- package/dist/react16/find-dom-node.js.map +1 -0
- package/dist/react16/react-dom-factories.d.ts +8 -0
- package/dist/react16/react-dom-factories.d.ts.map +1 -0
- package/dist/react16/react-dom-factories.js +72 -0
- package/dist/react16/react-dom-factories.js.map +1 -0
- package/dist/react16/react-prop-types.d.ts +8 -0
- package/dist/react16/react-prop-types.d.ts.map +1 -0
- package/dist/react16/react-prop-types.js +69 -0
- package/dist/react16/react-prop-types.js.map +1 -0
- package/dist/react16/react-to-react-dom.d.ts +8 -0
- package/dist/react16/react-to-react-dom.d.ts.map +1 -0
- package/dist/react16/react-to-react-dom.js +97 -0
- package/dist/react16/react-to-react-dom.js.map +1 -0
- package/dist/react16/replace-create-factory.d.ts +8 -0
- package/dist/react16/replace-create-factory.d.ts.map +1 -0
- package/dist/react16/replace-create-factory.js +69 -0
- package/dist/react16/replace-create-factory.js.map +1 -0
- package/dist/react16/upgrade-to-react-16.d.ts +8 -0
- package/dist/react16/upgrade-to-react-16.d.ts.map +1 -0
- package/dist/react16/upgrade-to-react-16.js +41 -0
- package/dist/react16/upgrade-to-react-16.js.map +1 -0
- package/dist/react17/remove-event-persist.d.ts +8 -0
- package/dist/react17/remove-event-persist.d.ts.map +1 -0
- package/dist/react17/remove-event-persist.js +114 -0
- package/dist/react17/remove-event-persist.js.map +1 -0
- package/dist/react17/rename-unsafe-lifecycles.d.ts +8 -0
- package/dist/react17/rename-unsafe-lifecycles.d.ts.map +1 -0
- package/dist/react17/rename-unsafe-lifecycles.js +48 -0
- package/dist/react17/rename-unsafe-lifecycles.js.map +1 -0
- package/dist/react17/update-react-imports.d.ts +8 -0
- package/dist/react17/update-react-imports.d.ts.map +1 -0
- package/dist/react17/update-react-imports.js +40 -0
- package/dist/react17/update-react-imports.js.map +1 -0
- package/dist/react17/upgrade-to-react-17.d.ts +8 -0
- package/dist/react17/upgrade-to-react-17.d.ts.map +1 -0
- package/dist/react17/upgrade-to-react-17.js +37 -0
- package/dist/react17/upgrade-to-react-17.js.map +1 -0
- package/dist/react18/remove-unstable-batched-updates.d.ts +8 -0
- package/dist/react18/remove-unstable-batched-updates.d.ts.map +1 -0
- package/dist/react18/remove-unstable-batched-updates.js +170 -0
- package/dist/react18/remove-unstable-batched-updates.js.map +1 -0
- package/dist/react18/replace-reactdom-render.d.ts +8 -0
- package/dist/react18/replace-reactdom-render.d.ts.map +1 -0
- package/dist/react18/replace-reactdom-render.js +55 -0
- package/dist/react18/replace-reactdom-render.js.map +1 -0
- package/dist/react18/replace-render-callback.d.ts +8 -0
- package/dist/react18/replace-render-callback.d.ts.map +1 -0
- package/dist/react18/replace-render-callback.js +60 -0
- package/dist/react18/replace-render-callback.js.map +1 -0
- package/dist/react18/replace-unmount-component-at-node.d.ts +8 -0
- package/dist/react18/replace-unmount-component-at-node.d.ts.map +1 -0
- package/dist/react18/replace-unmount-component-at-node.js +54 -0
- package/dist/react18/replace-unmount-component-at-node.js.map +1 -0
- package/dist/react18/upgrade-to-react-18.d.ts +8 -0
- package/dist/react18/upgrade-to-react-18.d.ts.map +1 -0
- package/dist/react18/upgrade-to-react-18.js +39 -0
- package/dist/react18/upgrade-to-react-18.js.map +1 -0
- package/dist/react19/deprecated-react-types.d.ts +8 -0
- package/dist/react19/deprecated-react-types.d.ts.map +1 -0
- package/dist/react19/deprecated-react-types.js +135 -0
- package/dist/react19/deprecated-react-types.js.map +1 -0
- package/dist/react19/find-context-consumer.d.ts +9 -0
- package/dist/react19/find-context-consumer.d.ts.map +1 -0
- package/dist/react19/find-context-consumer.js +128 -0
- package/dist/react19/find-context-consumer.js.map +1 -0
- package/dist/react19/find-deprecated-reactdom-apis.d.ts +9 -0
- package/dist/react19/find-deprecated-reactdom-apis.d.ts.map +1 -0
- package/dist/react19/find-deprecated-reactdom-apis.js +132 -0
- package/dist/react19/find-deprecated-reactdom-apis.js.map +1 -0
- package/dist/react19/find-element-ref.d.ts +9 -0
- package/dist/react19/find-element-ref.d.ts.map +1 -0
- package/dist/react19/find-element-ref.js +88 -0
- package/dist/react19/find-element-ref.js.map +1 -0
- package/dist/react19/find-legacy-context-api.d.ts +9 -0
- package/dist/react19/find-legacy-context-api.d.ts.map +1 -0
- package/dist/react19/find-legacy-context-api.js +163 -0
- package/dist/react19/find-legacy-context-api.js.map +1 -0
- package/dist/react19/no-implicit-ref-callback-return.d.ts +8 -0
- package/dist/react19/no-implicit-ref-callback-return.d.ts.map +1 -0
- package/dist/react19/no-implicit-ref-callback-return.js +107 -0
- package/dist/react19/no-implicit-ref-callback-return.js.map +1 -0
- package/dist/react19/remove-context-provider.d.ts +8 -0
- package/dist/react19/remove-context-provider.d.ts.map +1 -0
- package/dist/react19/remove-context-provider.js +59 -0
- package/dist/react19/remove-context-provider.js.map +1 -0
- package/dist/react19/remove-forward-ref.d.ts +8 -0
- package/dist/react19/remove-forward-ref.d.ts.map +1 -0
- package/dist/react19/remove-forward-ref.js +73 -0
- package/dist/react19/remove-forward-ref.js.map +1 -0
- package/dist/react19/remove-prop-types.d.ts +8 -0
- package/dist/react19/remove-prop-types.d.ts.map +1 -0
- package/dist/react19/remove-prop-types.js +76 -0
- package/dist/react19/remove-prop-types.js.map +1 -0
- package/dist/react19/remove-react-fc.d.ts +8 -0
- package/dist/react19/remove-react-fc.d.ts.map +1 -0
- package/dist/react19/remove-react-fc.js +149 -0
- package/dist/react19/remove-react-fc.js.map +1 -0
- package/dist/react19/replace-act-import.d.ts +9 -0
- package/dist/react19/replace-act-import.d.ts.map +1 -0
- package/dist/react19/replace-act-import.js +34 -0
- package/dist/react19/replace-act-import.js.map +1 -0
- package/dist/react19/replace-default-props.d.ts +8 -0
- package/dist/react19/replace-default-props.d.ts.map +1 -0
- package/dist/react19/replace-default-props.js +195 -0
- package/dist/react19/replace-default-props.js.map +1 -0
- package/dist/react19/replace-react-shallow-renderer.d.ts +8 -0
- package/dist/react19/replace-react-shallow-renderer.d.ts.map +1 -0
- package/dist/react19/replace-react-shallow-renderer.js +69 -0
- package/dist/react19/replace-react-shallow-renderer.js.map +1 -0
- package/dist/react19/replace-reactdom-hydrate.d.ts +8 -0
- package/dist/react19/replace-reactdom-hydrate.d.ts.map +1 -0
- package/dist/react19/replace-reactdom-hydrate.js +55 -0
- package/dist/react19/replace-reactdom-hydrate.js.map +1 -0
- package/dist/react19/replace-string-ref.d.ts +8 -0
- package/dist/react19/replace-string-ref.d.ts.map +1 -0
- package/dist/react19/replace-string-ref.js +75 -0
- package/dist/react19/replace-string-ref.js.map +1 -0
- package/dist/react19/replace-use-form-state.d.ts +8 -0
- package/dist/react19/replace-use-form-state.d.ts.map +1 -0
- package/dist/react19/replace-use-form-state.js +54 -0
- package/dist/react19/replace-use-form-state.js.map +1 -0
- package/dist/react19/upgrade-to-react-19.d.ts +8 -0
- package/dist/react19/upgrade-to-react-19.d.ts.map +1 -0
- package/dist/react19/upgrade-to-react-19.js +59 -0
- package/dist/react19/upgrade-to-react-19.js.map +1 -0
- package/dist/react19/use-context-hook.d.ts +8 -0
- package/dist/react19/use-context-hook.d.ts.map +1 -0
- package/dist/react19/use-context-hook.js +54 -0
- package/dist/react19/use-context-hook.js.map +1 -0
- package/dist/react19/use-ref-required-initial.d.ts +8 -0
- package/dist/react19/use-ref-required-initial.d.ts.map +1 -0
- package/dist/react19/use-ref-required-initial.js +74 -0
- package/dist/react19/use-ref-required-initial.js.map +1 -0
- package/dist/refactoring/class-to-functional.d.ts +8 -0
- package/dist/refactoring/class-to-functional.d.ts.map +1 -0
- package/dist/refactoring/class-to-functional.js +205 -0
- package/dist/refactoring/class-to-functional.js.map +1 -0
- package/dist/refactoring/create-class-to-es6.d.ts +8 -0
- package/dist/refactoring/create-class-to-es6.d.ts.map +1 -0
- package/dist/refactoring/create-class-to-es6.js +289 -0
- package/dist/refactoring/create-class-to-es6.js.map +1 -0
- package/dist/refactoring/create-element-to-jsx.d.ts +8 -0
- package/dist/refactoring/create-element-to-jsx.d.ts.map +1 -0
- package/dist/refactoring/create-element-to-jsx.js +167 -0
- package/dist/refactoring/create-element-to-jsx.js.map +1 -0
- package/dist/refactoring/manual-bind-to-arrow.d.ts +8 -0
- package/dist/refactoring/manual-bind-to-arrow.d.ts.map +1 -0
- package/dist/refactoring/manual-bind-to-arrow.js +134 -0
- package/dist/refactoring/manual-bind-to-arrow.js.map +1 -0
- package/dist/refactoring/pure-render-mixin.d.ts +8 -0
- package/dist/refactoring/pure-render-mixin.d.ts.map +1 -0
- package/dist/refactoring/pure-render-mixin.js +253 -0
- package/dist/refactoring/pure-render-mixin.js.map +1 -0
- package/dist/refactoring/sort-comp.d.ts +8 -0
- package/dist/refactoring/sort-comp.d.ts.map +1 -0
- package/dist/refactoring/sort-comp.js +128 -0
- package/dist/refactoring/sort-comp.js.map +1 -0
- package/dist/search/find-hook-usage.d.ts +9 -0
- package/dist/search/find-hook-usage.d.ts.map +1 -0
- package/dist/search/find-hook-usage.js +262 -0
- package/dist/search/find-hook-usage.js.map +1 -0
- package/dist/search/find-prop-usage.d.ts +15 -0
- package/dist/search/find-prop-usage.d.ts.map +1 -0
- package/dist/search/find-prop-usage.js +177 -0
- package/dist/search/find-prop-usage.js.map +1 -0
- package/dist/search/find-react-component.d.ts +15 -0
- package/dist/search/find-react-component.d.ts.map +1 -0
- package/dist/search/find-react-component.js +260 -0
- package/dist/search/find-react-component.js.map +1 -0
- package/dist/search/find-server-rendering-usage.d.ts +9 -0
- package/dist/search/find-server-rendering-usage.d.ts.map +1 -0
- package/dist/search/find-server-rendering-usage.js +131 -0
- package/dist/search/find-server-rendering-usage.js.map +1 -0
- package/dist/simplify-object-pattern-property.d.ts +8 -0
- package/dist/simplify-object-pattern-property.d.ts.map +1 -0
- package/dist/simplify-object-pattern-property.js +59 -0
- package/dist/simplify-object-pattern-property.js.map +1 -0
- package/dist/simplify-react-imports.d.ts +8 -0
- package/dist/simplify-react-imports.d.ts.map +1 -0
- package/dist/simplify-react-imports.js +199 -0
- package/dist/simplify-react-imports.js.map +1 -0
- package/package.json +39 -0
- package/src/index.ts +149 -0
- package/src/migration/change-component-prop-value.ts +268 -0
- package/src/react-native/view-prop-types.ts +63 -0
- package/src/react16/error-boundaries.ts +46 -0
- package/src/react16/find-dom-node.ts +55 -0
- package/src/react16/react-dom-factories.ts +99 -0
- package/src/react16/react-prop-types.ts +71 -0
- package/src/react16/react-to-react-dom.ts +104 -0
- package/src/react16/replace-create-factory.ts +96 -0
- package/src/react16/upgrade-to-react-16.ts +37 -0
- package/src/react17/remove-event-persist.ts +121 -0
- package/src/react17/rename-unsafe-lifecycles.ts +57 -0
- package/src/react17/update-react-imports.ts +50 -0
- package/src/react17/upgrade-to-react-17.ts +30 -0
- package/src/react18/remove-unstable-batched-updates.ts +192 -0
- package/src/react18/replace-reactdom-render.ts +68 -0
- package/src/react18/replace-render-callback.ts +66 -0
- package/src/react18/replace-unmount-component-at-node.ts +66 -0
- package/src/react18/upgrade-to-react-18.ts +33 -0
- package/src/react19/deprecated-react-types.ts +120 -0
- package/src/react19/find-context-consumer.ts +127 -0
- package/src/react19/find-deprecated-reactdom-apis.ts +125 -0
- package/src/react19/find-element-ref.ts +86 -0
- package/src/react19/find-legacy-context-api.ts +157 -0
- package/src/react19/no-implicit-ref-callback-return.ts +123 -0
- package/src/react19/remove-context-provider.ts +87 -0
- package/src/react19/remove-forward-ref.ts +69 -0
- package/src/react19/remove-prop-types.ts +86 -0
- package/src/react19/remove-react-fc.ts +247 -0
- package/src/react19/replace-act-import.ts +36 -0
- package/src/react19/replace-default-props.ts +220 -0
- package/src/react19/replace-react-shallow-renderer.ts +75 -0
- package/src/react19/replace-reactdom-hydrate.ts +67 -0
- package/src/react19/replace-string-ref.ts +89 -0
- package/src/react19/replace-use-form-state.ts +66 -0
- package/src/react19/upgrade-to-react-19.ts +66 -0
- package/src/react19/use-context-hook.ts +67 -0
- package/src/react19/use-ref-required-initial.ts +75 -0
- package/src/refactoring/class-to-functional.ts +229 -0
- package/src/refactoring/create-class-to-es6.ts +309 -0
- package/src/refactoring/create-element-to-jsx.ts +200 -0
- package/src/refactoring/manual-bind-to-arrow.ts +139 -0
- package/src/refactoring/pure-render-mixin.ts +346 -0
- package/src/refactoring/sort-comp.ts +135 -0
- package/src/search/find-hook-usage.ts +226 -0
- package/src/search/find-prop-usage.ts +176 -0
- package/src/search/find-react-component.ts +254 -0
- package/src/search/find-server-rendering-usage.ts +120 -0
- package/src/simplify-object-pattern-property.ts +71 -0
- package/src/simplify-react-imports.ts +241 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes `event.persist()` calls.
|
|
8
|
+
*
|
|
9
|
+
* React 17 removed event pooling, so calling `persist()` on
|
|
10
|
+
* synthetic events is no longer necessary.
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function handleChange(e) {
|
|
15
|
+
* e.persist();
|
|
16
|
+
* setState(e.target.value);
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* After:
|
|
21
|
+
* ```tsx
|
|
22
|
+
* function handleChange(e) {
|
|
23
|
+
* setState(e.target.value);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @see https://reactjs.org/blog/2020/10/20/react-v17.html#no-event-pooling
|
|
28
|
+
*/
|
|
29
|
+
export class RemoveEventPersist extends Recipe {
|
|
30
|
+
readonly name = "org.openrewrite.react.17.remove-event-persist";
|
|
31
|
+
readonly displayName: string = "Remove `event.persist()` calls";
|
|
32
|
+
readonly description: string = "Removes `event.persist()` calls. React 17 removed event pooling, making persist() unnecessary.";
|
|
33
|
+
|
|
34
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
35
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
36
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
37
|
+
let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
|
|
38
|
+
|
|
39
|
+
const statementsToRemove = new Set<string>();
|
|
40
|
+
|
|
41
|
+
for (const stmt of result.statements) {
|
|
42
|
+
const s = stmt.element;
|
|
43
|
+
if (!s) continue;
|
|
44
|
+
if (this.isPersistCall(s)) {
|
|
45
|
+
statementsToRemove.add((s as any).id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (statementsToRemove.size === 0) return result;
|
|
50
|
+
|
|
51
|
+
const filteredStatements = result.statements.filter(stmt => {
|
|
52
|
+
const s = stmt.element;
|
|
53
|
+
return !s || !statementsToRemove.has((s as any).id);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return create(result, draft => {
|
|
57
|
+
draft.statements = filteredStatements as any;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
override async visitBlock(block: J.Block, ctx: ExecutionContext): Promise<J | undefined> {
|
|
62
|
+
let b = await super.visitBlock(block, ctx) as J.Block;
|
|
63
|
+
|
|
64
|
+
const statementsToRemove = new Set<string>();
|
|
65
|
+
|
|
66
|
+
for (const stmt of b.statements) {
|
|
67
|
+
const s = stmt.element;
|
|
68
|
+
if (!s) continue;
|
|
69
|
+
if (this.isPersistCall(s)) {
|
|
70
|
+
statementsToRemove.add((s as any).id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (statementsToRemove.size === 0) return b;
|
|
75
|
+
|
|
76
|
+
const filteredStatements = b.statements.filter(stmt => {
|
|
77
|
+
const s = stmt.element;
|
|
78
|
+
return !s || !statementsToRemove.has((s as any).id);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return create(b, draft => {
|
|
82
|
+
draft.statements = filteredStatements as any;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private isPersistCall(stmt: any): boolean {
|
|
87
|
+
let expr: any;
|
|
88
|
+
|
|
89
|
+
if (stmt.kind === JS.Kind.ExpressionStatement) {
|
|
90
|
+
expr = (stmt as JS.ExpressionStatement).expression;
|
|
91
|
+
} else if (stmt.kind === J.Kind.MethodInvocation) {
|
|
92
|
+
expr = stmt;
|
|
93
|
+
} else {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!expr || expr.kind !== J.Kind.MethodInvocation) return false;
|
|
98
|
+
|
|
99
|
+
const method = expr as J.MethodInvocation;
|
|
100
|
+
|
|
101
|
+
// Must be called as x.persist(), not just persist()
|
|
102
|
+
if (!method.select) return false;
|
|
103
|
+
|
|
104
|
+
// Method name must be "persist"
|
|
105
|
+
if (method.name.simpleName !== 'persist') return false;
|
|
106
|
+
|
|
107
|
+
// Must have no real arguments (may contain J.Empty placeholder)
|
|
108
|
+
if (method.arguments) {
|
|
109
|
+
const args = method.arguments.elements;
|
|
110
|
+
const hasRealArgs = args.some((el: any) => {
|
|
111
|
+
const element = el.element ?? el;
|
|
112
|
+
return element.kind !== J.Kind.Empty;
|
|
113
|
+
});
|
|
114
|
+
if (hasRealArgs) return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
|
|
5
|
+
const UNSAFE_LIFECYCLE_MAP: Record<string, string> = {
|
|
6
|
+
'componentWillMount': 'UNSAFE_componentWillMount',
|
|
7
|
+
'componentWillReceiveProps': 'UNSAFE_componentWillReceiveProps',
|
|
8
|
+
'componentWillUpdate': 'UNSAFE_componentWillUpdate',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Adds `UNSAFE_` prefix to deprecated lifecycle methods.
|
|
13
|
+
*
|
|
14
|
+
* React 16.3 deprecated `componentWillMount`, `componentWillReceiveProps`,
|
|
15
|
+
* and `componentWillUpdate`. React 17 requires the UNSAFE_ prefix.
|
|
16
|
+
*
|
|
17
|
+
* Before:
|
|
18
|
+
* ```tsx
|
|
19
|
+
* class MyComponent extends React.Component {
|
|
20
|
+
* componentWillMount() { ... }
|
|
21
|
+
* componentWillReceiveProps(nextProps) { ... }
|
|
22
|
+
* componentWillUpdate(nextProps, nextState) { ... }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* After:
|
|
27
|
+
* ```tsx
|
|
28
|
+
* class MyComponent extends React.Component {
|
|
29
|
+
* UNSAFE_componentWillMount() { ... }
|
|
30
|
+
* UNSAFE_componentWillReceiveProps(nextProps) { ... }
|
|
31
|
+
* UNSAFE_componentWillUpdate(nextProps, nextState) { ... }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class RenameUnsafeLifecycles extends Recipe {
|
|
36
|
+
readonly name = "org.openrewrite.react.17.rename-unsafe-lifecycles";
|
|
37
|
+
readonly displayName: string = "Add `UNSAFE_` prefix to deprecated lifecycle methods";
|
|
38
|
+
readonly description: string = "Renames `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate` to their UNSAFE_ prefixed versions.";
|
|
39
|
+
|
|
40
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
41
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
42
|
+
override async visitIdentifier(ident: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
|
|
43
|
+
let id = await super.visitIdentifier(ident, ctx) as J.Identifier;
|
|
44
|
+
|
|
45
|
+
const newName = UNSAFE_LIFECYCLE_MAP[id.simpleName];
|
|
46
|
+
if (newName) {
|
|
47
|
+
return {
|
|
48
|
+
...id,
|
|
49
|
+
simpleName: newName
|
|
50
|
+
} as J.Identifier;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
}();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Removes unnecessary `import React from 'react'` default imports.
|
|
7
|
+
*
|
|
8
|
+
* With React 17's new JSX transform, you no longer need to import React
|
|
9
|
+
* in every file that uses JSX. This recipe removes the default React
|
|
10
|
+
* import when it's only used for JSX (not for React.xxx API calls).
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import React from 'react';
|
|
15
|
+
*
|
|
16
|
+
* function App() {
|
|
17
|
+
* return <div>Hello</div>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* After:
|
|
22
|
+
* ```tsx
|
|
23
|
+
* function App() {
|
|
24
|
+
* return <div>Hello</div>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Note: If `React` is used for API calls (e.g., `React.createElement`,
|
|
29
|
+
* `React.useRef`), the import is preserved.
|
|
30
|
+
*
|
|
31
|
+
* @see https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
|
|
32
|
+
*/
|
|
33
|
+
export class UpdateReactImports extends Recipe {
|
|
34
|
+
readonly name = "org.openrewrite.react.17.update-react-imports";
|
|
35
|
+
readonly displayName: string = "Remove unnecessary React imports";
|
|
36
|
+
readonly description: string = "Removes the default `import React from 'react'` when React is only used for JSX, which is no longer necessary with the new JSX transform in React 17+.";
|
|
37
|
+
|
|
38
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
39
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
40
|
+
override async visitJsCompilationUnit(cu: any, ctx: ExecutionContext): Promise<J | undefined> {
|
|
41
|
+
let result = await super.visitJsCompilationUnit(cu, ctx);
|
|
42
|
+
// Schedule removal of the default React import.
|
|
43
|
+
// RemoveImport will only remove it if 'React' is not referenced
|
|
44
|
+
// anywhere outside of the import declaration itself.
|
|
45
|
+
maybeRemoveImport(this, "react", "default");
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
}();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {Recipe} from "@openrewrite/rewrite";
|
|
2
|
+
import {UpgradeToReact16} from "../react16/upgrade-to-react-16";
|
|
3
|
+
import {RenameUnsafeLifecycles} from "./rename-unsafe-lifecycles";
|
|
4
|
+
import {UpdateReactImports} from "./update-react-imports";
|
|
5
|
+
import {RemoveEventPersist} from "./remove-event-persist";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Composite recipe that applies all React 17 migration recipes.
|
|
9
|
+
*
|
|
10
|
+
* First applies all React 16 migrations, then:
|
|
11
|
+
* - Add `UNSAFE_` prefix to deprecated lifecycle methods
|
|
12
|
+
* - Remove unnecessary `import React from 'react'`
|
|
13
|
+
* - Remove `e.persist()` calls (event pooling was removed)
|
|
14
|
+
*
|
|
15
|
+
* @see https://reactjs.org/blog/2020/10/20/react-v17.html
|
|
16
|
+
*/
|
|
17
|
+
export class UpgradeToReact17 extends Recipe {
|
|
18
|
+
readonly name = "org.openrewrite.react.migrate.upgrade-to-react-17";
|
|
19
|
+
readonly displayName = "Upgrade to React 17";
|
|
20
|
+
readonly description = "Migrate deprecated APIs for React 17 compatibility. Includes all React 16 migrations plus lifecycle method prefixing, import cleanup, and event.persist() removal.";
|
|
21
|
+
|
|
22
|
+
async recipeList(): Promise<Recipe[]> {
|
|
23
|
+
return [
|
|
24
|
+
new UpgradeToReact16(),
|
|
25
|
+
new RenameUnsafeLifecycles(),
|
|
26
|
+
new UpdateReactImports(),
|
|
27
|
+
new RemoveEventPersist(),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes `unstable_batchedUpdates` wrappers from `react-dom`.
|
|
8
|
+
*
|
|
9
|
+
* In React 18, all state updates are automatically batched,
|
|
10
|
+
* making `unstable_batchedUpdates` unnecessary. This recipe
|
|
11
|
+
* unwraps the callback body and removes the wrapping call.
|
|
12
|
+
*
|
|
13
|
+
* Before:
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { unstable_batchedUpdates } from 'react-dom';
|
|
16
|
+
*
|
|
17
|
+
* unstable_batchedUpdates(() => {
|
|
18
|
+
* setCount(1);
|
|
19
|
+
* setFlag(true);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* After:
|
|
24
|
+
* ```tsx
|
|
25
|
+
* setCount(1);
|
|
26
|
+
* setFlag(true);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @see https://react.dev/blog/2022/03/29/react-v18#new-feature-automatic-batching
|
|
30
|
+
*/
|
|
31
|
+
export class RemoveUnstableBatchedUpdates extends Recipe {
|
|
32
|
+
readonly name = "org.openrewrite.react.18.remove-unstable-batched-updates";
|
|
33
|
+
readonly displayName = "Remove `unstable_batchedUpdates`";
|
|
34
|
+
readonly description = "Removes `unstable_batchedUpdates` wrappers from `react-dom`. React 18 automatically batches all state updates, making this function unnecessary.";
|
|
35
|
+
|
|
36
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
37
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
38
|
+
private changed = false;
|
|
39
|
+
|
|
40
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
41
|
+
this.changed = false;
|
|
42
|
+
let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
|
|
43
|
+
|
|
44
|
+
result = this.inlineTopLevelStatements(result);
|
|
45
|
+
|
|
46
|
+
if (this.changed) {
|
|
47
|
+
maybeRemoveImport(this, "react-dom", "unstable_batchedUpdates");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override async visitBlock(block: J.Block, ctx: ExecutionContext): Promise<J | undefined> {
|
|
54
|
+
let b = await super.visitBlock(block, ctx) as J.Block;
|
|
55
|
+
return this.inlineBlockStatements(b);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private inlineTopLevelStatements(cu: JS.CompilationUnit): JS.CompilationUnit {
|
|
59
|
+
const result = this.spliceStatements(cu.statements);
|
|
60
|
+
if (!result) return cu;
|
|
61
|
+
return create(cu, draft => {
|
|
62
|
+
draft.statements = result as any;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private inlineBlockStatements(block: J.Block): J.Block {
|
|
67
|
+
const result = this.spliceStatements(block.statements);
|
|
68
|
+
if (!result) return block;
|
|
69
|
+
return create(block, draft => {
|
|
70
|
+
draft.statements = result as any;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private spliceStatements(statements: any[]): any[] | null {
|
|
75
|
+
const newStatements: any[] = [];
|
|
76
|
+
let changed = false;
|
|
77
|
+
|
|
78
|
+
for (const paddedStmt of statements) {
|
|
79
|
+
const stmt = paddedStmt.element;
|
|
80
|
+
if (!stmt) {
|
|
81
|
+
newStatements.push(paddedStmt);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bodyStatements = this.extractBatchedUpdatesBody(stmt);
|
|
86
|
+
if (bodyStatements !== null && bodyStatements.length > 0) {
|
|
87
|
+
// Compute indentation adjustment: replace the inner callback
|
|
88
|
+
// indentation with the outer statement's indentation
|
|
89
|
+
const outerPrefix = paddedStmt.element.prefix;
|
|
90
|
+
const outerIndent = this.getIndentation(outerPrefix);
|
|
91
|
+
const innerIndent = bodyStatements.length > 0
|
|
92
|
+
? this.getIndentation(bodyStatements[0].element.prefix)
|
|
93
|
+
: outerIndent;
|
|
94
|
+
|
|
95
|
+
for (const bodyStmt of bodyStatements) {
|
|
96
|
+
const adjustedPrefix = this.adjustIndentation(
|
|
97
|
+
bodyStmt.element.prefix, innerIndent, outerIndent
|
|
98
|
+
);
|
|
99
|
+
newStatements.push({
|
|
100
|
+
...bodyStmt,
|
|
101
|
+
element: {
|
|
102
|
+
...bodyStmt.element,
|
|
103
|
+
prefix: adjustedPrefix
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
changed = true;
|
|
108
|
+
this.changed = true;
|
|
109
|
+
} else {
|
|
110
|
+
newStatements.push(paddedStmt);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return changed ? newStatements : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private getIndentation(prefix: any): string {
|
|
118
|
+
const ws: string = prefix?.whitespace ?? '';
|
|
119
|
+
// Get the indentation from the last line of the whitespace
|
|
120
|
+
const lastNewline = ws.lastIndexOf('\n');
|
|
121
|
+
return lastNewline >= 0 ? ws.substring(lastNewline + 1) : ws;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private adjustIndentation(prefix: any, fromIndent: string, toIndent: string): any {
|
|
125
|
+
if (fromIndent === toIndent) return prefix;
|
|
126
|
+
const ws: string = prefix?.whitespace ?? '';
|
|
127
|
+
const lastNewline = ws.lastIndexOf('\n');
|
|
128
|
+
if (lastNewline < 0) return {...prefix, whitespace: toIndent};
|
|
129
|
+
const currentIndent = ws.substring(lastNewline + 1);
|
|
130
|
+
// Replace the inner indentation with the outer one
|
|
131
|
+
const adjusted = currentIndent.startsWith(fromIndent)
|
|
132
|
+
? toIndent + currentIndent.substring(fromIndent.length)
|
|
133
|
+
: toIndent;
|
|
134
|
+
return {...prefix, whitespace: ws.substring(0, lastNewline + 1) + adjusted};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private extractBatchedUpdatesBody(stmt: any): any[] | null {
|
|
138
|
+
let expr: any;
|
|
139
|
+
|
|
140
|
+
if (stmt.kind === JS.Kind.ExpressionStatement) {
|
|
141
|
+
expr = (stmt as JS.ExpressionStatement).expression;
|
|
142
|
+
} else if (stmt.kind === J.Kind.MethodInvocation) {
|
|
143
|
+
expr = stmt;
|
|
144
|
+
} else {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!expr || expr.kind !== J.Kind.MethodInvocation) return null;
|
|
149
|
+
|
|
150
|
+
const method = expr as J.MethodInvocation;
|
|
151
|
+
|
|
152
|
+
if (!this.isBatchedUpdatesCall(method)) return null;
|
|
153
|
+
|
|
154
|
+
// Get the callback argument
|
|
155
|
+
const args = method.arguments;
|
|
156
|
+
if (!args) return null;
|
|
157
|
+
|
|
158
|
+
const elements = args.elements;
|
|
159
|
+
if (!elements || elements.length === 0) return null;
|
|
160
|
+
|
|
161
|
+
const callback = elements[0].element;
|
|
162
|
+
if (!callback) return null;
|
|
163
|
+
|
|
164
|
+
// Arrow function with block body
|
|
165
|
+
if (callback.kind === JS.Kind.ArrowFunction) {
|
|
166
|
+
const arrow = callback as JS.ArrowFunction;
|
|
167
|
+
const body = arrow.lambda.body;
|
|
168
|
+
if (body && body.kind === J.Kind.Block) {
|
|
169
|
+
return (body as J.Block).statements;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private isBatchedUpdatesCall(method: J.MethodInvocation): boolean {
|
|
177
|
+
if (method.name.simpleName === 'unstable_batchedUpdates') {
|
|
178
|
+
// Direct call: unstable_batchedUpdates(...)
|
|
179
|
+
if (!method.select) return true;
|
|
180
|
+
|
|
181
|
+
// Namespace call: ReactDOM.unstable_batchedUpdates(...)
|
|
182
|
+
const select = (method.select as any).element ?? method.select;
|
|
183
|
+
if (isIdentifier(select) && select.simpleName === 'ReactDOM') {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
capture,
|
|
4
|
+
JavaScriptVisitor,
|
|
5
|
+
maybeAddImport,
|
|
6
|
+
maybeRemoveImport,
|
|
7
|
+
pattern,
|
|
8
|
+
rewrite,
|
|
9
|
+
template
|
|
10
|
+
} from "@openrewrite/rewrite/javascript";
|
|
11
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces `ReactDOM.render()` with `createRoot().render()`.
|
|
15
|
+
*
|
|
16
|
+
* In React 18, `ReactDOM.render()` is deprecated in favor of
|
|
17
|
+
* `createRoot()` from `react-dom/client`.
|
|
18
|
+
*
|
|
19
|
+
* Before:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import ReactDOM from 'react-dom';
|
|
22
|
+
* ReactDOM.render(<App />, document.getElementById('root'));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* After:
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { createRoot } from 'react-dom/client';
|
|
28
|
+
* const root = createRoot(document.getElementById('root'));
|
|
29
|
+
* root.render(<App />);
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
|
|
33
|
+
*/
|
|
34
|
+
export class ReplaceReactDomRender extends Recipe {
|
|
35
|
+
readonly name = "org.openrewrite.react.18.replace-reactdom-render";
|
|
36
|
+
readonly displayName: string = "Replace `ReactDOM.render` with `createRoot`";
|
|
37
|
+
readonly description: string = "Migrates from the legacy `ReactDOM.render()` API to the `createRoot()` API from `react-dom/client` introduced in React 18.";
|
|
38
|
+
|
|
39
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
40
|
+
const reactDomOptions = {
|
|
41
|
+
context: ['import { render } from "react-dom";'],
|
|
42
|
+
dependencies: {'@types/react-dom': '^17.0.0'}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const element = capture<Expression>();
|
|
46
|
+
const container = capture<Expression>();
|
|
47
|
+
|
|
48
|
+
const rule = rewrite(() => ({
|
|
49
|
+
before: pattern`render(${element}, ${container})`.configure(reactDomOptions),
|
|
50
|
+
after: template`createRoot(${container}).render(${element})`
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
54
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
55
|
+
let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
56
|
+
|
|
57
|
+
const result = await rule.tryOn(this.cursor, m!);
|
|
58
|
+
if (result) {
|
|
59
|
+
maybeRemoveImport(this, "react-dom", "render");
|
|
60
|
+
maybeAddImport(this, {module: "react-dom/client", member: "createRoot", onlyIfReferenced: false});
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return m;
|
|
65
|
+
}
|
|
66
|
+
}();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes the third callback argument from `ReactDOM.render()` calls.
|
|
8
|
+
*
|
|
9
|
+
* In React 18, `ReactDOM.render()` is deprecated in favor of `createRoot()`,
|
|
10
|
+
* which does not support a callback argument. This recipe strips the callback
|
|
11
|
+
* to prepare code for the migration.
|
|
12
|
+
*
|
|
13
|
+
* Before:
|
|
14
|
+
* ```tsx
|
|
15
|
+
* ReactDOM.render(<App />, document.getElementById('root'), () => {
|
|
16
|
+
* console.log('rendered');
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* After:
|
|
21
|
+
* ```tsx
|
|
22
|
+
* ReactDOM.render(<App />, document.getElementById('root'));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
|
|
26
|
+
*/
|
|
27
|
+
export class ReplaceRenderCallback extends Recipe {
|
|
28
|
+
readonly name = "org.openrewrite.react.18.replace-render-callback";
|
|
29
|
+
readonly displayName: string = "Remove `ReactDOM.render` callback argument";
|
|
30
|
+
readonly description: string = "Removes the third callback argument from `ReactDOM.render(element, container, callback)` calls. Callbacks are not supported in React 18's `createRoot` API.";
|
|
31
|
+
|
|
32
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
33
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
34
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
35
|
+
let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
36
|
+
|
|
37
|
+
if (m.name.simpleName !== 'render') return m;
|
|
38
|
+
|
|
39
|
+
// Check for ReactDOM.render (namespace call)
|
|
40
|
+
const isReactDomRender = (() => {
|
|
41
|
+
if (m.select) {
|
|
42
|
+
const select = (m.select as any).element ?? m.select;
|
|
43
|
+
return isIdentifier(select) && select.simpleName === 'ReactDOM';
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
})();
|
|
47
|
+
|
|
48
|
+
if (!isReactDomRender) return m;
|
|
49
|
+
|
|
50
|
+
// Only transform when there are exactly 3 arguments (element, container, callback)
|
|
51
|
+
const args = m.arguments?.elements;
|
|
52
|
+
if (!args || args.length !== 3) return m;
|
|
53
|
+
|
|
54
|
+
// Remove the 3rd argument (callback)
|
|
55
|
+
const newArgs = args.slice(0, 2);
|
|
56
|
+
|
|
57
|
+
return create(m, draft => {
|
|
58
|
+
(draft as any).arguments = {
|
|
59
|
+
...m.arguments,
|
|
60
|
+
elements: newArgs
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
capture,
|
|
4
|
+
JavaScriptVisitor,
|
|
5
|
+
maybeAddImport,
|
|
6
|
+
maybeRemoveImport,
|
|
7
|
+
pattern,
|
|
8
|
+
rewrite,
|
|
9
|
+
template
|
|
10
|
+
} from "@openrewrite/rewrite/javascript";
|
|
11
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces `ReactDOM.unmountComponentAtNode()` with `createRoot().unmount()`.
|
|
15
|
+
*
|
|
16
|
+
* In React 18, `ReactDOM.unmountComponentAtNode()` is deprecated in favor of
|
|
17
|
+
* `createRoot().unmount()` from `react-dom/client`.
|
|
18
|
+
*
|
|
19
|
+
* Before:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { unmountComponentAtNode } from 'react-dom';
|
|
22
|
+
* unmountComponentAtNode(document.getElementById('root'));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* After:
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { createRoot } from 'react-dom/client';
|
|
28
|
+
* createRoot(document.getElementById('root')).unmount();
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
|
|
32
|
+
*/
|
|
33
|
+
export class ReplaceUnmountComponentAtNode extends Recipe {
|
|
34
|
+
readonly name = "org.openrewrite.react.18.replace-unmount-component-at-node";
|
|
35
|
+
readonly displayName: string = "Replace `unmountComponentAtNode` with `createRoot().unmount()`";
|
|
36
|
+
readonly description: string = "Replaces `ReactDOM.unmountComponentAtNode(container)` with `createRoot(container).unmount()` from `react-dom/client`.";
|
|
37
|
+
|
|
38
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
39
|
+
const reactDomOptions = {
|
|
40
|
+
context: ['import { unmountComponentAtNode } from "react-dom";'],
|
|
41
|
+
dependencies: {'@types/react-dom': '^17.0.0'}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const container = capture<Expression>();
|
|
45
|
+
|
|
46
|
+
const rule = rewrite(() => ({
|
|
47
|
+
before: pattern`unmountComponentAtNode(${container})`.configure(reactDomOptions),
|
|
48
|
+
after: template`createRoot(${container}).unmount()`
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
52
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
53
|
+
let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
54
|
+
|
|
55
|
+
const result = await rule.tryOn(this.cursor, m!);
|
|
56
|
+
if (result) {
|
|
57
|
+
maybeRemoveImport(this, "react-dom", "unmountComponentAtNode");
|
|
58
|
+
maybeAddImport(this, {module: "react-dom/client", member: "createRoot", onlyIfReferenced: false});
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return m;
|
|
63
|
+
}
|
|
64
|
+
}();
|
|
65
|
+
}
|
|
66
|
+
}
|