@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,220 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {Expression, isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts `Component.defaultProps` to ES6 default parameter values.
|
|
8
|
+
*
|
|
9
|
+
* In React 19, `defaultProps` for function components is deprecated.
|
|
10
|
+
* This recipe moves default values into the function's parameter destructuring.
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function Greeting({name}) {
|
|
15
|
+
* return <div>Hello {name}</div>;
|
|
16
|
+
* }
|
|
17
|
+
* Greeting.defaultProps = { name: 'World' };
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* After:
|
|
21
|
+
* ```tsx
|
|
22
|
+
* function Greeting({name = 'World'}) {
|
|
23
|
+
* return <div>Hello {name}</div>;
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-defaultprops
|
|
28
|
+
*/
|
|
29
|
+
export class ReplaceDefaultProps extends Recipe {
|
|
30
|
+
readonly name = "org.openrewrite.react.19.replace-default-props";
|
|
31
|
+
readonly displayName: string = "Replace `defaultProps` with default parameter values";
|
|
32
|
+
readonly description: string = "Converts `Component.defaultProps = {...}` to ES6 default parameter values in function components. `defaultProps` for function components is deprecated in React 19.";
|
|
33
|
+
|
|
34
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
35
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
36
|
+
// Map: component name → { propName → default value expression }
|
|
37
|
+
private defaultPropsMap = new Map<string, Map<string, Expression>>();
|
|
38
|
+
// Track which statement IDs to remove
|
|
39
|
+
private statementsToRemove = new Set<string>();
|
|
40
|
+
|
|
41
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
42
|
+
this.defaultPropsMap.clear();
|
|
43
|
+
this.statementsToRemove.clear();
|
|
44
|
+
|
|
45
|
+
// First pass: collect all defaultProps assignments
|
|
46
|
+
this.collectDefaultProps(cu);
|
|
47
|
+
|
|
48
|
+
if (this.defaultPropsMap.size === 0) {
|
|
49
|
+
return cu;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Second pass: apply defaults to function declarations
|
|
53
|
+
let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
|
|
54
|
+
|
|
55
|
+
// Remove defaultProps assignment statements
|
|
56
|
+
if (this.statementsToRemove.size > 0) {
|
|
57
|
+
const filteredStatements = result.statements.filter(stmt => {
|
|
58
|
+
const s = stmt.element;
|
|
59
|
+
return !s || !this.statementsToRemove.has((s as any).id);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (filteredStatements.length !== result.statements.length) {
|
|
63
|
+
result = create(result, draft => {
|
|
64
|
+
draft.statements = filteredStatements as any;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private collectDefaultProps(cu: JS.CompilationUnit): void {
|
|
73
|
+
for (const stmt of cu.statements) {
|
|
74
|
+
const s = stmt.element;
|
|
75
|
+
if (!s) continue;
|
|
76
|
+
|
|
77
|
+
// Look for ExpressionStatement wrapping an Assignment
|
|
78
|
+
let assignment: J.Assignment | undefined;
|
|
79
|
+
|
|
80
|
+
if (s.kind === JS.Kind.ExpressionStatement) {
|
|
81
|
+
const exprStmt = s as JS.ExpressionStatement;
|
|
82
|
+
const expr = exprStmt.expression;
|
|
83
|
+
if (expr.kind === J.Kind.Assignment) {
|
|
84
|
+
assignment = expr as J.Assignment;
|
|
85
|
+
}
|
|
86
|
+
} else if (s.kind === J.Kind.Assignment) {
|
|
87
|
+
assignment = s as J.Assignment;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!assignment) continue;
|
|
91
|
+
|
|
92
|
+
// Check for Component.defaultProps = { ... }
|
|
93
|
+
if (assignment.variable.kind !== J.Kind.FieldAccess) continue;
|
|
94
|
+
const fa = assignment.variable as J.FieldAccess;
|
|
95
|
+
if (fa.name.element.simpleName !== 'defaultProps') continue;
|
|
96
|
+
if (!isIdentifier(fa.target)) continue;
|
|
97
|
+
const componentName = (fa.target as J.Identifier).simpleName;
|
|
98
|
+
|
|
99
|
+
// Extract the object literal values
|
|
100
|
+
const rhs = assignment.assignment.element;
|
|
101
|
+
if (!rhs) continue;
|
|
102
|
+
|
|
103
|
+
const defaults = this.extractObjectLiteralDefaults(rhs);
|
|
104
|
+
if (defaults.size > 0) {
|
|
105
|
+
this.defaultPropsMap.set(componentName, defaults);
|
|
106
|
+
this.statementsToRemove.add((s as any).id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private extractObjectLiteralDefaults(expr: Expression): Map<string, Expression> {
|
|
112
|
+
const defaults = new Map<string, Expression>();
|
|
113
|
+
|
|
114
|
+
// Object literals are represented as J.NewClass in the AST
|
|
115
|
+
if (expr.kind === J.Kind.NewClass) {
|
|
116
|
+
const newClass = expr as J.NewClass;
|
|
117
|
+
if (newClass.body) {
|
|
118
|
+
for (const stmt of newClass.body.statements) {
|
|
119
|
+
const s = stmt.element;
|
|
120
|
+
if (!s) continue;
|
|
121
|
+
|
|
122
|
+
if (s.kind === JS.Kind.PropertyAssignment) {
|
|
123
|
+
const prop = s as JS.PropertyAssignment;
|
|
124
|
+
const nameExpr = prop.name.element;
|
|
125
|
+
if (isIdentifier(nameExpr) && prop.initializer) {
|
|
126
|
+
defaults.set(nameExpr.simpleName, prop.initializer);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return defaults;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Apply defaults to function parameter destructuring
|
|
137
|
+
override async visitMethodDeclaration(method: J.MethodDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
|
|
138
|
+
const m = await super.visitMethodDeclaration(method, ctx) as J.MethodDeclaration;
|
|
139
|
+
return this.applyDefaults(m.name.simpleName, m);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private applyDefaults(componentName: string, method: J.MethodDeclaration): J.MethodDeclaration {
|
|
143
|
+
const defaults = this.defaultPropsMap.get(componentName);
|
|
144
|
+
if (!defaults || defaults.size === 0) return method;
|
|
145
|
+
|
|
146
|
+
// Parameters structure: method.parameters.elements → [J.VariableDeclarations]
|
|
147
|
+
// Each VariableDeclarations has .variables → [J.RightPadded<NamedVariable>]
|
|
148
|
+
// NamedVariable.name → ObjectBindingPattern
|
|
149
|
+
// ObjectBindingPattern.bindings.elements → [J.RightPadded<JS.BindingElement>]
|
|
150
|
+
const params = method.parameters;
|
|
151
|
+
if (!params) return method;
|
|
152
|
+
|
|
153
|
+
const paramElements = params.elements;
|
|
154
|
+
if (!paramElements || paramElements.length === 0) return method;
|
|
155
|
+
|
|
156
|
+
// The first param element is J.VariableDeclarations (the destructured props)
|
|
157
|
+
const firstParamPadded = paramElements[0];
|
|
158
|
+
const firstParam = (firstParamPadded as any).element ?? firstParamPadded;
|
|
159
|
+
|
|
160
|
+
if (firstParam.kind !== J.Kind.VariableDeclarations) return method;
|
|
161
|
+
|
|
162
|
+
const varDecls = firstParam as J.VariableDeclarations;
|
|
163
|
+
if (!varDecls.variables || varDecls.variables.length === 0) return method;
|
|
164
|
+
|
|
165
|
+
const firstVarPadded = varDecls.variables[0];
|
|
166
|
+
const namedVar = firstVarPadded.element as J.VariableDeclarations.NamedVariable;
|
|
167
|
+
const varName = namedVar.name;
|
|
168
|
+
|
|
169
|
+
if ((varName as any).kind !== JS.Kind.ObjectBindingPattern) return method;
|
|
170
|
+
|
|
171
|
+
const obp = varName as unknown as JS.ObjectBindingPattern;
|
|
172
|
+
const updatedBindings = this.addDefaultsToBindings(obp, defaults);
|
|
173
|
+
if (updatedBindings === obp) return method;
|
|
174
|
+
|
|
175
|
+
// Rebuild the tree with updated bindings
|
|
176
|
+
return create(method, draft => {
|
|
177
|
+
const dp = (draft.parameters!.elements[0] as any).element ?? draft.parameters!.elements[0];
|
|
178
|
+
dp.variables[0].element.name = updatedBindings;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private addDefaultsToBindings(obp: JS.ObjectBindingPattern, defaults: Map<string, Expression>): JS.ObjectBindingPattern {
|
|
183
|
+
let changed = false;
|
|
184
|
+
const updatedElements = obp.bindings.elements.map((elem: any) => {
|
|
185
|
+
const el = elem.element ?? elem;
|
|
186
|
+
|
|
187
|
+
// BindingElement: { name: Identifier, initializer?: ... }
|
|
188
|
+
if (el.kind === JS.Kind.BindingElement) {
|
|
189
|
+
const binding = el as JS.BindingElement;
|
|
190
|
+
const name = binding.name;
|
|
191
|
+
if (isIdentifier(name) && defaults.has(name.simpleName) && !binding.initializer) {
|
|
192
|
+
const defaultVal = defaults.get(name.simpleName)!;
|
|
193
|
+
changed = true;
|
|
194
|
+
const updatedBinding = create(binding, draft => {
|
|
195
|
+
draft.initializer = {
|
|
196
|
+
kind: J.Kind.LeftPadded,
|
|
197
|
+
before: {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: ' ', comments: []},
|
|
198
|
+
element: defaultVal,
|
|
199
|
+
markers: binding.markers
|
|
200
|
+
} as any;
|
|
201
|
+
});
|
|
202
|
+
if (elem.element) {
|
|
203
|
+
return {...elem, element: updatedBinding};
|
|
204
|
+
}
|
|
205
|
+
return updatedBinding;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return elem;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (!changed) return obp;
|
|
213
|
+
|
|
214
|
+
return create(obp, draft => {
|
|
215
|
+
(draft as any).bindings.elements = updatedElements;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* Replaces `react-test-renderer/shallow` imports with `react-shallow-renderer`.
|
|
8
|
+
*
|
|
9
|
+
* In React 19, the shallow renderer has been removed from `react-test-renderer`.
|
|
10
|
+
* This recipe updates imports to use the standalone `react-shallow-renderer` package.
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import ShallowRenderer from 'react-test-renderer/shallow';
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* After:
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import ShallowRenderer from 'react-shallow-renderer';
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide
|
|
23
|
+
*/
|
|
24
|
+
export class ReplaceReactShallowRenderer extends Recipe {
|
|
25
|
+
readonly name = "org.openrewrite.react.19.replace-react-shallow-renderer";
|
|
26
|
+
readonly displayName: string = "Replace `react-test-renderer/shallow` import";
|
|
27
|
+
readonly description: string = "Changes import of shallow renderer from `react-test-renderer/shallow` to the standalone `react-shallow-renderer` package, as it was removed from React 19.";
|
|
28
|
+
|
|
29
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
30
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
31
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
32
|
+
let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
|
|
33
|
+
|
|
34
|
+
let changed = false;
|
|
35
|
+
const newStatements = result.statements.map((paddedStmt: any) => {
|
|
36
|
+
const stmt = paddedStmt.element;
|
|
37
|
+
if (!stmt || stmt.kind !== JS.Kind.Import) return paddedStmt;
|
|
38
|
+
|
|
39
|
+
const imp = stmt as JS.Import;
|
|
40
|
+
const modSpec = (imp.moduleSpecifier as any).element ?? imp.moduleSpecifier;
|
|
41
|
+
if (modSpec.kind !== J.Kind.Literal) return paddedStmt;
|
|
42
|
+
|
|
43
|
+
const literal = modSpec as J.Literal;
|
|
44
|
+
if (literal.value !== 'react-test-renderer/shallow') return paddedStmt;
|
|
45
|
+
|
|
46
|
+
const newLiteral = create(literal, draft => {
|
|
47
|
+
draft.value = 'react-shallow-renderer';
|
|
48
|
+
if (draft.valueSource) {
|
|
49
|
+
draft.valueSource = draft.valueSource.replace('react-test-renderer/shallow', 'react-shallow-renderer');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const newModSpec = (imp.moduleSpecifier as any).element
|
|
54
|
+
? {...(imp.moduleSpecifier as any), element: newLiteral}
|
|
55
|
+
: newLiteral;
|
|
56
|
+
|
|
57
|
+
changed = true;
|
|
58
|
+
return {
|
|
59
|
+
...paddedStmt,
|
|
60
|
+
element: {
|
|
61
|
+
...imp,
|
|
62
|
+
moduleSpecifier: newModSpec
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!changed) return result;
|
|
68
|
+
|
|
69
|
+
return create(result, draft => {
|
|
70
|
+
draft.statements = newStatements as any;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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.hydrate()` with `hydrateRoot()`.
|
|
15
|
+
*
|
|
16
|
+
* In React 18+, `ReactDOM.hydrate()` is deprecated in favor of
|
|
17
|
+
* `hydrateRoot()` from `react-dom/client`.
|
|
18
|
+
*
|
|
19
|
+
* Before:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import ReactDOM from 'react-dom';
|
|
22
|
+
* ReactDOM.hydrate(<App />, document.getElementById('root'));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* After:
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { hydrateRoot } from 'react-dom/client';
|
|
28
|
+
* hydrateRoot(document.getElementById('root'), <App />);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-reactdom-render
|
|
32
|
+
*/
|
|
33
|
+
export class ReplaceReactDomHydrate extends Recipe {
|
|
34
|
+
readonly name = "org.openrewrite.react.19.replace-reactdom-hydrate";
|
|
35
|
+
readonly displayName: string = "Replace `ReactDOM.hydrate` with `hydrateRoot`";
|
|
36
|
+
readonly description: string = "Migrates from the legacy `ReactDOM.hydrate()` API to the `hydrateRoot()` API from `react-dom/client`.";
|
|
37
|
+
|
|
38
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
39
|
+
const reactDomOptions = {
|
|
40
|
+
context: ['import { hydrate } from "react-dom";'],
|
|
41
|
+
dependencies: {'@types/react-dom': '^17.0.0'}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const element = capture<Expression>();
|
|
45
|
+
const container = capture<Expression>();
|
|
46
|
+
|
|
47
|
+
const rule = rewrite(() => ({
|
|
48
|
+
before: pattern`hydrate(${element}, ${container})`.configure(reactDomOptions),
|
|
49
|
+
after: template`hydrateRoot(${container}, ${element})`
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
53
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
54
|
+
let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
55
|
+
|
|
56
|
+
const result = await rule.tryOn(this.cursor, m!);
|
|
57
|
+
if (result) {
|
|
58
|
+
maybeRemoveImport(this, "react-dom", "hydrate");
|
|
59
|
+
maybeAddImport(this, {module: "react-dom/client", member: "hydrateRoot", onlyIfReferenced: false});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return m;
|
|
64
|
+
}
|
|
65
|
+
}();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, raw, template} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {JS, JSX} from "@openrewrite/rewrite/javascript";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Replaces deprecated string refs with callback refs.
|
|
8
|
+
*
|
|
9
|
+
* String refs have been deprecated in React and are removed in React 19.
|
|
10
|
+
*
|
|
11
|
+
* Before:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <input ref="myInput" />
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* After:
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <input ref={(ref) => { this.refs.myInput = ref; }} />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-string-refs
|
|
22
|
+
*/
|
|
23
|
+
export class ReplaceStringRef extends Recipe {
|
|
24
|
+
readonly name = "org.openrewrite.react.19.replace-string-ref";
|
|
25
|
+
readonly displayName: string = "Replace string refs with callback refs";
|
|
26
|
+
readonly description: string = "String refs are removed in React 19. This recipe converts them to callback refs.";
|
|
27
|
+
|
|
28
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
29
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
30
|
+
override async visitJsxAttribute(attribute: JSX.Attribute, ctx: ExecutionContext): Promise<J | undefined> {
|
|
31
|
+
let attr = await super.visitJsxAttribute(attribute, ctx) as JSX.Attribute;
|
|
32
|
+
|
|
33
|
+
// Check if the attribute key is "ref"
|
|
34
|
+
if (attr.key.kind !== J.Kind.Identifier) {
|
|
35
|
+
return attr;
|
|
36
|
+
}
|
|
37
|
+
if ((attr.key as J.Identifier).simpleName !== 'ref') {
|
|
38
|
+
return attr;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if the value is a string literal
|
|
42
|
+
if (!attr.value) {
|
|
43
|
+
return attr;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const valueExpr = attr.value.element;
|
|
47
|
+
if (valueExpr.kind !== J.Kind.Literal) {
|
|
48
|
+
return attr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const literal = valueExpr as J.Literal;
|
|
52
|
+
if (typeof literal.value !== 'string') {
|
|
53
|
+
return attr;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const refName = literal.value;
|
|
57
|
+
|
|
58
|
+
// Use template with raw() to create the callback ref expression
|
|
59
|
+
const callbackExpr = await template`(ref) => { this.refs.${raw(refName)} = ref; }`.apply(literal, this.cursor);
|
|
60
|
+
|
|
61
|
+
if (!callbackExpr) {
|
|
62
|
+
return attr;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Wrap in a JSX embedded expression
|
|
66
|
+
const embeddedExpr = {
|
|
67
|
+
kind: JS.Kind.JsxEmbeddedExpression,
|
|
68
|
+
id: literal.id,
|
|
69
|
+
prefix: literal.prefix,
|
|
70
|
+
markers: literal.markers,
|
|
71
|
+
expression: {
|
|
72
|
+
kind: J.Kind.RightPadded,
|
|
73
|
+
element: callbackExpr,
|
|
74
|
+
after: literal.prefix,
|
|
75
|
+
markers: literal.markers
|
|
76
|
+
}
|
|
77
|
+
} as JSX.EmbeddedExpression;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
...attr,
|
|
81
|
+
value: {
|
|
82
|
+
...attr.value,
|
|
83
|
+
element: embeddedExpr
|
|
84
|
+
}
|
|
85
|
+
} as any as JSX.Attribute;
|
|
86
|
+
}
|
|
87
|
+
}();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
JavaScriptVisitor,
|
|
4
|
+
maybeAddImport,
|
|
5
|
+
maybeRemoveImport,
|
|
6
|
+
capture,
|
|
7
|
+
pattern,
|
|
8
|
+
rewrite,
|
|
9
|
+
template
|
|
10
|
+
} from "@openrewrite/rewrite/javascript";
|
|
11
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces `useFormState()` with `useActionState()` for React 19.
|
|
15
|
+
*
|
|
16
|
+
* In React 19, `useFormState` from `react-dom` has been renamed to
|
|
17
|
+
* `useActionState` and moved to `react`.
|
|
18
|
+
*
|
|
19
|
+
* Before:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { useFormState } from 'react-dom';
|
|
22
|
+
* const [state, formAction] = useFormState(action, initialState);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* After:
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { useActionState } from 'react';
|
|
28
|
+
* const [state, formAction] = useActionState(action, initialState);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#new-hook-useactionstate
|
|
32
|
+
*/
|
|
33
|
+
export class ReplaceUseFormState extends Recipe {
|
|
34
|
+
readonly name = "org.openrewrite.react.19.replace-use-form-state";
|
|
35
|
+
readonly displayName: string = "Replace `useFormState` with `useActionState`";
|
|
36
|
+
readonly description: string = "In React 19, `useFormState` from `react-dom` has been renamed to `useActionState` and moved to `react`.";
|
|
37
|
+
|
|
38
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
39
|
+
const reactDomOptions = {
|
|
40
|
+
context: ['import { useFormState } from "react-dom";'],
|
|
41
|
+
dependencies: {'@types/react-dom': '^18.0.0'}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const args = capture<Expression>({variadic: true});
|
|
45
|
+
|
|
46
|
+
const rule = rewrite(() => ({
|
|
47
|
+
before: pattern`useFormState(${args})`.configure(reactDomOptions),
|
|
48
|
+
after: template`useActionState(${args})`
|
|
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", "useFormState");
|
|
58
|
+
maybeAddImport(this, {module: "react", member: "useActionState", onlyIfReferenced: false});
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return m;
|
|
63
|
+
}
|
|
64
|
+
}();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {Recipe} from "@openrewrite/rewrite";
|
|
2
|
+
import {UpgradeToReact18} from "../react18/upgrade-to-react-18";
|
|
3
|
+
import {RemoveForwardRef} from "./remove-forward-ref";
|
|
4
|
+
import {RemoveReactFC} from "./remove-react-fc";
|
|
5
|
+
import {ReplaceActImport} from "./replace-act-import";
|
|
6
|
+
import {RemoveContextProvider} from "./remove-context-provider";
|
|
7
|
+
import {UseContextHook} from "./use-context-hook";
|
|
8
|
+
import {ReplaceUseFormState} from "./replace-use-form-state";
|
|
9
|
+
import {ReplaceStringRef} from "./replace-string-ref";
|
|
10
|
+
import {ReplaceDefaultProps} from "./replace-default-props";
|
|
11
|
+
import {ReplaceReactDomHydrate} from "./replace-reactdom-hydrate";
|
|
12
|
+
import {UseRefRequiredInitial} from "./use-ref-required-initial";
|
|
13
|
+
import {RemovePropTypes} from "./remove-prop-types";
|
|
14
|
+
import {NoImplicitRefCallbackReturn} from "./no-implicit-ref-callback-return";
|
|
15
|
+
import {DeprecatedReactTypes} from "./deprecated-react-types";
|
|
16
|
+
import {ReplaceReactShallowRenderer} from "./replace-react-shallow-renderer";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Composite recipe that applies all React 19 migration recipes.
|
|
20
|
+
*
|
|
21
|
+
* Running this recipe applies all necessary changes to upgrade
|
|
22
|
+
* a React 18 codebase to React 19.
|
|
23
|
+
*
|
|
24
|
+
* Changes include:
|
|
25
|
+
* - Remove `forwardRef` wrapper (ref is now a regular prop)
|
|
26
|
+
* - Remove `React.FC` type annotations
|
|
27
|
+
* - Move `act` import from `react-dom/test-utils` to `react`
|
|
28
|
+
* - Remove `.Provider` from Context JSX elements
|
|
29
|
+
* - Replace `useContext` with `use`
|
|
30
|
+
* - Replace `useFormState` with `useActionState`
|
|
31
|
+
* - Replace string refs with callback refs
|
|
32
|
+
* - Replace `defaultProps` with default parameters
|
|
33
|
+
* - Replace `ReactDOM.hydrate` with `hydrateRoot`
|
|
34
|
+
* - Add `undefined` to `useRef()` calls with no arguments
|
|
35
|
+
* - Remove `propTypes` assignments
|
|
36
|
+
* - Wrap implicit ref callback returns in block bodies
|
|
37
|
+
* - Replace deprecated TypeScript types
|
|
38
|
+
* - Replace `react-test-renderer/shallow` with `react-shallow-renderer`
|
|
39
|
+
*
|
|
40
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide
|
|
41
|
+
*/
|
|
42
|
+
export class UpgradeToReact19 extends Recipe {
|
|
43
|
+
readonly name = "org.openrewrite.react.migrate.upgrade-to-react-19";
|
|
44
|
+
readonly displayName = "Upgrade to React 19";
|
|
45
|
+
readonly description = "Migrate deprecated and removed APIs for React 19 compatibility. This includes removing forwardRef, updating Context.Provider usage, replacing useContext with use, and other breaking changes.";
|
|
46
|
+
|
|
47
|
+
async recipeList(): Promise<Recipe[]> {
|
|
48
|
+
return [
|
|
49
|
+
new UpgradeToReact18(),
|
|
50
|
+
new RemoveForwardRef(),
|
|
51
|
+
new RemoveReactFC(),
|
|
52
|
+
new ReplaceActImport(),
|
|
53
|
+
new RemoveContextProvider(),
|
|
54
|
+
new UseContextHook(),
|
|
55
|
+
new ReplaceUseFormState(),
|
|
56
|
+
new ReplaceStringRef(),
|
|
57
|
+
new ReplaceDefaultProps(),
|
|
58
|
+
new ReplaceReactDomHydrate(),
|
|
59
|
+
new UseRefRequiredInitial(),
|
|
60
|
+
new RemovePropTypes(),
|
|
61
|
+
new NoImplicitRefCallbackReturn(),
|
|
62
|
+
new DeprecatedReactTypes(),
|
|
63
|
+
new ReplaceReactShallowRenderer(),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
JavaScriptVisitor,
|
|
4
|
+
maybeAddImport,
|
|
5
|
+
maybeRemoveImport,
|
|
6
|
+
capture,
|
|
7
|
+
pattern,
|
|
8
|
+
rewrite,
|
|
9
|
+
template
|
|
10
|
+
} from "@openrewrite/rewrite/javascript";
|
|
11
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replaces `useContext()` with `use()` for React 19.
|
|
15
|
+
*
|
|
16
|
+
* In React 19, `useContext` is replaced by the new `use` API.
|
|
17
|
+
*
|
|
18
|
+
* Before:
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { useContext } from 'react';
|
|
21
|
+
* const theme = useContext(ThemeContext);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* After:
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import { use } from 'react';
|
|
27
|
+
* const theme = use(ThemeContext);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Also handles `React.useContext()` → `React.use()`.
|
|
31
|
+
*
|
|
32
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#use-context
|
|
33
|
+
*/
|
|
34
|
+
export class UseContextHook extends Recipe {
|
|
35
|
+
readonly name = "org.openrewrite.react.19.use-context-hook";
|
|
36
|
+
readonly displayName: string = "Replace `useContext` with `use`";
|
|
37
|
+
readonly description: string = "In React 19, `useContext` is replaced by the `use` API. This recipe updates both direct and namespace imports.";
|
|
38
|
+
|
|
39
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
40
|
+
const reactOptions = {
|
|
41
|
+
context: ['import { useContext } from "react";'],
|
|
42
|
+
dependencies: {'@types/react': '^18.0.0'}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const ctx = capture<Expression>({variadic: true});
|
|
46
|
+
|
|
47
|
+
const rule = rewrite(() => ({
|
|
48
|
+
before: pattern`useContext(${ctx})`.configure(reactOptions),
|
|
49
|
+
after: template`use(${ctx})`
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
53
|
+
override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
|
|
54
|
+
let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
|
|
55
|
+
|
|
56
|
+
const result = await rule.tryOn(this.cursor, m!);
|
|
57
|
+
if (result) {
|
|
58
|
+
maybeRemoveImport(this, "react", "useContext");
|
|
59
|
+
maybeAddImport(this, {module: "react", member: "use", onlyIfReferenced: false});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return m;
|
|
64
|
+
}
|
|
65
|
+
}();
|
|
66
|
+
}
|
|
67
|
+
}
|