@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,123 @@
|
|
|
1
|
+
import {emptyMarkers, ExecutionContext, markers, randomId, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS, JSX} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
const emptySpace = {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: '', comments: []};
|
|
7
|
+
const singleSpace = {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: ' ', comments: []};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts implicit returns in ref callbacks to block bodies.
|
|
11
|
+
*
|
|
12
|
+
* In React 19, ref callbacks can return a cleanup function.
|
|
13
|
+
* Arrow functions with expression bodies implicitly return a value,
|
|
14
|
+
* which React would incorrectly interpret as a cleanup function.
|
|
15
|
+
*
|
|
16
|
+
* Before:
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <div ref={current => (instance = current)} />
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* After:
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <div ref={current => { instance = current; }} />
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#cleanup-functions-for-refs
|
|
27
|
+
*/
|
|
28
|
+
export class NoImplicitRefCallbackReturn extends Recipe {
|
|
29
|
+
readonly name = "org.openrewrite.react.19.no-implicit-ref-callback-return";
|
|
30
|
+
readonly displayName: string = "Remove implicit ref callback returns";
|
|
31
|
+
readonly description: string = "In React 19, ref callbacks can return cleanup functions. Arrow functions with expression bodies implicitly return values, which React would interpret as cleanup functions. This recipe wraps them in block bodies.";
|
|
32
|
+
|
|
33
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
34
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
35
|
+
override async visitJsxAttribute(attribute: JSX.Attribute, ctx: ExecutionContext): Promise<J | undefined> {
|
|
36
|
+
let attr = await super.visitJsxAttribute(attribute, ctx) as JSX.Attribute;
|
|
37
|
+
|
|
38
|
+
// Check if the attribute key is "ref"
|
|
39
|
+
if (attr.key.kind !== J.Kind.Identifier) {
|
|
40
|
+
return attr;
|
|
41
|
+
}
|
|
42
|
+
if ((attr.key as J.Identifier).simpleName !== 'ref') {
|
|
43
|
+
return attr;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!attr.value) {
|
|
47
|
+
return attr;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const valueExpr = attr.value.element;
|
|
51
|
+
|
|
52
|
+
// The ref value is wrapped in JSX embedded expression: {expr}
|
|
53
|
+
if (valueExpr.kind !== JS.Kind.JsxEmbeddedExpression) {
|
|
54
|
+
return attr;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const embedded = valueExpr as JSX.EmbeddedExpression;
|
|
58
|
+
const innerExpr = (embedded.expression as any).element ?? embedded.expression;
|
|
59
|
+
|
|
60
|
+
// Check if it's an arrow function
|
|
61
|
+
if (innerExpr.kind !== JS.Kind.ArrowFunction) {
|
|
62
|
+
return attr;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const arrowFn = innerExpr as JS.ArrowFunction;
|
|
66
|
+
const lambda = arrowFn.lambda;
|
|
67
|
+
|
|
68
|
+
// Check if body is an expression (not a block)
|
|
69
|
+
if (!lambda.body || lambda.body.kind === J.Kind.Block) {
|
|
70
|
+
return attr;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The body is an expression — unwrap parentheses if present
|
|
74
|
+
let stmtExpr: Expression = lambda.body as any;
|
|
75
|
+
if (stmtExpr.kind === J.Kind.Parentheses) {
|
|
76
|
+
stmtExpr = ((stmtExpr as J.Parentheses<Expression>).tree as any).element;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Strip prefix from the expression since ExpressionStatement carries the space
|
|
80
|
+
stmtExpr = {...stmtExpr, prefix: emptySpace} as any;
|
|
81
|
+
|
|
82
|
+
const semicolonMarkers = markers({
|
|
83
|
+
kind: J.Markers.Semicolon,
|
|
84
|
+
id: randomId()
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const exprStatement: JS.ExpressionStatement = {
|
|
88
|
+
kind: JS.Kind.ExpressionStatement,
|
|
89
|
+
id: randomId(),
|
|
90
|
+
prefix: singleSpace,
|
|
91
|
+
markers: emptyMarkers,
|
|
92
|
+
expression: stmtExpr
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const block: J.Block = {
|
|
96
|
+
kind: J.Kind.Block,
|
|
97
|
+
id: randomId(),
|
|
98
|
+
prefix: lambda.body.prefix,
|
|
99
|
+
markers: emptyMarkers,
|
|
100
|
+
static: {
|
|
101
|
+
kind: J.Kind.RightPadded,
|
|
102
|
+
element: false,
|
|
103
|
+
after: emptySpace,
|
|
104
|
+
markers: emptyMarkers
|
|
105
|
+
} as any,
|
|
106
|
+
statements: [{
|
|
107
|
+
kind: J.Kind.RightPadded,
|
|
108
|
+
element: exprStatement,
|
|
109
|
+
after: emptySpace,
|
|
110
|
+
markers: semicolonMarkers
|
|
111
|
+
} as any],
|
|
112
|
+
end: singleSpace
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return create(attr, draft => {
|
|
116
|
+
const embeddedDraft = draft.value!.element as any;
|
|
117
|
+
const innerDraft = embeddedDraft.expression.element ?? embeddedDraft.expression;
|
|
118
|
+
innerDraft.lambda.body = block;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {JS, JSX} from "@openrewrite/rewrite/javascript";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes `.Provider` from Context JSX elements.
|
|
8
|
+
*
|
|
9
|
+
* In React 19, `<Context.Provider>` is deprecated in favor of
|
|
10
|
+
* rendering `<Context>` directly as a provider.
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ThemeContext.Provider value={theme}>
|
|
15
|
+
* {children}
|
|
16
|
+
* </ThemeContext.Provider>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* After:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <ThemeContext value={theme}>
|
|
22
|
+
* {children}
|
|
23
|
+
* </ThemeContext>
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#context-as-a-provider
|
|
27
|
+
*/
|
|
28
|
+
export class RemoveContextProvider extends Recipe {
|
|
29
|
+
readonly name = "org.openrewrite.react.19.remove-context-provider";
|
|
30
|
+
readonly displayName: string = "Remove `Context.Provider` wrapper";
|
|
31
|
+
readonly description: string = "In React 19, `<Context.Provider>` is deprecated. Render `<Context>` directly as a provider instead.";
|
|
32
|
+
|
|
33
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
34
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
35
|
+
override async visitJsxTag(tag: JSX.Tag, ctx: ExecutionContext): Promise<J | undefined> {
|
|
36
|
+
let t = await super.visitJsxTag(tag, ctx) as JSX.Tag;
|
|
37
|
+
|
|
38
|
+
const openName = t.openName.element;
|
|
39
|
+
|
|
40
|
+
// Check if the opening tag is a FieldAccess like Something.Provider
|
|
41
|
+
if (openName.kind !== J.Kind.FieldAccess) {
|
|
42
|
+
return t;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const fieldAccess = openName as J.FieldAccess;
|
|
46
|
+
const propertyName = fieldAccess.name.element.simpleName;
|
|
47
|
+
|
|
48
|
+
if (propertyName !== 'Provider') {
|
|
49
|
+
return t;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Replace the FieldAccess with just the target (e.g., ThemeContext)
|
|
53
|
+
const target = fieldAccess.target as J.Identifier;
|
|
54
|
+
const newName: J.Identifier = {
|
|
55
|
+
...target,
|
|
56
|
+
prefix: openName.prefix
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Update the opening tag name
|
|
60
|
+
const result = {
|
|
61
|
+
...t,
|
|
62
|
+
openName: {
|
|
63
|
+
...t.openName,
|
|
64
|
+
element: newName
|
|
65
|
+
}
|
|
66
|
+
} as any;
|
|
67
|
+
|
|
68
|
+
// Also update the closing tag name if present (non-self-closing tags)
|
|
69
|
+
if (t.closingName) {
|
|
70
|
+
const closingElement = t.closingName.element;
|
|
71
|
+
if (closingElement.kind === J.Kind.FieldAccess) {
|
|
72
|
+
const closingTarget = (closingElement as J.FieldAccess).target as J.Identifier;
|
|
73
|
+
result.closingName = {
|
|
74
|
+
...t.closingName,
|
|
75
|
+
element: {
|
|
76
|
+
...closingTarget,
|
|
77
|
+
prefix: closingElement.prefix
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result as JSX.Tag;
|
|
84
|
+
}
|
|
85
|
+
}();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
capture,
|
|
4
|
+
fromRecipe,
|
|
5
|
+
JavaScriptVisitor,
|
|
6
|
+
maybeRemoveImport,
|
|
7
|
+
pattern,
|
|
8
|
+
rewrite,
|
|
9
|
+
template
|
|
10
|
+
} from "@openrewrite/rewrite/javascript";
|
|
11
|
+
import {Expression, J} from "@openrewrite/rewrite/java";
|
|
12
|
+
import {SimplifyObjectPatternProperty} from "../simplify-object-pattern-property";
|
|
13
|
+
|
|
14
|
+
export class RemoveForwardRef extends Recipe {
|
|
15
|
+
readonly name = "org.openrewrite.react.19.remove-forward-ref";
|
|
16
|
+
readonly displayName: string = "Remove `React.forwardRef` wrapper";
|
|
17
|
+
readonly description: string = "`React.forwardRef` is deprecated for Function Components in React 19. This recipe removes the `forwardRef` wrapper and converts ref to a regular prop.";
|
|
18
|
+
|
|
19
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
20
|
+
// Pattern: forwardRef((props, ref) => body)
|
|
21
|
+
const reactOptions = {
|
|
22
|
+
context: ['import { forwardRef } from "react";'],
|
|
23
|
+
dependencies: {'@types/react': '^19.0.0'}
|
|
24
|
+
};
|
|
25
|
+
const {props, ref, body, name, refType, propsType} = {
|
|
26
|
+
props: capture<Expression>({variadic: true}),
|
|
27
|
+
ref: capture(),
|
|
28
|
+
body: capture({variadic: true}),
|
|
29
|
+
name: capture(),
|
|
30
|
+
refType: capture(),
|
|
31
|
+
propsType: capture()
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const rule =
|
|
35
|
+
// Handle forwardRef with type parameters: forwardRef<Ref, Props>((props, ref) => body)
|
|
36
|
+
// Note: This creates a typed arrow function with ref as part of props
|
|
37
|
+
rewrite(() => ({
|
|
38
|
+
before: pattern`forwardRef<${refType}, ${propsType}>((${props}, ${ref}) => ${body})`.configure(reactOptions),
|
|
39
|
+
after: template`({ ref: ${ref}, ...${props} }: ${propsType} & { ref?: React.Ref<${refType}> }) => ${body}`
|
|
40
|
+
})).orElse(rewrite(() => ({
|
|
41
|
+
before: pattern`forwardRef((${props}, ${ref}) => ${body})`.configure(reactOptions),
|
|
42
|
+
after: template`({ ref: ${ref}, ...${props} }) => ${body}`
|
|
43
|
+
}))).orElse(rewrite(() => ({
|
|
44
|
+
before: pattern`forwardRef(function ${name}({${props}}, ${ref}) {${body}})`.configure(reactOptions),
|
|
45
|
+
after: template`function ${name}({ ref: ${ref}, ${props} }) {${body}}`
|
|
46
|
+
}))).orElse(rewrite(() => ({
|
|
47
|
+
before: pattern`forwardRef(function ${name}(${props}, ${ref}) {${body}})`.configure(reactOptions),
|
|
48
|
+
after: template`function ${name}({ ref: ${ref}, ...${props} }) {${body}}`
|
|
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);
|
|
54
|
+
|
|
55
|
+
let result = await rule
|
|
56
|
+
.andThen(fromRecipe(new SimplifyObjectPatternProperty(), p))
|
|
57
|
+
.tryOn(this.cursor, m!);
|
|
58
|
+
if (result) {
|
|
59
|
+
maybeRemoveImport(this, "react", "default");
|
|
60
|
+
maybeRemoveImport(this, "react", "forwardRef");
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return m;
|
|
66
|
+
}
|
|
67
|
+
}();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
4
|
+
import {create} from "mutative";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Removes `Component.propTypes` assignments.
|
|
8
|
+
*
|
|
9
|
+
* In React 19, PropTypes are silently ignored at runtime.
|
|
10
|
+
* This recipe removes the `propTypes` assignments to clean up code.
|
|
11
|
+
*
|
|
12
|
+
* Before:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import PropTypes from 'prop-types';
|
|
15
|
+
*
|
|
16
|
+
* function Greeting({name}) {
|
|
17
|
+
* return <div>Hello {name}</div>;
|
|
18
|
+
* }
|
|
19
|
+
* Greeting.propTypes = {
|
|
20
|
+
* name: PropTypes.string.isRequired
|
|
21
|
+
* };
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* After:
|
|
25
|
+
* ```tsx
|
|
26
|
+
* function Greeting({name}) {
|
|
27
|
+
* return <div>Hello {name}</div>;
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-proptypes
|
|
32
|
+
*/
|
|
33
|
+
export class RemovePropTypes extends Recipe {
|
|
34
|
+
readonly name = "org.openrewrite.react.19.remove-prop-types";
|
|
35
|
+
readonly displayName: string = "Remove `propTypes` assignments";
|
|
36
|
+
readonly description: string = "Removes `Component.propTypes = {...}` assignments. PropTypes are silently ignored in React 19.";
|
|
37
|
+
|
|
38
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
39
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
40
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
41
|
+
let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
|
|
42
|
+
|
|
43
|
+
// Find and remove propTypes assignment statements
|
|
44
|
+
const statementsToRemove = new Set<string>();
|
|
45
|
+
|
|
46
|
+
for (const stmt of result.statements) {
|
|
47
|
+
const s = stmt.element;
|
|
48
|
+
if (!s) continue;
|
|
49
|
+
|
|
50
|
+
let assignment: J.Assignment | undefined;
|
|
51
|
+
|
|
52
|
+
if (s.kind === JS.Kind.ExpressionStatement) {
|
|
53
|
+
const exprStmt = s as JS.ExpressionStatement;
|
|
54
|
+
const expr = exprStmt.expression;
|
|
55
|
+
if (expr.kind === J.Kind.Assignment) {
|
|
56
|
+
assignment = expr as J.Assignment;
|
|
57
|
+
}
|
|
58
|
+
} else if (s.kind === J.Kind.Assignment) {
|
|
59
|
+
assignment = s as J.Assignment;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!assignment) continue;
|
|
63
|
+
|
|
64
|
+
// Check for Component.propTypes = { ... }
|
|
65
|
+
if (assignment.variable.kind !== J.Kind.FieldAccess) continue;
|
|
66
|
+
const fa = assignment.variable as J.FieldAccess;
|
|
67
|
+
if (fa.name.element.simpleName !== 'propTypes') continue;
|
|
68
|
+
if (!isIdentifier(fa.target)) continue;
|
|
69
|
+
|
|
70
|
+
statementsToRemove.add((s as any).id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (statementsToRemove.size === 0) return result;
|
|
74
|
+
|
|
75
|
+
const filteredStatements = result.statements.filter(stmt => {
|
|
76
|
+
const s = stmt.element;
|
|
77
|
+
return !s || !statementsToRemove.has((s as any).id);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return create(result, draft => {
|
|
81
|
+
draft.statements = filteredStatements as any;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {
|
|
3
|
+
capture,
|
|
4
|
+
JavaScriptVisitor,
|
|
5
|
+
pattern,
|
|
6
|
+
rewrite,
|
|
7
|
+
template,
|
|
8
|
+
maybeRemoveImport
|
|
9
|
+
} from "@openrewrite/rewrite/javascript";
|
|
10
|
+
import {Expression, J, singleSpace, emptySpace} from "@openrewrite/rewrite/java";
|
|
11
|
+
import {JS} from "@openrewrite/rewrite/javascript";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Removes React.FC and FC type annotations from functional components.
|
|
15
|
+
*
|
|
16
|
+
* React.FC (FunctionComponent) was historically used to type React components,
|
|
17
|
+
* but it has several drawbacks:
|
|
18
|
+
* - It implicitly types children, which can mask errors
|
|
19
|
+
* - It makes generic components harder to type
|
|
20
|
+
* - It's no longer recommended by the React TypeScript community
|
|
21
|
+
*
|
|
22
|
+
* This recipe transforms:
|
|
23
|
+
* - `const App: React.FC<Props> = (props) => ...` to `const App = (props: Props) => ...`
|
|
24
|
+
* - `const App: FC<Props> = ({ name }) => ...` to `const App = ({ name }: Props) => ...`
|
|
25
|
+
* - `const App: React.FC = () => ...` to `const App = () => ...`
|
|
26
|
+
*/
|
|
27
|
+
export class RemoveReactFC extends Recipe {
|
|
28
|
+
readonly name = "org.openrewrite.react.19.remove-react-fc";
|
|
29
|
+
readonly displayName: string = "Remove `React.FC` type annotation";
|
|
30
|
+
readonly description: string = "Removes `React.FC` and `FC` type annotations from functional components, moving the props type to the function parameter instead.";
|
|
31
|
+
|
|
32
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
33
|
+
const reactOptions = {
|
|
34
|
+
context: ['import { FC } from "react";'],
|
|
35
|
+
dependencies: {'@types/react': '^18.0.0'}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const {props, body, propsType} = {
|
|
39
|
+
props: capture<Expression>({variadic: true}),
|
|
40
|
+
body: capture({variadic: true}),
|
|
41
|
+
propsType: capture()
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Pattern: FC<Props>((props) => body) - matches the type annotation + arrow function
|
|
45
|
+
// This targets the initializer expression where the FC type wraps the arrow function
|
|
46
|
+
const ruleWithPropsType = rewrite(() => ({
|
|
47
|
+
before: pattern`FC<${propsType}>((${props}) => ${body})`.configure(reactOptions),
|
|
48
|
+
after: template`(${props}: ${propsType}) => ${body}`
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Pattern without type argument: FC(() => body)
|
|
52
|
+
const ruleWithoutPropsType = rewrite(() => ({
|
|
53
|
+
before: pattern`FC(() => ${body})`.configure(reactOptions),
|
|
54
|
+
after: template`() => ${body}`
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
58
|
+
override async visitVariableDeclarations(varDecl: J.VariableDeclarations, ctx: ExecutionContext): Promise<J | undefined> {
|
|
59
|
+
let v = await super.visitVariableDeclarations(varDecl, ctx) as J.VariableDeclarations;
|
|
60
|
+
|
|
61
|
+
// Check if there's a type expression that could be FC or React.FC
|
|
62
|
+
if (!v.typeExpression) {
|
|
63
|
+
return v;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if type is wrapped in JS.TypeInfo
|
|
67
|
+
const typeExpr = v.typeExpression;
|
|
68
|
+
if (typeExpr.kind !== JS.Kind.TypeInfo) {
|
|
69
|
+
return v;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const typeInfo = typeExpr as JS.TypeInfo;
|
|
73
|
+
const isFCType = this.isFunctionalComponentType(typeInfo.typeIdentifier);
|
|
74
|
+
|
|
75
|
+
if (!isFCType) {
|
|
76
|
+
return v;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Get the props type argument if present
|
|
80
|
+
const propsTypeArg = this.getPropsTypeArgument(typeInfo.typeIdentifier);
|
|
81
|
+
|
|
82
|
+
// Get the initializer (should be an arrow function)
|
|
83
|
+
if (v.variables.length !== 1) {
|
|
84
|
+
return v;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const namedVar = v.variables[0].element;
|
|
88
|
+
if (!namedVar.initializer) {
|
|
89
|
+
return v;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const initializer = namedVar.initializer.element;
|
|
93
|
+
|
|
94
|
+
// Check if initializer is an arrow function
|
|
95
|
+
if (initializer.kind !== JS.Kind.ArrowFunction) {
|
|
96
|
+
return v;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const arrowFn = initializer as JS.ArrowFunction;
|
|
100
|
+
|
|
101
|
+
// Modify the arrow function to add props type to the first parameter
|
|
102
|
+
const modifiedArrowFn = propsTypeArg
|
|
103
|
+
? this.addPropsTypeToArrowFunction(arrowFn, propsTypeArg)
|
|
104
|
+
: arrowFn;
|
|
105
|
+
|
|
106
|
+
// Remove the type expression and update the initializer
|
|
107
|
+
maybeRemoveImport(this, "react", "FC");
|
|
108
|
+
maybeRemoveImport(this, "react", "FunctionComponent");
|
|
109
|
+
|
|
110
|
+
const updatedVarDecl: J.VariableDeclarations = {
|
|
111
|
+
...v,
|
|
112
|
+
typeExpression: undefined,
|
|
113
|
+
variables: [{
|
|
114
|
+
...v.variables[0],
|
|
115
|
+
element: {
|
|
116
|
+
...namedVar,
|
|
117
|
+
initializer: {
|
|
118
|
+
...namedVar.initializer,
|
|
119
|
+
element: modifiedArrowFn
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return updatedVarDecl;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if the type identifier is FC, React.FC, FunctionComponent, or React.FunctionComponent
|
|
130
|
+
*/
|
|
131
|
+
private isFunctionalComponentType(typeIdentifier: any): boolean {
|
|
132
|
+
// Handle ParameterizedType: FC<Props>
|
|
133
|
+
if (typeIdentifier.kind === J.Kind.ParameterizedType) {
|
|
134
|
+
const paramType = typeIdentifier as J.ParameterizedType;
|
|
135
|
+
return this.isFCIdentifier(paramType.class);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Handle bare FC (no type argument)
|
|
139
|
+
return this.isFCIdentifier(typeIdentifier);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check if an expression is FC, React.FC, FunctionComponent, or React.FunctionComponent
|
|
144
|
+
*/
|
|
145
|
+
private isFCIdentifier(expr: any): boolean {
|
|
146
|
+
// Direct identifier: FC or FunctionComponent
|
|
147
|
+
if (expr.kind === J.Kind.Identifier) {
|
|
148
|
+
const name = (expr as J.Identifier).simpleName;
|
|
149
|
+
return name === 'FC' || name === 'FunctionComponent';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// FieldAccess: React.FC or React.FunctionComponent
|
|
153
|
+
if (expr.kind === J.Kind.FieldAccess) {
|
|
154
|
+
const fieldAccess = expr as J.FieldAccess;
|
|
155
|
+
const fieldName = fieldAccess.name.element.simpleName;
|
|
156
|
+
|
|
157
|
+
if (fieldName !== 'FC' && fieldName !== 'FunctionComponent') {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check if target is React
|
|
162
|
+
if (fieldAccess.target.kind === J.Kind.Identifier) {
|
|
163
|
+
return (fieldAccess.target as J.Identifier).simpleName === 'React';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Extract the props type from FC<Props> or React.FC<Props>
|
|
172
|
+
*/
|
|
173
|
+
private getPropsTypeArgument(typeIdentifier: any): Expression | undefined {
|
|
174
|
+
if (typeIdentifier.kind === J.Kind.ParameterizedType) {
|
|
175
|
+
const paramType = typeIdentifier as J.ParameterizedType;
|
|
176
|
+
if (paramType.typeParameters && paramType.typeParameters.elements.length > 0) {
|
|
177
|
+
return paramType.typeParameters.elements[0].element;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Add props type annotation to the first parameter of an arrow function
|
|
185
|
+
*/
|
|
186
|
+
private addPropsTypeToArrowFunction(arrowFn: JS.ArrowFunction, propsType: Expression): JS.ArrowFunction {
|
|
187
|
+
const lambda = arrowFn.lambda;
|
|
188
|
+
|
|
189
|
+
// If no parameters, nothing to type
|
|
190
|
+
if (!lambda.parameters || lambda.parameters.parameters.length === 0) {
|
|
191
|
+
return arrowFn;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const params = lambda.parameters.parameters;
|
|
195
|
+
const firstParam = params[0].element;
|
|
196
|
+
|
|
197
|
+
// Add type annotation to the first parameter
|
|
198
|
+
if (firstParam.kind === J.Kind.VariableDeclarations) {
|
|
199
|
+
const varDecl = firstParam as J.VariableDeclarations;
|
|
200
|
+
|
|
201
|
+
// If it already has a type, don't override
|
|
202
|
+
if (varDecl.typeExpression) {
|
|
203
|
+
return arrowFn;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Wrap the props type in a TypeInfo node
|
|
207
|
+
// The TypeInfo prefix comes BEFORE the colon in rendering
|
|
208
|
+
// The typeIdentifier prefix comes AFTER the colon
|
|
209
|
+
const typeInfo: JS.TypeInfo = {
|
|
210
|
+
kind: JS.Kind.TypeInfo,
|
|
211
|
+
id: propsType.id,
|
|
212
|
+
prefix: emptySpace, // No space before the colon
|
|
213
|
+
markers: propsType.markers,
|
|
214
|
+
typeIdentifier: {
|
|
215
|
+
...propsType,
|
|
216
|
+
prefix: singleSpace // Space after the colon (: Props)
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const updatedVarDecl: J.VariableDeclarations = {
|
|
221
|
+
...varDecl,
|
|
222
|
+
typeExpression: typeInfo
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...arrowFn,
|
|
227
|
+
lambda: {
|
|
228
|
+
...lambda,
|
|
229
|
+
parameters: {
|
|
230
|
+
...lambda.parameters,
|
|
231
|
+
parameters: [
|
|
232
|
+
{
|
|
233
|
+
...params[0],
|
|
234
|
+
element: updatedVarDecl
|
|
235
|
+
},
|
|
236
|
+
...params.slice(1)
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return arrowFn;
|
|
244
|
+
}
|
|
245
|
+
}();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {ChangeImport} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Migrates `act` imports from `react-dom/test-utils` to `react`.
|
|
6
|
+
*
|
|
7
|
+
* In React 19, the `act` function has been moved from `react-dom/test-utils`
|
|
8
|
+
* to `react` itself. This recipe automatically updates imports.
|
|
9
|
+
*
|
|
10
|
+
* Before:
|
|
11
|
+
* ```tsx
|
|
12
|
+
* import { act } from 'react-dom/test-utils';
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* After:
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { act } from 'react';
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-react-dom-test-utils
|
|
21
|
+
*/
|
|
22
|
+
export class ReplaceActImport extends Recipe {
|
|
23
|
+
readonly name = "org.openrewrite.react.19.replace-act-import";
|
|
24
|
+
readonly displayName: string = "Replace `act` import from react-dom/test-utils";
|
|
25
|
+
readonly description: string = "In React 19, `act` has been moved from `react-dom/test-utils` to `react`. This recipe updates the import statement.";
|
|
26
|
+
|
|
27
|
+
private changeImport = new ChangeImport({
|
|
28
|
+
oldModule: "react-dom/test-utils",
|
|
29
|
+
oldMember: "act",
|
|
30
|
+
newModule: "react"
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
34
|
+
return this.changeImport.editor();
|
|
35
|
+
}
|
|
36
|
+
}
|