@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,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
+ }