@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,121 @@
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
+ * Removes `event.persist()` calls.
8
+ *
9
+ * React 17 removed event pooling, so calling `persist()` on
10
+ * synthetic events is no longer necessary.
11
+ *
12
+ * Before:
13
+ * ```tsx
14
+ * function handleChange(e) {
15
+ * e.persist();
16
+ * setState(e.target.value);
17
+ * }
18
+ * ```
19
+ *
20
+ * After:
21
+ * ```tsx
22
+ * function handleChange(e) {
23
+ * setState(e.target.value);
24
+ * }
25
+ * ```
26
+ *
27
+ * @see https://reactjs.org/blog/2020/10/20/react-v17.html#no-event-pooling
28
+ */
29
+ export class RemoveEventPersist extends Recipe {
30
+ readonly name = "org.openrewrite.react.17.remove-event-persist";
31
+ readonly displayName: string = "Remove `event.persist()` calls";
32
+ readonly description: string = "Removes `event.persist()` calls. React 17 removed event pooling, making persist() unnecessary.";
33
+
34
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
35
+ return new class extends JavaScriptVisitor<ExecutionContext> {
36
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
37
+ let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
38
+
39
+ const statementsToRemove = new Set<string>();
40
+
41
+ for (const stmt of result.statements) {
42
+ const s = stmt.element;
43
+ if (!s) continue;
44
+ if (this.isPersistCall(s)) {
45
+ statementsToRemove.add((s as any).id);
46
+ }
47
+ }
48
+
49
+ if (statementsToRemove.size === 0) return result;
50
+
51
+ const filteredStatements = result.statements.filter(stmt => {
52
+ const s = stmt.element;
53
+ return !s || !statementsToRemove.has((s as any).id);
54
+ });
55
+
56
+ return create(result, draft => {
57
+ draft.statements = filteredStatements as any;
58
+ });
59
+ }
60
+
61
+ override async visitBlock(block: J.Block, ctx: ExecutionContext): Promise<J | undefined> {
62
+ let b = await super.visitBlock(block, ctx) as J.Block;
63
+
64
+ const statementsToRemove = new Set<string>();
65
+
66
+ for (const stmt of b.statements) {
67
+ const s = stmt.element;
68
+ if (!s) continue;
69
+ if (this.isPersistCall(s)) {
70
+ statementsToRemove.add((s as any).id);
71
+ }
72
+ }
73
+
74
+ if (statementsToRemove.size === 0) return b;
75
+
76
+ const filteredStatements = b.statements.filter(stmt => {
77
+ const s = stmt.element;
78
+ return !s || !statementsToRemove.has((s as any).id);
79
+ });
80
+
81
+ return create(b, draft => {
82
+ draft.statements = filteredStatements as any;
83
+ });
84
+ }
85
+
86
+ private isPersistCall(stmt: any): boolean {
87
+ let expr: any;
88
+
89
+ if (stmt.kind === JS.Kind.ExpressionStatement) {
90
+ expr = (stmt as JS.ExpressionStatement).expression;
91
+ } else if (stmt.kind === J.Kind.MethodInvocation) {
92
+ expr = stmt;
93
+ } else {
94
+ return false;
95
+ }
96
+
97
+ if (!expr || expr.kind !== J.Kind.MethodInvocation) return false;
98
+
99
+ const method = expr as J.MethodInvocation;
100
+
101
+ // Must be called as x.persist(), not just persist()
102
+ if (!method.select) return false;
103
+
104
+ // Method name must be "persist"
105
+ if (method.name.simpleName !== 'persist') return false;
106
+
107
+ // Must have no real arguments (may contain J.Empty placeholder)
108
+ if (method.arguments) {
109
+ const args = method.arguments.elements;
110
+ const hasRealArgs = args.some((el: any) => {
111
+ const element = el.element ?? el;
112
+ return element.kind !== J.Kind.Empty;
113
+ });
114
+ if (hasRealArgs) return false;
115
+ }
116
+
117
+ return true;
118
+ }
119
+ }();
120
+ }
121
+ }
@@ -0,0 +1,57 @@
1
+ import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
3
+ import {J} from "@openrewrite/rewrite/java";
4
+
5
+ const UNSAFE_LIFECYCLE_MAP: Record<string, string> = {
6
+ 'componentWillMount': 'UNSAFE_componentWillMount',
7
+ 'componentWillReceiveProps': 'UNSAFE_componentWillReceiveProps',
8
+ 'componentWillUpdate': 'UNSAFE_componentWillUpdate',
9
+ };
10
+
11
+ /**
12
+ * Adds `UNSAFE_` prefix to deprecated lifecycle methods.
13
+ *
14
+ * React 16.3 deprecated `componentWillMount`, `componentWillReceiveProps`,
15
+ * and `componentWillUpdate`. React 17 requires the UNSAFE_ prefix.
16
+ *
17
+ * Before:
18
+ * ```tsx
19
+ * class MyComponent extends React.Component {
20
+ * componentWillMount() { ... }
21
+ * componentWillReceiveProps(nextProps) { ... }
22
+ * componentWillUpdate(nextProps, nextState) { ... }
23
+ * }
24
+ * ```
25
+ *
26
+ * After:
27
+ * ```tsx
28
+ * class MyComponent extends React.Component {
29
+ * UNSAFE_componentWillMount() { ... }
30
+ * UNSAFE_componentWillReceiveProps(nextProps) { ... }
31
+ * UNSAFE_componentWillUpdate(nextProps, nextState) { ... }
32
+ * }
33
+ * ```
34
+ */
35
+ export class RenameUnsafeLifecycles extends Recipe {
36
+ readonly name = "org.openrewrite.react.17.rename-unsafe-lifecycles";
37
+ readonly displayName: string = "Add `UNSAFE_` prefix to deprecated lifecycle methods";
38
+ readonly description: string = "Renames `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate` to their UNSAFE_ prefixed versions.";
39
+
40
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
41
+ return new class extends JavaScriptVisitor<ExecutionContext> {
42
+ override async visitIdentifier(ident: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
43
+ let id = await super.visitIdentifier(ident, ctx) as J.Identifier;
44
+
45
+ const newName = UNSAFE_LIFECYCLE_MAP[id.simpleName];
46
+ if (newName) {
47
+ return {
48
+ ...id,
49
+ simpleName: newName
50
+ } as J.Identifier;
51
+ }
52
+
53
+ return id;
54
+ }
55
+ }();
56
+ }
57
+ }
@@ -0,0 +1,50 @@
1
+ import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
3
+ import {J} from "@openrewrite/rewrite/java";
4
+
5
+ /**
6
+ * Removes unnecessary `import React from 'react'` default imports.
7
+ *
8
+ * With React 17's new JSX transform, you no longer need to import React
9
+ * in every file that uses JSX. This recipe removes the default React
10
+ * import when it's only used for JSX (not for React.xxx API calls).
11
+ *
12
+ * Before:
13
+ * ```tsx
14
+ * import React from 'react';
15
+ *
16
+ * function App() {
17
+ * return <div>Hello</div>;
18
+ * }
19
+ * ```
20
+ *
21
+ * After:
22
+ * ```tsx
23
+ * function App() {
24
+ * return <div>Hello</div>;
25
+ * }
26
+ * ```
27
+ *
28
+ * Note: If `React` is used for API calls (e.g., `React.createElement`,
29
+ * `React.useRef`), the import is preserved.
30
+ *
31
+ * @see https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
32
+ */
33
+ export class UpdateReactImports extends Recipe {
34
+ readonly name = "org.openrewrite.react.17.update-react-imports";
35
+ readonly displayName: string = "Remove unnecessary React imports";
36
+ readonly description: string = "Removes the default `import React from 'react'` when React is only used for JSX, which is no longer necessary with the new JSX transform in React 17+.";
37
+
38
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
39
+ return new class extends JavaScriptVisitor<ExecutionContext> {
40
+ override async visitJsCompilationUnit(cu: any, ctx: ExecutionContext): Promise<J | undefined> {
41
+ let result = await super.visitJsCompilationUnit(cu, ctx);
42
+ // Schedule removal of the default React import.
43
+ // RemoveImport will only remove it if 'React' is not referenced
44
+ // anywhere outside of the import declaration itself.
45
+ maybeRemoveImport(this, "react", "default");
46
+ return result;
47
+ }
48
+ }();
49
+ }
50
+ }
@@ -0,0 +1,30 @@
1
+ import {Recipe} from "@openrewrite/rewrite";
2
+ import {UpgradeToReact16} from "../react16/upgrade-to-react-16";
3
+ import {RenameUnsafeLifecycles} from "./rename-unsafe-lifecycles";
4
+ import {UpdateReactImports} from "./update-react-imports";
5
+ import {RemoveEventPersist} from "./remove-event-persist";
6
+
7
+ /**
8
+ * Composite recipe that applies all React 17 migration recipes.
9
+ *
10
+ * First applies all React 16 migrations, then:
11
+ * - Add `UNSAFE_` prefix to deprecated lifecycle methods
12
+ * - Remove unnecessary `import React from 'react'`
13
+ * - Remove `e.persist()` calls (event pooling was removed)
14
+ *
15
+ * @see https://reactjs.org/blog/2020/10/20/react-v17.html
16
+ */
17
+ export class UpgradeToReact17 extends Recipe {
18
+ readonly name = "org.openrewrite.react.migrate.upgrade-to-react-17";
19
+ readonly displayName = "Upgrade to React 17";
20
+ readonly description = "Migrate deprecated APIs for React 17 compatibility. Includes all React 16 migrations plus lifecycle method prefixing, import cleanup, and event.persist() removal.";
21
+
22
+ async recipeList(): Promise<Recipe[]> {
23
+ return [
24
+ new UpgradeToReact16(),
25
+ new RenameUnsafeLifecycles(),
26
+ new UpdateReactImports(),
27
+ new RemoveEventPersist(),
28
+ ];
29
+ }
30
+ }
@@ -0,0 +1,192 @@
1
+ import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS, maybeRemoveImport} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+ import {create} from "mutative";
5
+
6
+ /**
7
+ * Removes `unstable_batchedUpdates` wrappers from `react-dom`.
8
+ *
9
+ * In React 18, all state updates are automatically batched,
10
+ * making `unstable_batchedUpdates` unnecessary. This recipe
11
+ * unwraps the callback body and removes the wrapping call.
12
+ *
13
+ * Before:
14
+ * ```tsx
15
+ * import { unstable_batchedUpdates } from 'react-dom';
16
+ *
17
+ * unstable_batchedUpdates(() => {
18
+ * setCount(1);
19
+ * setFlag(true);
20
+ * });
21
+ * ```
22
+ *
23
+ * After:
24
+ * ```tsx
25
+ * setCount(1);
26
+ * setFlag(true);
27
+ * ```
28
+ *
29
+ * @see https://react.dev/blog/2022/03/29/react-v18#new-feature-automatic-batching
30
+ */
31
+ export class RemoveUnstableBatchedUpdates extends Recipe {
32
+ readonly name = "org.openrewrite.react.18.remove-unstable-batched-updates";
33
+ readonly displayName = "Remove `unstable_batchedUpdates`";
34
+ readonly description = "Removes `unstable_batchedUpdates` wrappers from `react-dom`. React 18 automatically batches all state updates, making this function unnecessary.";
35
+
36
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
37
+ return new class extends JavaScriptVisitor<ExecutionContext> {
38
+ private changed = false;
39
+
40
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
41
+ this.changed = false;
42
+ let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
43
+
44
+ result = this.inlineTopLevelStatements(result);
45
+
46
+ if (this.changed) {
47
+ maybeRemoveImport(this, "react-dom", "unstable_batchedUpdates");
48
+ }
49
+
50
+ return result;
51
+ }
52
+
53
+ override async visitBlock(block: J.Block, ctx: ExecutionContext): Promise<J | undefined> {
54
+ let b = await super.visitBlock(block, ctx) as J.Block;
55
+ return this.inlineBlockStatements(b);
56
+ }
57
+
58
+ private inlineTopLevelStatements(cu: JS.CompilationUnit): JS.CompilationUnit {
59
+ const result = this.spliceStatements(cu.statements);
60
+ if (!result) return cu;
61
+ return create(cu, draft => {
62
+ draft.statements = result as any;
63
+ });
64
+ }
65
+
66
+ private inlineBlockStatements(block: J.Block): J.Block {
67
+ const result = this.spliceStatements(block.statements);
68
+ if (!result) return block;
69
+ return create(block, draft => {
70
+ draft.statements = result as any;
71
+ });
72
+ }
73
+
74
+ private spliceStatements(statements: any[]): any[] | null {
75
+ const newStatements: any[] = [];
76
+ let changed = false;
77
+
78
+ for (const paddedStmt of statements) {
79
+ const stmt = paddedStmt.element;
80
+ if (!stmt) {
81
+ newStatements.push(paddedStmt);
82
+ continue;
83
+ }
84
+
85
+ const bodyStatements = this.extractBatchedUpdatesBody(stmt);
86
+ if (bodyStatements !== null && bodyStatements.length > 0) {
87
+ // Compute indentation adjustment: replace the inner callback
88
+ // indentation with the outer statement's indentation
89
+ const outerPrefix = paddedStmt.element.prefix;
90
+ const outerIndent = this.getIndentation(outerPrefix);
91
+ const innerIndent = bodyStatements.length > 0
92
+ ? this.getIndentation(bodyStatements[0].element.prefix)
93
+ : outerIndent;
94
+
95
+ for (const bodyStmt of bodyStatements) {
96
+ const adjustedPrefix = this.adjustIndentation(
97
+ bodyStmt.element.prefix, innerIndent, outerIndent
98
+ );
99
+ newStatements.push({
100
+ ...bodyStmt,
101
+ element: {
102
+ ...bodyStmt.element,
103
+ prefix: adjustedPrefix
104
+ }
105
+ });
106
+ }
107
+ changed = true;
108
+ this.changed = true;
109
+ } else {
110
+ newStatements.push(paddedStmt);
111
+ }
112
+ }
113
+
114
+ return changed ? newStatements : null;
115
+ }
116
+
117
+ private getIndentation(prefix: any): string {
118
+ const ws: string = prefix?.whitespace ?? '';
119
+ // Get the indentation from the last line of the whitespace
120
+ const lastNewline = ws.lastIndexOf('\n');
121
+ return lastNewline >= 0 ? ws.substring(lastNewline + 1) : ws;
122
+ }
123
+
124
+ private adjustIndentation(prefix: any, fromIndent: string, toIndent: string): any {
125
+ if (fromIndent === toIndent) return prefix;
126
+ const ws: string = prefix?.whitespace ?? '';
127
+ const lastNewline = ws.lastIndexOf('\n');
128
+ if (lastNewline < 0) return {...prefix, whitespace: toIndent};
129
+ const currentIndent = ws.substring(lastNewline + 1);
130
+ // Replace the inner indentation with the outer one
131
+ const adjusted = currentIndent.startsWith(fromIndent)
132
+ ? toIndent + currentIndent.substring(fromIndent.length)
133
+ : toIndent;
134
+ return {...prefix, whitespace: ws.substring(0, lastNewline + 1) + adjusted};
135
+ }
136
+
137
+ private extractBatchedUpdatesBody(stmt: any): any[] | null {
138
+ let expr: any;
139
+
140
+ if (stmt.kind === JS.Kind.ExpressionStatement) {
141
+ expr = (stmt as JS.ExpressionStatement).expression;
142
+ } else if (stmt.kind === J.Kind.MethodInvocation) {
143
+ expr = stmt;
144
+ } else {
145
+ return null;
146
+ }
147
+
148
+ if (!expr || expr.kind !== J.Kind.MethodInvocation) return null;
149
+
150
+ const method = expr as J.MethodInvocation;
151
+
152
+ if (!this.isBatchedUpdatesCall(method)) return null;
153
+
154
+ // Get the callback argument
155
+ const args = method.arguments;
156
+ if (!args) return null;
157
+
158
+ const elements = args.elements;
159
+ if (!elements || elements.length === 0) return null;
160
+
161
+ const callback = elements[0].element;
162
+ if (!callback) return null;
163
+
164
+ // Arrow function with block body
165
+ if (callback.kind === JS.Kind.ArrowFunction) {
166
+ const arrow = callback as JS.ArrowFunction;
167
+ const body = arrow.lambda.body;
168
+ if (body && body.kind === J.Kind.Block) {
169
+ return (body as J.Block).statements;
170
+ }
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ private isBatchedUpdatesCall(method: J.MethodInvocation): boolean {
177
+ if (method.name.simpleName === 'unstable_batchedUpdates') {
178
+ // Direct call: unstable_batchedUpdates(...)
179
+ if (!method.select) return true;
180
+
181
+ // Namespace call: ReactDOM.unstable_batchedUpdates(...)
182
+ const select = (method.select as any).element ?? method.select;
183
+ if (isIdentifier(select) && select.simpleName === 'ReactDOM') {
184
+ return true;
185
+ }
186
+ }
187
+
188
+ return false;
189
+ }
190
+ }();
191
+ }
192
+ }
@@ -0,0 +1,68 @@
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.render()` with `createRoot().render()`.
15
+ *
16
+ * In React 18, `ReactDOM.render()` is deprecated in favor of
17
+ * `createRoot()` from `react-dom/client`.
18
+ *
19
+ * Before:
20
+ * ```tsx
21
+ * import ReactDOM from 'react-dom';
22
+ * ReactDOM.render(<App />, document.getElementById('root'));
23
+ * ```
24
+ *
25
+ * After:
26
+ * ```tsx
27
+ * import { createRoot } from 'react-dom/client';
28
+ * const root = createRoot(document.getElementById('root'));
29
+ * root.render(<App />);
30
+ * ```
31
+ *
32
+ * @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
33
+ */
34
+ export class ReplaceReactDomRender extends Recipe {
35
+ readonly name = "org.openrewrite.react.18.replace-reactdom-render";
36
+ readonly displayName: string = "Replace `ReactDOM.render` with `createRoot`";
37
+ readonly description: string = "Migrates from the legacy `ReactDOM.render()` API to the `createRoot()` API from `react-dom/client` introduced in React 18.";
38
+
39
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
40
+ const reactDomOptions = {
41
+ context: ['import { render } from "react-dom";'],
42
+ dependencies: {'@types/react-dom': '^17.0.0'}
43
+ };
44
+
45
+ const element = capture<Expression>();
46
+ const container = capture<Expression>();
47
+
48
+ const rule = rewrite(() => ({
49
+ before: pattern`render(${element}, ${container})`.configure(reactDomOptions),
50
+ after: template`createRoot(${container}).render(${element})`
51
+ }));
52
+
53
+ return new class extends JavaScriptVisitor<ExecutionContext> {
54
+ override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
55
+ let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
56
+
57
+ const result = await rule.tryOn(this.cursor, m!);
58
+ if (result) {
59
+ maybeRemoveImport(this, "react-dom", "render");
60
+ maybeAddImport(this, {module: "react-dom/client", member: "createRoot", onlyIfReferenced: false});
61
+ return result;
62
+ }
63
+
64
+ return m;
65
+ }
66
+ }();
67
+ }
68
+ }
@@ -0,0 +1,66 @@
1
+ import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
3
+ import {isIdentifier, J} from "@openrewrite/rewrite/java";
4
+ import {create} from "mutative";
5
+
6
+ /**
7
+ * Removes the third callback argument from `ReactDOM.render()` calls.
8
+ *
9
+ * In React 18, `ReactDOM.render()` is deprecated in favor of `createRoot()`,
10
+ * which does not support a callback argument. This recipe strips the callback
11
+ * to prepare code for the migration.
12
+ *
13
+ * Before:
14
+ * ```tsx
15
+ * ReactDOM.render(<App />, document.getElementById('root'), () => {
16
+ * console.log('rendered');
17
+ * });
18
+ * ```
19
+ *
20
+ * After:
21
+ * ```tsx
22
+ * ReactDOM.render(<App />, document.getElementById('root'));
23
+ * ```
24
+ *
25
+ * @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
26
+ */
27
+ export class ReplaceRenderCallback extends Recipe {
28
+ readonly name = "org.openrewrite.react.18.replace-render-callback";
29
+ readonly displayName: string = "Remove `ReactDOM.render` callback argument";
30
+ readonly description: string = "Removes the third callback argument from `ReactDOM.render(element, container, callback)` calls. Callbacks are not supported in React 18's `createRoot` API.";
31
+
32
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
33
+ return new class extends JavaScriptVisitor<ExecutionContext> {
34
+ override async visitMethodInvocation(method: J.MethodInvocation, p: ExecutionContext): Promise<J | undefined> {
35
+ let m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
36
+
37
+ if (m.name.simpleName !== 'render') return m;
38
+
39
+ // Check for ReactDOM.render (namespace call)
40
+ const isReactDomRender = (() => {
41
+ if (m.select) {
42
+ const select = (m.select as any).element ?? m.select;
43
+ return isIdentifier(select) && select.simpleName === 'ReactDOM';
44
+ }
45
+ return false;
46
+ })();
47
+
48
+ if (!isReactDomRender) return m;
49
+
50
+ // Only transform when there are exactly 3 arguments (element, container, callback)
51
+ const args = m.arguments?.elements;
52
+ if (!args || args.length !== 3) return m;
53
+
54
+ // Remove the 3rd argument (callback)
55
+ const newArgs = args.slice(0, 2);
56
+
57
+ return create(m, draft => {
58
+ (draft as any).arguments = {
59
+ ...m.arguments,
60
+ elements: newArgs
61
+ };
62
+ });
63
+ }
64
+ }();
65
+ }
66
+ }
@@ -0,0 +1,66 @@
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.unmountComponentAtNode()` with `createRoot().unmount()`.
15
+ *
16
+ * In React 18, `ReactDOM.unmountComponentAtNode()` is deprecated in favor of
17
+ * `createRoot().unmount()` from `react-dom/client`.
18
+ *
19
+ * Before:
20
+ * ```tsx
21
+ * import { unmountComponentAtNode } from 'react-dom';
22
+ * unmountComponentAtNode(document.getElementById('root'));
23
+ * ```
24
+ *
25
+ * After:
26
+ * ```tsx
27
+ * import { createRoot } from 'react-dom/client';
28
+ * createRoot(document.getElementById('root')).unmount();
29
+ * ```
30
+ *
31
+ * @see https://react.dev/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis
32
+ */
33
+ export class ReplaceUnmountComponentAtNode extends Recipe {
34
+ readonly name = "org.openrewrite.react.18.replace-unmount-component-at-node";
35
+ readonly displayName: string = "Replace `unmountComponentAtNode` with `createRoot().unmount()`";
36
+ readonly description: string = "Replaces `ReactDOM.unmountComponentAtNode(container)` with `createRoot(container).unmount()` from `react-dom/client`.";
37
+
38
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
39
+ const reactDomOptions = {
40
+ context: ['import { unmountComponentAtNode } from "react-dom";'],
41
+ dependencies: {'@types/react-dom': '^17.0.0'}
42
+ };
43
+
44
+ const container = capture<Expression>();
45
+
46
+ const rule = rewrite(() => ({
47
+ before: pattern`unmountComponentAtNode(${container})`.configure(reactDomOptions),
48
+ after: template`createRoot(${container}).unmount()`
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", "unmountComponentAtNode");
58
+ maybeAddImport(this, {module: "react-dom/client", member: "createRoot", onlyIfReferenced: false});
59
+ return result;
60
+ }
61
+
62
+ return m;
63
+ }
64
+ }();
65
+ }
66
+ }