@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.
Files changed (251) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +130 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/migration/change-component-prop-value.d.ts +20 -0
  6. package/dist/migration/change-component-prop-value.d.ts.map +1 -0
  7. package/dist/migration/change-component-prop-value.js +217 -0
  8. package/dist/migration/change-component-prop-value.js.map +1 -0
  9. package/dist/react-native/view-prop-types.d.ts +8 -0
  10. package/dist/react-native/view-prop-types.d.ts.map +1 -0
  11. package/dist/react-native/view-prop-types.js +65 -0
  12. package/dist/react-native/view-prop-types.js.map +1 -0
  13. package/dist/react16/error-boundaries.d.ts +8 -0
  14. package/dist/react16/error-boundaries.d.ts.map +1 -0
  15. package/dist/react16/error-boundaries.js +42 -0
  16. package/dist/react16/error-boundaries.js.map +1 -0
  17. package/dist/react16/find-dom-node.d.ts +8 -0
  18. package/dist/react16/find-dom-node.d.ts.map +1 -0
  19. package/dist/react16/find-dom-node.js +48 -0
  20. package/dist/react16/find-dom-node.js.map +1 -0
  21. package/dist/react16/react-dom-factories.d.ts +8 -0
  22. package/dist/react16/react-dom-factories.d.ts.map +1 -0
  23. package/dist/react16/react-dom-factories.js +72 -0
  24. package/dist/react16/react-dom-factories.js.map +1 -0
  25. package/dist/react16/react-prop-types.d.ts +8 -0
  26. package/dist/react16/react-prop-types.d.ts.map +1 -0
  27. package/dist/react16/react-prop-types.js +69 -0
  28. package/dist/react16/react-prop-types.js.map +1 -0
  29. package/dist/react16/react-to-react-dom.d.ts +8 -0
  30. package/dist/react16/react-to-react-dom.d.ts.map +1 -0
  31. package/dist/react16/react-to-react-dom.js +97 -0
  32. package/dist/react16/react-to-react-dom.js.map +1 -0
  33. package/dist/react16/replace-create-factory.d.ts +8 -0
  34. package/dist/react16/replace-create-factory.d.ts.map +1 -0
  35. package/dist/react16/replace-create-factory.js +69 -0
  36. package/dist/react16/replace-create-factory.js.map +1 -0
  37. package/dist/react16/upgrade-to-react-16.d.ts +8 -0
  38. package/dist/react16/upgrade-to-react-16.d.ts.map +1 -0
  39. package/dist/react16/upgrade-to-react-16.js +41 -0
  40. package/dist/react16/upgrade-to-react-16.js.map +1 -0
  41. package/dist/react17/remove-event-persist.d.ts +8 -0
  42. package/dist/react17/remove-event-persist.d.ts.map +1 -0
  43. package/dist/react17/remove-event-persist.js +114 -0
  44. package/dist/react17/remove-event-persist.js.map +1 -0
  45. package/dist/react17/rename-unsafe-lifecycles.d.ts +8 -0
  46. package/dist/react17/rename-unsafe-lifecycles.d.ts.map +1 -0
  47. package/dist/react17/rename-unsafe-lifecycles.js +48 -0
  48. package/dist/react17/rename-unsafe-lifecycles.js.map +1 -0
  49. package/dist/react17/update-react-imports.d.ts +8 -0
  50. package/dist/react17/update-react-imports.d.ts.map +1 -0
  51. package/dist/react17/update-react-imports.js +40 -0
  52. package/dist/react17/update-react-imports.js.map +1 -0
  53. package/dist/react17/upgrade-to-react-17.d.ts +8 -0
  54. package/dist/react17/upgrade-to-react-17.d.ts.map +1 -0
  55. package/dist/react17/upgrade-to-react-17.js +37 -0
  56. package/dist/react17/upgrade-to-react-17.js.map +1 -0
  57. package/dist/react18/remove-unstable-batched-updates.d.ts +8 -0
  58. package/dist/react18/remove-unstable-batched-updates.d.ts.map +1 -0
  59. package/dist/react18/remove-unstable-batched-updates.js +170 -0
  60. package/dist/react18/remove-unstable-batched-updates.js.map +1 -0
  61. package/dist/react18/replace-reactdom-render.d.ts +8 -0
  62. package/dist/react18/replace-reactdom-render.d.ts.map +1 -0
  63. package/dist/react18/replace-reactdom-render.js +55 -0
  64. package/dist/react18/replace-reactdom-render.js.map +1 -0
  65. package/dist/react18/replace-render-callback.d.ts +8 -0
  66. package/dist/react18/replace-render-callback.d.ts.map +1 -0
  67. package/dist/react18/replace-render-callback.js +60 -0
  68. package/dist/react18/replace-render-callback.js.map +1 -0
  69. package/dist/react18/replace-unmount-component-at-node.d.ts +8 -0
  70. package/dist/react18/replace-unmount-component-at-node.d.ts.map +1 -0
  71. package/dist/react18/replace-unmount-component-at-node.js +54 -0
  72. package/dist/react18/replace-unmount-component-at-node.js.map +1 -0
  73. package/dist/react18/upgrade-to-react-18.d.ts +8 -0
  74. package/dist/react18/upgrade-to-react-18.d.ts.map +1 -0
  75. package/dist/react18/upgrade-to-react-18.js +39 -0
  76. package/dist/react18/upgrade-to-react-18.js.map +1 -0
  77. package/dist/react19/deprecated-react-types.d.ts +8 -0
  78. package/dist/react19/deprecated-react-types.d.ts.map +1 -0
  79. package/dist/react19/deprecated-react-types.js +135 -0
  80. package/dist/react19/deprecated-react-types.js.map +1 -0
  81. package/dist/react19/find-context-consumer.d.ts +9 -0
  82. package/dist/react19/find-context-consumer.d.ts.map +1 -0
  83. package/dist/react19/find-context-consumer.js +128 -0
  84. package/dist/react19/find-context-consumer.js.map +1 -0
  85. package/dist/react19/find-deprecated-reactdom-apis.d.ts +9 -0
  86. package/dist/react19/find-deprecated-reactdom-apis.d.ts.map +1 -0
  87. package/dist/react19/find-deprecated-reactdom-apis.js +132 -0
  88. package/dist/react19/find-deprecated-reactdom-apis.js.map +1 -0
  89. package/dist/react19/find-element-ref.d.ts +9 -0
  90. package/dist/react19/find-element-ref.d.ts.map +1 -0
  91. package/dist/react19/find-element-ref.js +88 -0
  92. package/dist/react19/find-element-ref.js.map +1 -0
  93. package/dist/react19/find-legacy-context-api.d.ts +9 -0
  94. package/dist/react19/find-legacy-context-api.d.ts.map +1 -0
  95. package/dist/react19/find-legacy-context-api.js +163 -0
  96. package/dist/react19/find-legacy-context-api.js.map +1 -0
  97. package/dist/react19/no-implicit-ref-callback-return.d.ts +8 -0
  98. package/dist/react19/no-implicit-ref-callback-return.d.ts.map +1 -0
  99. package/dist/react19/no-implicit-ref-callback-return.js +107 -0
  100. package/dist/react19/no-implicit-ref-callback-return.js.map +1 -0
  101. package/dist/react19/remove-context-provider.d.ts +8 -0
  102. package/dist/react19/remove-context-provider.d.ts.map +1 -0
  103. package/dist/react19/remove-context-provider.js +59 -0
  104. package/dist/react19/remove-context-provider.js.map +1 -0
  105. package/dist/react19/remove-forward-ref.d.ts +8 -0
  106. package/dist/react19/remove-forward-ref.d.ts.map +1 -0
  107. package/dist/react19/remove-forward-ref.js +73 -0
  108. package/dist/react19/remove-forward-ref.js.map +1 -0
  109. package/dist/react19/remove-prop-types.d.ts +8 -0
  110. package/dist/react19/remove-prop-types.d.ts.map +1 -0
  111. package/dist/react19/remove-prop-types.js +76 -0
  112. package/dist/react19/remove-prop-types.js.map +1 -0
  113. package/dist/react19/remove-react-fc.d.ts +8 -0
  114. package/dist/react19/remove-react-fc.d.ts.map +1 -0
  115. package/dist/react19/remove-react-fc.js +149 -0
  116. package/dist/react19/remove-react-fc.js.map +1 -0
  117. package/dist/react19/replace-act-import.d.ts +9 -0
  118. package/dist/react19/replace-act-import.d.ts.map +1 -0
  119. package/dist/react19/replace-act-import.js +34 -0
  120. package/dist/react19/replace-act-import.js.map +1 -0
  121. package/dist/react19/replace-default-props.d.ts +8 -0
  122. package/dist/react19/replace-default-props.d.ts.map +1 -0
  123. package/dist/react19/replace-default-props.js +195 -0
  124. package/dist/react19/replace-default-props.js.map +1 -0
  125. package/dist/react19/replace-react-shallow-renderer.d.ts +8 -0
  126. package/dist/react19/replace-react-shallow-renderer.d.ts.map +1 -0
  127. package/dist/react19/replace-react-shallow-renderer.js +69 -0
  128. package/dist/react19/replace-react-shallow-renderer.js.map +1 -0
  129. package/dist/react19/replace-reactdom-hydrate.d.ts +8 -0
  130. package/dist/react19/replace-reactdom-hydrate.d.ts.map +1 -0
  131. package/dist/react19/replace-reactdom-hydrate.js +55 -0
  132. package/dist/react19/replace-reactdom-hydrate.js.map +1 -0
  133. package/dist/react19/replace-string-ref.d.ts +8 -0
  134. package/dist/react19/replace-string-ref.d.ts.map +1 -0
  135. package/dist/react19/replace-string-ref.js +75 -0
  136. package/dist/react19/replace-string-ref.js.map +1 -0
  137. package/dist/react19/replace-use-form-state.d.ts +8 -0
  138. package/dist/react19/replace-use-form-state.d.ts.map +1 -0
  139. package/dist/react19/replace-use-form-state.js +54 -0
  140. package/dist/react19/replace-use-form-state.js.map +1 -0
  141. package/dist/react19/upgrade-to-react-19.d.ts +8 -0
  142. package/dist/react19/upgrade-to-react-19.d.ts.map +1 -0
  143. package/dist/react19/upgrade-to-react-19.js +59 -0
  144. package/dist/react19/upgrade-to-react-19.js.map +1 -0
  145. package/dist/react19/use-context-hook.d.ts +8 -0
  146. package/dist/react19/use-context-hook.d.ts.map +1 -0
  147. package/dist/react19/use-context-hook.js +54 -0
  148. package/dist/react19/use-context-hook.js.map +1 -0
  149. package/dist/react19/use-ref-required-initial.d.ts +8 -0
  150. package/dist/react19/use-ref-required-initial.d.ts.map +1 -0
  151. package/dist/react19/use-ref-required-initial.js +74 -0
  152. package/dist/react19/use-ref-required-initial.js.map +1 -0
  153. package/dist/refactoring/class-to-functional.d.ts +8 -0
  154. package/dist/refactoring/class-to-functional.d.ts.map +1 -0
  155. package/dist/refactoring/class-to-functional.js +205 -0
  156. package/dist/refactoring/class-to-functional.js.map +1 -0
  157. package/dist/refactoring/create-class-to-es6.d.ts +8 -0
  158. package/dist/refactoring/create-class-to-es6.d.ts.map +1 -0
  159. package/dist/refactoring/create-class-to-es6.js +289 -0
  160. package/dist/refactoring/create-class-to-es6.js.map +1 -0
  161. package/dist/refactoring/create-element-to-jsx.d.ts +8 -0
  162. package/dist/refactoring/create-element-to-jsx.d.ts.map +1 -0
  163. package/dist/refactoring/create-element-to-jsx.js +167 -0
  164. package/dist/refactoring/create-element-to-jsx.js.map +1 -0
  165. package/dist/refactoring/manual-bind-to-arrow.d.ts +8 -0
  166. package/dist/refactoring/manual-bind-to-arrow.d.ts.map +1 -0
  167. package/dist/refactoring/manual-bind-to-arrow.js +134 -0
  168. package/dist/refactoring/manual-bind-to-arrow.js.map +1 -0
  169. package/dist/refactoring/pure-render-mixin.d.ts +8 -0
  170. package/dist/refactoring/pure-render-mixin.d.ts.map +1 -0
  171. package/dist/refactoring/pure-render-mixin.js +253 -0
  172. package/dist/refactoring/pure-render-mixin.js.map +1 -0
  173. package/dist/refactoring/sort-comp.d.ts +8 -0
  174. package/dist/refactoring/sort-comp.d.ts.map +1 -0
  175. package/dist/refactoring/sort-comp.js +128 -0
  176. package/dist/refactoring/sort-comp.js.map +1 -0
  177. package/dist/search/find-hook-usage.d.ts +9 -0
  178. package/dist/search/find-hook-usage.d.ts.map +1 -0
  179. package/dist/search/find-hook-usage.js +262 -0
  180. package/dist/search/find-hook-usage.js.map +1 -0
  181. package/dist/search/find-prop-usage.d.ts +15 -0
  182. package/dist/search/find-prop-usage.d.ts.map +1 -0
  183. package/dist/search/find-prop-usage.js +177 -0
  184. package/dist/search/find-prop-usage.js.map +1 -0
  185. package/dist/search/find-react-component.d.ts +15 -0
  186. package/dist/search/find-react-component.d.ts.map +1 -0
  187. package/dist/search/find-react-component.js +260 -0
  188. package/dist/search/find-react-component.js.map +1 -0
  189. package/dist/search/find-server-rendering-usage.d.ts +9 -0
  190. package/dist/search/find-server-rendering-usage.d.ts.map +1 -0
  191. package/dist/search/find-server-rendering-usage.js +131 -0
  192. package/dist/search/find-server-rendering-usage.js.map +1 -0
  193. package/dist/simplify-object-pattern-property.d.ts +8 -0
  194. package/dist/simplify-object-pattern-property.d.ts.map +1 -0
  195. package/dist/simplify-object-pattern-property.js +59 -0
  196. package/dist/simplify-object-pattern-property.js.map +1 -0
  197. package/dist/simplify-react-imports.d.ts +8 -0
  198. package/dist/simplify-react-imports.d.ts.map +1 -0
  199. package/dist/simplify-react-imports.js +199 -0
  200. package/dist/simplify-react-imports.js.map +1 -0
  201. package/package.json +39 -0
  202. package/src/index.ts +149 -0
  203. package/src/migration/change-component-prop-value.ts +268 -0
  204. package/src/react-native/view-prop-types.ts +63 -0
  205. package/src/react16/error-boundaries.ts +46 -0
  206. package/src/react16/find-dom-node.ts +55 -0
  207. package/src/react16/react-dom-factories.ts +99 -0
  208. package/src/react16/react-prop-types.ts +71 -0
  209. package/src/react16/react-to-react-dom.ts +104 -0
  210. package/src/react16/replace-create-factory.ts +96 -0
  211. package/src/react16/upgrade-to-react-16.ts +37 -0
  212. package/src/react17/remove-event-persist.ts +121 -0
  213. package/src/react17/rename-unsafe-lifecycles.ts +57 -0
  214. package/src/react17/update-react-imports.ts +50 -0
  215. package/src/react17/upgrade-to-react-17.ts +30 -0
  216. package/src/react18/remove-unstable-batched-updates.ts +192 -0
  217. package/src/react18/replace-reactdom-render.ts +68 -0
  218. package/src/react18/replace-render-callback.ts +66 -0
  219. package/src/react18/replace-unmount-component-at-node.ts +66 -0
  220. package/src/react18/upgrade-to-react-18.ts +33 -0
  221. package/src/react19/deprecated-react-types.ts +120 -0
  222. package/src/react19/find-context-consumer.ts +127 -0
  223. package/src/react19/find-deprecated-reactdom-apis.ts +125 -0
  224. package/src/react19/find-element-ref.ts +86 -0
  225. package/src/react19/find-legacy-context-api.ts +157 -0
  226. package/src/react19/no-implicit-ref-callback-return.ts +123 -0
  227. package/src/react19/remove-context-provider.ts +87 -0
  228. package/src/react19/remove-forward-ref.ts +69 -0
  229. package/src/react19/remove-prop-types.ts +86 -0
  230. package/src/react19/remove-react-fc.ts +247 -0
  231. package/src/react19/replace-act-import.ts +36 -0
  232. package/src/react19/replace-default-props.ts +220 -0
  233. package/src/react19/replace-react-shallow-renderer.ts +75 -0
  234. package/src/react19/replace-reactdom-hydrate.ts +67 -0
  235. package/src/react19/replace-string-ref.ts +89 -0
  236. package/src/react19/replace-use-form-state.ts +66 -0
  237. package/src/react19/upgrade-to-react-19.ts +66 -0
  238. package/src/react19/use-context-hook.ts +67 -0
  239. package/src/react19/use-ref-required-initial.ts +75 -0
  240. package/src/refactoring/class-to-functional.ts +229 -0
  241. package/src/refactoring/create-class-to-es6.ts +309 -0
  242. package/src/refactoring/create-element-to-jsx.ts +200 -0
  243. package/src/refactoring/manual-bind-to-arrow.ts +139 -0
  244. package/src/refactoring/pure-render-mixin.ts +346 -0
  245. package/src/refactoring/sort-comp.ts +135 -0
  246. package/src/search/find-hook-usage.ts +226 -0
  247. package/src/search/find-prop-usage.ts +176 -0
  248. package/src/search/find-react-component.ts +254 -0
  249. package/src/search/find-server-rendering-usage.ts +120 -0
  250. package/src/simplify-object-pattern-property.ts +71 -0
  251. 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
+ }