@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,33 @@
1
+ import {Recipe} from "@openrewrite/rewrite";
2
+ import {UpgradeToReact17} from "../react17/upgrade-to-react-17";
3
+ import {ReplaceReactDomRender} from "./replace-reactdom-render";
4
+ import {RemoveUnstableBatchedUpdates} from "./remove-unstable-batched-updates";
5
+ import {ReplaceUnmountComponentAtNode} from "./replace-unmount-component-at-node";
6
+ import {ReplaceRenderCallback} from "./replace-render-callback";
7
+
8
+ /**
9
+ * Composite recipe that applies all React 18 migration recipes.
10
+ *
11
+ * First applies all React 17 migrations, then:
12
+ * - Replace `ReactDOM.render()` with `createRoot().render()`
13
+ * - Remove `unstable_batchedUpdates` wrappers (React 18 auto-batches)
14
+ * - Replace `unmountComponentAtNode` with `createRoot().unmount()`
15
+ * - Remove render callback (3rd argument to `ReactDOM.render`)
16
+ *
17
+ * @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide
18
+ */
19
+ export class UpgradeToReact18 extends Recipe {
20
+ readonly name = "org.openrewrite.react.migrate.upgrade-to-react-18";
21
+ readonly displayName = "Upgrade to React 18";
22
+ readonly description = "Migrate deprecated APIs for React 18 compatibility. Includes all React 16 and 17 migrations plus the createRoot API migration, removal of unstable_batchedUpdates, unmountComponentAtNode replacement, and render callback removal.";
23
+
24
+ async recipeList(): Promise<Recipe[]> {
25
+ return [
26
+ new UpgradeToReact17(),
27
+ new ReplaceReactDomRender(),
28
+ new RemoveUnstableBatchedUpdates(),
29
+ new ReplaceUnmountComponentAtNode(),
30
+ new ReplaceRenderCallback(),
31
+ ];
32
+ }
33
+ }
@@ -0,0 +1,120 @@
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
+ const typeRenames: Record<string, string> = {
7
+ 'SFC': 'FC',
8
+ 'StatelessComponent': 'FunctionComponent',
9
+ 'VFC': 'FC',
10
+ 'VoidFunctionComponent': 'FunctionComponent',
11
+ };
12
+
13
+ /**
14
+ * Replaces deprecated React TypeScript types with their modern equivalents.
15
+ *
16
+ * In React 19, several TypeScript types have been removed:
17
+ * - `SFC` → `FC`
18
+ * - `StatelessComponent` → `FunctionComponent`
19
+ * - `VFC` → `FC`
20
+ * - `VoidFunctionComponent` → `FunctionComponent`
21
+ *
22
+ * @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-deprecated-typescript-types
23
+ */
24
+ export class DeprecatedReactTypes extends Recipe {
25
+ readonly name = "org.openrewrite.react.19.deprecated-react-types";
26
+ readonly displayName: string = "Replace deprecated React types";
27
+ readonly description: string = "Replaces deprecated React TypeScript types (`SFC`, `StatelessComponent`, `VFC`, `VoidFunctionComponent`) with their modern equivalents.";
28
+
29
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
30
+ return new class extends JavaScriptVisitor<ExecutionContext> {
31
+ private deprecatedNames = new Set<string>();
32
+
33
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
34
+ this.deprecatedNames.clear();
35
+
36
+ // First pass: scan imports to find deprecated React types
37
+ for (const stmt of cu.statements) {
38
+ const s = stmt.element;
39
+ if (!s || s.kind !== JS.Kind.Import) continue;
40
+
41
+ const imp = s as JS.Import;
42
+ const modulePath = this.getModuleSpecifier(imp);
43
+ if (modulePath !== 'react') continue;
44
+
45
+ // Check named imports for deprecated types
46
+ if (imp.importClause?.namedBindings) {
47
+ const namedImports = imp.importClause.namedBindings as any;
48
+ const jContainer = namedImports.elements;
49
+ const elements = jContainer?.elements ?? [];
50
+ for (const specPadded of elements) {
51
+ const spec = specPadded.element ?? specPadded;
52
+ if (spec.kind === JS.Kind.ImportSpecifier) {
53
+ const importedName = this.getImportedName(spec as JS.ImportSpecifier);
54
+ if (importedName && typeRenames[importedName]) {
55
+ this.deprecatedNames.add(importedName);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ if (this.deprecatedNames.size === 0) {
63
+ return cu;
64
+ }
65
+
66
+ return super.visitJsCompilationUnit(cu, ctx);
67
+ }
68
+
69
+ override async visitIdentifier(identifier: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
70
+ const id = await super.visitIdentifier(identifier, ctx) as J.Identifier;
71
+ return this.renameIfDeprecated(id);
72
+ }
73
+
74
+ override async visitParameterizedType(type: J.ParameterizedType, ctx: ExecutionContext): Promise<J | undefined> {
75
+ let pt = await super.visitParameterizedType(type, ctx) as J.ParameterizedType;
76
+ if (isIdentifier(pt.class)) {
77
+ const renamed = this.renameIfDeprecated(pt.class);
78
+ if (renamed !== pt.class) {
79
+ return create(pt, draft => {
80
+ (draft as any).class = renamed;
81
+ });
82
+ }
83
+ }
84
+ return pt;
85
+ }
86
+
87
+ private renameIfDeprecated(id: J.Identifier): J.Identifier {
88
+ const name = id.simpleName;
89
+ if (this.deprecatedNames.has(name) && typeRenames[name]) {
90
+ return create(id, draft => {
91
+ draft.simpleName = typeRenames[name];
92
+ });
93
+ }
94
+ return id;
95
+ }
96
+
97
+ private getModuleSpecifier(imp: JS.Import): string {
98
+ const modSpec = (imp.moduleSpecifier as any).element ?? imp.moduleSpecifier;
99
+ if (modSpec.kind === J.Kind.Literal) {
100
+ return (modSpec as J.Literal).value as string;
101
+ }
102
+ return '';
103
+ }
104
+
105
+ private getImportedName(spec: JS.ImportSpecifier): string | undefined {
106
+ const specifier = spec.specifier;
107
+ if (specifier.kind === JS.Kind.Alias) {
108
+ const alias = specifier as JS.Alias;
109
+ const propName = alias.propertyName.element;
110
+ if (isIdentifier(propName)) {
111
+ return propName.simpleName;
112
+ }
113
+ } else if (isIdentifier(specifier)) {
114
+ return specifier.simpleName;
115
+ }
116
+ return undefined;
117
+ }
118
+ }();
119
+ }
120
+ }
@@ -0,0 +1,127 @@
1
+ import {Column, DataTable, ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS, JSX} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ class ContextConsumerRow {
6
+ @Column({displayName: "File path", description: "The file path where Context.Consumer is used"})
7
+ filePath!: string;
8
+
9
+ @Column({displayName: "Context name", description: "The name of the Context being consumed"})
10
+ contextName!: string;
11
+
12
+ @Column({displayName: "Component name", description: "The enclosing component or function name"})
13
+ componentName!: string;
14
+ }
15
+
16
+ /**
17
+ * Finds usage of the deprecated `<Context.Consumer>` pattern.
18
+ *
19
+ * In React 19, `Context.Consumer` is deprecated in favor of the
20
+ * `use()` hook for reading context in function components.
21
+ *
22
+ * Detects:
23
+ * - `<ThemeContext.Consumer>` JSX elements
24
+ * - `<SomeContext.Consumer>` with any context name
25
+ *
26
+ * Before:
27
+ * ```tsx
28
+ * <ThemeContext.Consumer>
29
+ * {theme => <div className={theme}>Hello</div>}
30
+ * </ThemeContext.Consumer>
31
+ * ```
32
+ *
33
+ * Recommended replacement:
34
+ * ```tsx
35
+ * import { use } from 'react';
36
+ * const theme = use(ThemeContext);
37
+ * return <div className={theme}>Hello</div>;
38
+ * ```
39
+ *
40
+ * @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#context-as-a-provider
41
+ */
42
+ export class FindContextConsumer extends Recipe {
43
+ readonly name = "org.openrewrite.react.19.find-context-consumer";
44
+ readonly displayName = "Find `Context.Consumer` usage";
45
+ readonly description = "Finds usage of the deprecated `<Context.Consumer>` pattern. In React 19, use the `use()` hook instead.";
46
+
47
+ private dataTable = new DataTable<ContextConsumerRow>(
48
+ "org.openrewrite.react.19.ContextConsumerTable",
49
+ "Context.Consumer usages",
50
+ "Table of all Context.Consumer usages found in the codebase.",
51
+ ContextConsumerRow
52
+ );
53
+
54
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
55
+ const dataTable = this.dataTable;
56
+
57
+ return new class extends JavaScriptVisitor<ExecutionContext> {
58
+ private filePath = "";
59
+ private componentStack: string[] = [];
60
+
61
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
62
+ this.filePath = cu.sourcePath ?? "";
63
+ this.componentStack = [];
64
+ return super.visitJsCompilationUnit(cu, ctx);
65
+ }
66
+
67
+ // Track function/component names
68
+ override async visitMethodDeclaration(method: J.MethodDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
69
+ const name = method.name?.simpleName ?? "<anonymous>";
70
+ this.componentStack.push(name);
71
+ const result = await super.visitMethodDeclaration(method, ctx);
72
+ this.componentStack.pop();
73
+ return result;
74
+ }
75
+
76
+ override async visitVariableDeclarations(vd: J.VariableDeclarations, ctx: ExecutionContext): Promise<J | undefined> {
77
+ const namedVar = vd.variables?.[0]?.element;
78
+ if (namedVar) {
79
+ const name = (namedVar.name as J.Identifier)?.simpleName;
80
+ if (name) {
81
+ this.componentStack.push(name);
82
+ const result = await super.visitVariableDeclarations(vd, ctx);
83
+ this.componentStack.pop();
84
+ return result;
85
+ }
86
+ }
87
+ return super.visitVariableDeclarations(vd, ctx);
88
+ }
89
+
90
+ override async visitJsxTag(tag: JSX.Tag, ctx: ExecutionContext): Promise<J | undefined> {
91
+ let t = await super.visitJsxTag(tag, ctx) as JSX.Tag;
92
+
93
+ const openName = t.openName.element;
94
+
95
+ // Check if the opening tag is a FieldAccess like Something.Consumer
96
+ if (openName.kind !== J.Kind.FieldAccess) {
97
+ return t;
98
+ }
99
+
100
+ const fieldAccess = openName as J.FieldAccess;
101
+ const propertyName = fieldAccess.name.element.simpleName;
102
+
103
+ if (propertyName !== 'Consumer') {
104
+ return t;
105
+ }
106
+
107
+ // Get the context name
108
+ let contextName = "<unknown>";
109
+ if (isIdentifier(fieldAccess.target)) {
110
+ contextName = (fieldAccess.target as J.Identifier).simpleName;
111
+ }
112
+
113
+ const componentName = this.componentStack.length > 0
114
+ ? this.componentStack[this.componentStack.length - 1]
115
+ : "<module>";
116
+
117
+ dataTable.insertRow(ctx, {
118
+ filePath: this.filePath,
119
+ contextName,
120
+ componentName
121
+ });
122
+
123
+ return foundSearchResult(t, `Deprecated: ${contextName}.Consumer — use the use() hook instead`);
124
+ }
125
+ }();
126
+ }
127
+ }
@@ -0,0 +1,125 @@
1
+ import {Column, DataTable, ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ class DeprecatedReactDomRow {
6
+ @Column({displayName: "File path", description: "The file path where the deprecated API is used"})
7
+ filePath!: string;
8
+
9
+ @Column({displayName: "API name", description: "The name of the deprecated ReactDOM API"})
10
+ apiName!: string;
11
+
12
+ @Column({displayName: "Replacement", description: "The recommended replacement"})
13
+ replacement!: string;
14
+ }
15
+
16
+ const DEPRECATED_APIS: Record<string, string> = {
17
+ 'findDOMNode': 'Use refs instead of ReactDOM.findDOMNode()',
18
+ 'unmountComponentAtNode': 'Use root.unmount() from createRoot() instead',
19
+ 'createFactory': 'Use JSX or React.createElement() instead',
20
+ 'renderToNodeStream': 'Use renderToPipeableStream instead',
21
+ 'renderToStaticNodeStream': 'Use renderToReadableStream or renderToPipeableStream instead',
22
+ };
23
+
24
+ /**
25
+ * Finds usage of deprecated or removed ReactDOM APIs in React 19.
26
+ *
27
+ * Detects:
28
+ * - `ReactDOM.findDOMNode(component)` — removed, use refs instead
29
+ * - `ReactDOM.unmountComponentAtNode(container)` — removed, use `root.unmount()`
30
+ * - `React.createFactory(type)` — removed, use JSX or `createElement`
31
+ * - `ReactDOMServer.renderToNodeStream()` — removed, use `renderToPipeableStream`
32
+ * - `ReactDOMServer.renderToStaticNodeStream()` — removed
33
+ *
34
+ * Also detects named imports of these functions.
35
+ *
36
+ * @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-deprecated-react-apis
37
+ */
38
+ export class FindDeprecatedReactDomAPIs extends Recipe {
39
+ readonly name = "org.openrewrite.react.19.find-deprecated-reactdom-apis";
40
+ readonly displayName = "Find deprecated ReactDOM APIs";
41
+ readonly description = "Finds usage of deprecated or removed ReactDOM APIs (`findDOMNode`, `unmountComponentAtNode`, `createFactory`, `renderToNodeStream`) that were removed in React 19.";
42
+
43
+ private dataTable = new DataTable<DeprecatedReactDomRow>(
44
+ "org.openrewrite.react.19.DeprecatedReactDomAPITable",
45
+ "Deprecated ReactDOM API usages",
46
+ "Table of all deprecated ReactDOM API usages found in the codebase.",
47
+ DeprecatedReactDomRow
48
+ );
49
+
50
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
51
+ const dataTable = this.dataTable;
52
+
53
+ return new class extends JavaScriptVisitor<ExecutionContext> {
54
+ private filePath = "";
55
+ private importedDeprecatedApis = new Set<string>();
56
+
57
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
58
+ this.filePath = cu.sourcePath ?? "";
59
+ this.importedDeprecatedApis.clear();
60
+ return super.visitJsCompilationUnit(cu, ctx);
61
+ }
62
+
63
+ // Detect imports of deprecated APIs
64
+ override async visitImportSpecifier(spec: JS.ImportSpecifier, ctx: ExecutionContext): Promise<J | undefined> {
65
+ const specifier = spec.specifier;
66
+
67
+ let importedName: string | undefined;
68
+ if (specifier.kind === JS.Kind.Alias) {
69
+ const alias = specifier as JS.Alias;
70
+ const propName = alias.propertyName.element;
71
+ if (isIdentifier(propName)) {
72
+ importedName = propName.simpleName;
73
+ }
74
+ } else if (isIdentifier(specifier)) {
75
+ importedName = specifier.simpleName;
76
+ }
77
+
78
+ if (importedName && DEPRECATED_APIS[importedName]) {
79
+ this.importedDeprecatedApis.add(importedName);
80
+ dataTable.insertRow(ctx, {
81
+ filePath: this.filePath,
82
+ apiName: importedName,
83
+ replacement: DEPRECATED_APIS[importedName]
84
+ });
85
+ return foundSearchResult(spec, `Deprecated API: ${importedName}`);
86
+ }
87
+
88
+ return super.visitImportSpecifier(spec, ctx);
89
+ }
90
+
91
+ // Detect method calls: ReactDOM.findDOMNode(), ReactDOM.unmountComponentAtNode(), etc.
92
+ override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
93
+ let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
94
+
95
+ const methodName = m.name.simpleName;
96
+
97
+ if (!DEPRECATED_APIS[methodName]) return m;
98
+
99
+ // Direct call from named import: findDOMNode(...)
100
+ if (!m.select && this.importedDeprecatedApis.has(methodName)) {
101
+ // Already marked at import level, just mark the call too
102
+ return foundSearchResult(m, `Deprecated API: ${methodName}`);
103
+ }
104
+
105
+ // Namespace call: ReactDOM.findDOMNode(...), React.createFactory(...)
106
+ if (m.select) {
107
+ const select = (m.select as any).element ?? m.select;
108
+ if (isIdentifier(select)) {
109
+ const ns = select.simpleName;
110
+ if (ns === 'ReactDOM' || ns === 'React' || ns === 'ReactDOMServer') {
111
+ dataTable.insertRow(ctx, {
112
+ filePath: this.filePath,
113
+ apiName: `${ns}.${methodName}`,
114
+ replacement: DEPRECATED_APIS[methodName]
115
+ });
116
+ return foundSearchResult(m, `Deprecated API: ${ns}.${methodName}`);
117
+ }
118
+ }
119
+ }
120
+
121
+ return m;
122
+ }
123
+ }();
124
+ }
125
+ }
@@ -0,0 +1,86 @@
1
+ import {Column, DataTable, ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ class ElementRefRow {
6
+ @Column({displayName: "File path", description: "The file path where element.ref is accessed"})
7
+ filePath!: string;
8
+
9
+ @Column({displayName: "Expression", description: "The full expression accessing .ref"})
10
+ expression!: string;
11
+ }
12
+
13
+ /**
14
+ * Finds direct access of `element.ref` on React elements.
15
+ *
16
+ * In React 19, `element.ref` is deprecated and will be removed in a future version.
17
+ * Use `element.props.ref` instead.
18
+ *
19
+ * Before:
20
+ * ```tsx
21
+ * const ref = element.ref;
22
+ * ```
23
+ *
24
+ * After (manual fix):
25
+ * ```tsx
26
+ * const ref = element.props.ref;
27
+ * ```
28
+ *
29
+ * @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide
30
+ */
31
+ export class FindElementRef extends Recipe {
32
+ readonly name = "org.openrewrite.react.19.find-element-ref";
33
+ readonly displayName = "Find `element.ref` access";
34
+ readonly description = "Finds direct access of `element.ref` on React elements. In React 19, `element.ref` is deprecated; use `element.props.ref` instead.";
35
+
36
+ private dataTable = new DataTable<ElementRefRow>(
37
+ "org.openrewrite.react.19.ElementRefTable",
38
+ "Element ref access usages",
39
+ "Table of all element.ref access usages found in the codebase.",
40
+ ElementRefRow
41
+ );
42
+
43
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
44
+ const dataTable = this.dataTable;
45
+
46
+ return new class extends JavaScriptVisitor<ExecutionContext> {
47
+ private filePath = "";
48
+
49
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
50
+ this.filePath = cu.sourcePath ?? "";
51
+ return super.visitJsCompilationUnit(cu, ctx);
52
+ }
53
+
54
+ override async visitFieldAccess(fieldAccess: J.FieldAccess, ctx: ExecutionContext): Promise<J | undefined> {
55
+ let fa = await super.visitFieldAccess(fieldAccess, ctx) as J.FieldAccess;
56
+
57
+ if (fa.name.element.simpleName !== 'ref') return fa;
58
+
59
+ // Exclude this.refs (different issue - string refs)
60
+ if (isIdentifier(fa.target) && fa.target.simpleName === 'refs') return fa;
61
+
62
+ // Exclude props.ref (that's the recommended replacement)
63
+ if (isIdentifier(fa.target) && fa.target.simpleName === 'props') return fa;
64
+
65
+ // Exclude element.props.ref (FieldAccess target with .props name)
66
+ if (fa.target.kind === J.Kind.FieldAccess) {
67
+ const targetFa = fa.target as J.FieldAccess;
68
+ if (targetFa.name.element.simpleName === 'props') return fa;
69
+ }
70
+
71
+ // Exclude this.ref (not a React element pattern)
72
+ if (fa.target.kind === J.Kind.Identifier && (fa.target as J.Identifier).simpleName === 'this') return fa;
73
+
74
+ // This is element.ref access - mark it
75
+ const targetName = isIdentifier(fa.target) ? fa.target.simpleName : '(expression)';
76
+
77
+ dataTable.insertRow(ctx, {
78
+ filePath: this.filePath,
79
+ expression: `${targetName}.ref`
80
+ });
81
+
82
+ return foundSearchResult(fa, "element.ref is deprecated, use element.props.ref");
83
+ }
84
+ }();
85
+ }
86
+ }
@@ -0,0 +1,157 @@
1
+ import {Column, DataTable, ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ class LegacyContextRow {
6
+ @Column({displayName: "File path", description: "The file path where the legacy context API is used"})
7
+ filePath!: string;
8
+
9
+ @Column({displayName: "API type", description: "The type of legacy context API: contextTypes, childContextTypes, or getChildContext"})
10
+ apiType!: string;
11
+
12
+ @Column({displayName: "Component name", description: "The name of the component using the legacy context API"})
13
+ componentName!: string;
14
+ }
15
+
16
+ /**
17
+ * Finds usage of the legacy Context API that was removed in React 19.
18
+ *
19
+ * Detects:
20
+ * - `static contextTypes = { ... }` in class components
21
+ * - `Component.contextTypes = { ... }` static assignments
22
+ * - `static childContextTypes = { ... }` in class components
23
+ * - `Component.childContextTypes = { ... }` static assignments
24
+ * - `getChildContext()` method definitions in class components
25
+ *
26
+ * These should be migrated to `React.createContext()` with the
27
+ * `<Context.Provider>` / `useContext()` API.
28
+ *
29
+ * @see https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-legacy-context
30
+ */
31
+ export class FindLegacyContextAPI extends Recipe {
32
+ readonly name = "org.openrewrite.react.19.find-legacy-context-api";
33
+ readonly displayName = "Find legacy Context API usage";
34
+ readonly description = "Finds usage of the legacy Context API (`contextTypes`, `childContextTypes`, `getChildContext`) that was removed in React 19. These must be migrated to `React.createContext()`.";
35
+
36
+ private dataTable = new DataTable<LegacyContextRow>(
37
+ "org.openrewrite.react.19.LegacyContextAPITable",
38
+ "Legacy Context API usages",
39
+ "Table of all legacy Context API usages found in the codebase.",
40
+ LegacyContextRow
41
+ );
42
+
43
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
44
+ const dataTable = this.dataTable;
45
+ const LEGACY_CONTEXT_PROPS = new Set(['contextTypes', 'childContextTypes']);
46
+
47
+ return new class extends JavaScriptVisitor<ExecutionContext> {
48
+ private filePath = "";
49
+ private classStack: string[] = [];
50
+
51
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
52
+ this.filePath = cu.sourcePath ?? "";
53
+ this.classStack = [];
54
+
55
+ let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
56
+
57
+ // Check top-level static assignments: Component.contextTypes = { ... }
58
+ for (const paddedStmt of result.statements) {
59
+ const stmt = paddedStmt.element;
60
+ if (!stmt) continue;
61
+
62
+ let assignment: J.Assignment | undefined;
63
+
64
+ if (stmt.kind === JS.Kind.ExpressionStatement) {
65
+ const exprStmt = stmt as JS.ExpressionStatement;
66
+ if (exprStmt.expression.kind === J.Kind.Assignment) {
67
+ assignment = exprStmt.expression as J.Assignment;
68
+ }
69
+ } else if (stmt.kind === J.Kind.Assignment) {
70
+ assignment = stmt as J.Assignment;
71
+ }
72
+
73
+ if (!assignment) continue;
74
+ if (assignment.variable.kind !== J.Kind.FieldAccess) continue;
75
+
76
+ const fa = assignment.variable as J.FieldAccess;
77
+ const propName = fa.name.element.simpleName;
78
+
79
+ if (!LEGACY_CONTEXT_PROPS.has(propName)) continue;
80
+ if (!isIdentifier(fa.target)) continue;
81
+
82
+ const componentName = (fa.target as J.Identifier).simpleName;
83
+ dataTable.insertRow(ctx, {
84
+ filePath: this.filePath,
85
+ apiType: propName,
86
+ componentName
87
+ });
88
+ // Mark the assignment as a search result
89
+ result = {
90
+ ...result,
91
+ statements: result.statements.map(s => {
92
+ if (s === paddedStmt) {
93
+ return {
94
+ ...s,
95
+ element: foundSearchResult(stmt, `Legacy Context API: ${propName}`)
96
+ };
97
+ }
98
+ return s;
99
+ })
100
+ } as any;
101
+ }
102
+
103
+ return result;
104
+ }
105
+
106
+ override async visitClassDeclaration(classDecl: J.ClassDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
107
+ const name = classDecl.name?.simpleName ?? "<anonymous>";
108
+ this.classStack.push(name);
109
+ const result = await super.visitClassDeclaration(classDecl, ctx);
110
+ this.classStack.pop();
111
+ return result;
112
+ }
113
+
114
+ // Detect static contextTypes / childContextTypes in class bodies
115
+ override async visitVariableDeclarations(vd: J.VariableDeclarations, ctx: ExecutionContext): Promise<J | undefined> {
116
+ const result = await super.visitVariableDeclarations(vd, ctx) as J.VariableDeclarations;
117
+
118
+ if (this.classStack.length === 0) return result;
119
+
120
+ // Check for static contextTypes or childContextTypes
121
+ const namedVar = vd.variables?.[0]?.element;
122
+ if (!namedVar) return result;
123
+
124
+ const varName = (namedVar.name as J.Identifier)?.simpleName;
125
+ if (!varName || !LEGACY_CONTEXT_PROPS.has(varName)) return result;
126
+
127
+ const componentName = this.classStack[this.classStack.length - 1];
128
+ dataTable.insertRow(ctx, {
129
+ filePath: this.filePath,
130
+ apiType: varName,
131
+ componentName
132
+ });
133
+
134
+ return foundSearchResult(result, `Legacy Context API: ${varName}`);
135
+ }
136
+
137
+ // Detect getChildContext() method definitions
138
+ override async visitMethodDeclaration(method: J.MethodDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
139
+ const result = await super.visitMethodDeclaration(method, ctx) as J.MethodDeclaration;
140
+
141
+ if (this.classStack.length === 0) return result;
142
+
143
+ const methodName = method.name?.simpleName;
144
+ if (methodName !== 'getChildContext') return result;
145
+
146
+ const componentName = this.classStack[this.classStack.length - 1];
147
+ dataTable.insertRow(ctx, {
148
+ filePath: this.filePath,
149
+ apiType: 'getChildContext',
150
+ componentName
151
+ });
152
+
153
+ return foundSearchResult(result, "Legacy Context API: getChildContext");
154
+ }
155
+ }();
156
+ }
157
+ }