@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,199 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SimplifyReactImports = void 0;
13
+ const rewrite_1 = require("@openrewrite/rewrite");
14
+ const javascript_1 = require("@openrewrite/rewrite/javascript");
15
+ const java_1 = require("@openrewrite/rewrite/java");
16
+ const javascript_2 = require("@openrewrite/rewrite/javascript");
17
+ const REACT_EXPORTS = new Set([
18
+ 'useState',
19
+ 'useEffect',
20
+ 'useContext',
21
+ 'useReducer',
22
+ 'useCallback',
23
+ 'useMemo',
24
+ 'useRef',
25
+ 'useImperativeHandle',
26
+ 'useLayoutEffect',
27
+ 'useDebugValue',
28
+ 'useDeferredValue',
29
+ 'useTransition',
30
+ 'useId',
31
+ 'useSyncExternalStore',
32
+ 'useInsertionEffect',
33
+ 'useActionState',
34
+ 'useFormStatus',
35
+ 'useOptimistic',
36
+ 'use',
37
+ 'cache',
38
+ 'Fragment',
39
+ 'Suspense',
40
+ 'StrictMode',
41
+ 'Profiler',
42
+ 'createContext',
43
+ 'forwardRef',
44
+ 'lazy',
45
+ 'memo',
46
+ 'startTransition',
47
+ 'Children',
48
+ 'cloneElement',
49
+ 'createElement',
50
+ 'createRef',
51
+ 'isValidElement',
52
+ ]);
53
+ function getReactMethodName(method) {
54
+ var _a, _b;
55
+ const methodName = (_a = method.name) === null || _a === void 0 ? void 0 : _a.simpleName;
56
+ if (!methodName || !REACT_EXPORTS.has(methodName))
57
+ return undefined;
58
+ const select = method.select;
59
+ if (!select)
60
+ return undefined;
61
+ const selectExpr = (_b = select.element) !== null && _b !== void 0 ? _b : select;
62
+ if ((selectExpr === null || selectExpr === void 0 ? void 0 : selectExpr.kind) !== java_1.J.Kind.Identifier)
63
+ return undefined;
64
+ const identifier = selectExpr;
65
+ if (identifier.simpleName !== 'React')
66
+ return undefined;
67
+ return methodName;
68
+ }
69
+ class SimplifyReactImports extends rewrite_1.Recipe {
70
+ constructor() {
71
+ super(...arguments);
72
+ this.name = "org.openrewrite.react.simplify-react-imports";
73
+ this.displayName = "Simplify `React.xxx` to direct imports";
74
+ this.description = "Converts `React.useState`, `React.useEffect`, and other React namespace accesses to direct named imports.";
75
+ }
76
+ editor() {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ return new class extends javascript_1.JavaScriptVisitor {
79
+ constructor() {
80
+ super(...arguments);
81
+ this.usedExports = new Set();
82
+ this.localIdentifiers = new Set();
83
+ }
84
+ visitJsCompilationUnit(cu, ctx) {
85
+ const _super = Object.create(null, {
86
+ visitJsCompilationUnit: { get: () => super.visitJsCompilationUnit }
87
+ });
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ this.usedExports = new Set();
90
+ this.localIdentifiers = new Set();
91
+ yield this.collectLocalIdentifiers(cu, ctx);
92
+ let result = yield _super.visitJsCompilationUnit.call(this, cu, ctx);
93
+ for (const exportName of this.usedExports) {
94
+ (0, javascript_1.maybeAddImport)(this, { module: "react", member: exportName, onlyIfReferenced: false });
95
+ }
96
+ return result;
97
+ });
98
+ }
99
+ collectLocalIdentifiers(cu, ctx) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ const collector = new class extends javascript_1.JavaScriptVisitor {
102
+ constructor(identifiers) {
103
+ super();
104
+ this.identifiers = identifiers;
105
+ }
106
+ visitVariableDeclarations(varDecl, ctx) {
107
+ const _super = Object.create(null, {
108
+ visitVariableDeclarations: { get: () => super.visitVariableDeclarations }
109
+ });
110
+ return __awaiter(this, void 0, void 0, function* () {
111
+ for (const v of varDecl.variables) {
112
+ const name = v.element.name;
113
+ if (name.kind === java_1.J.Kind.Identifier) {
114
+ this.identifiers.add(name.simpleName);
115
+ }
116
+ }
117
+ return _super.visitVariableDeclarations.call(this, varDecl, ctx);
118
+ });
119
+ }
120
+ visitMethodDeclaration(method, ctx) {
121
+ const _super = Object.create(null, {
122
+ visitMethodDeclaration: { get: () => super.visitMethodDeclaration }
123
+ });
124
+ return __awaiter(this, void 0, void 0, function* () {
125
+ this.identifiers.add(method.name.simpleName);
126
+ return _super.visitMethodDeclaration.call(this, method, ctx);
127
+ });
128
+ }
129
+ visitImportSpecifier(spec, ctx) {
130
+ const _super = Object.create(null, {
131
+ visitImportSpecifier: { get: () => super.visitImportSpecifier }
132
+ });
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const specifier = spec.specifier;
135
+ if (specifier.kind === javascript_2.JS.Kind.Alias) {
136
+ const alias = specifier;
137
+ const aliasIdent = alias.alias;
138
+ if (aliasIdent.kind === java_1.J.Kind.Identifier) {
139
+ this.identifiers.add(aliasIdent.simpleName);
140
+ }
141
+ }
142
+ else if (specifier.kind === java_1.J.Kind.Identifier) {
143
+ }
144
+ return _super.visitImportSpecifier.call(this, spec, ctx);
145
+ });
146
+ }
147
+ }(this.localIdentifiers);
148
+ yield collector.visit(cu, ctx);
149
+ });
150
+ }
151
+ visitMethodInvocation(method, ctx) {
152
+ const _super = Object.create(null, {
153
+ visitMethodInvocation: { get: () => super.visitMethodInvocation }
154
+ });
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ let m = yield _super.visitMethodInvocation.call(this, method, ctx);
157
+ const methodName = getReactMethodName(m);
158
+ if (!methodName) {
159
+ return m;
160
+ }
161
+ if (this.localIdentifiers.has(methodName)) {
162
+ return m;
163
+ }
164
+ this.usedExports.add(methodName);
165
+ const result = Object.assign(Object.assign({}, m), { select: undefined, name: m.name });
166
+ return result;
167
+ });
168
+ }
169
+ visitFieldAccess(fieldAccess, ctx) {
170
+ const _super = Object.create(null, {
171
+ visitFieldAccess: { get: () => super.visitFieldAccess }
172
+ });
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ let fa = yield _super.visitFieldAccess.call(this, fieldAccess, ctx);
175
+ if (fa.target.kind !== java_1.J.Kind.Identifier) {
176
+ return fa;
177
+ }
178
+ const target = fa.target;
179
+ if (target.simpleName !== 'React') {
180
+ return fa;
181
+ }
182
+ const memberName = fa.name.element.simpleName;
183
+ if (!REACT_EXPORTS.has(memberName)) {
184
+ return fa;
185
+ }
186
+ if (this.localIdentifiers.has(memberName)) {
187
+ return fa;
188
+ }
189
+ this.usedExports.add(memberName);
190
+ const identifier = Object.assign(Object.assign({}, fa.name.element), { prefix: fa.prefix });
191
+ return identifier;
192
+ });
193
+ }
194
+ }();
195
+ });
196
+ }
197
+ }
198
+ exports.SimplifyReactImports = SimplifyReactImports;
199
+ //# sourceMappingURL=simplify-react-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simplify-react-imports.js","sourceRoot":"","sources":["../src/simplify-react-imports.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@openrewrite/recipes-react",
3
+ "version": "0.2.9",
4
+ "license": "Moderne Source Available License",
5
+ "description": "OpenRewrite recipes for React.",
6
+ "homepage": "https://github.com/openrewrite/rewrite",
7
+ "main": "./dist/src/index.js",
8
+ "types": "./dist/src/index.d.ts",
9
+ "files": [
10
+ "dist/src/**",
11
+ "src/**"
12
+ ],
13
+ "exports": {
14
+ ".": "./dist/src/index.js"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "prebuild": "bun -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun -e \"require('fs').rmSync('tsconfig.build.tsbuildinfo',{force:true})\"",
21
+ "build": "tsc --build tsconfig.build.json",
22
+ "dev": "tsc --watch -p tsconfig.json",
23
+ "test": "npm run build && jest",
24
+ "ci:test": "NODE_OPTIONS=--max-old-space-size=6144 jest --runInBand"
25
+ },
26
+ "dependencies": {
27
+ "@openrewrite/rewrite": "next",
28
+ "mutative": "^1.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/jest": "^29.5.13",
32
+ "bun": "^1.3.5",
33
+ "jest": "^29.7.0",
34
+ "tmp-promise": "^3.0.3",
35
+ "ts-jest": "^29.2.5",
36
+ "ts-node": "^10.9.2",
37
+ "typescript": "^5.6.2"
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,149 @@
1
+ import {CategoryDescriptor, JavaScript, RecipeMarketplace} from "@openrewrite/rewrite";
2
+
3
+ // React 19
4
+ import {RemoveForwardRef} from './react19/remove-forward-ref';
5
+ import {RemoveReactFC} from './react19/remove-react-fc';
6
+ import {ReplaceActImport} from './react19/replace-act-import';
7
+ import {RemoveContextProvider} from './react19/remove-context-provider';
8
+ import {UseContextHook} from './react19/use-context-hook';
9
+ import {ReplaceUseFormState} from './react19/replace-use-form-state';
10
+ import {ReplaceStringRef} from './react19/replace-string-ref';
11
+ import {ReplaceDefaultProps} from './react19/replace-default-props';
12
+ import {ReplaceReactDomHydrate} from './react19/replace-reactdom-hydrate';
13
+ import {UseRefRequiredInitial} from './react19/use-ref-required-initial';
14
+ import {RemovePropTypes} from './react19/remove-prop-types';
15
+ import {NoImplicitRefCallbackReturn} from './react19/no-implicit-ref-callback-return';
16
+ import {DeprecatedReactTypes} from './react19/deprecated-react-types';
17
+ import {FindLegacyContextAPI} from './react19/find-legacy-context-api';
18
+ import {FindDeprecatedReactDomAPIs} from './react19/find-deprecated-reactdom-apis';
19
+ import {FindContextConsumer} from './react19/find-context-consumer';
20
+ import {UpgradeToReact19} from './react19/upgrade-to-react-19';
21
+
22
+ import {FindElementRef} from './react19/find-element-ref';
23
+ import {ReplaceReactShallowRenderer} from './react19/replace-react-shallow-renderer';
24
+
25
+ // React 18
26
+ import {ReplaceReactDomRender} from './react18/replace-reactdom-render';
27
+ import {RemoveUnstableBatchedUpdates} from './react18/remove-unstable-batched-updates';
28
+ import {ReplaceUnmountComponentAtNode} from './react18/replace-unmount-component-at-node';
29
+ import {ReplaceRenderCallback} from './react18/replace-render-callback';
30
+ import {UpgradeToReact18} from './react18/upgrade-to-react-18';
31
+
32
+ // React 17
33
+ import {RenameUnsafeLifecycles} from './react17/rename-unsafe-lifecycles';
34
+ import {UpdateReactImports} from './react17/update-react-imports';
35
+ import {RemoveEventPersist} from './react17/remove-event-persist';
36
+ import {UpgradeToReact17} from './react17/upgrade-to-react-17';
37
+
38
+ // React 16
39
+ import {ErrorBoundaries} from './react16/error-boundaries';
40
+ import {FindDomNode} from './react16/find-dom-node';
41
+ import {ReactPropTypes} from './react16/react-prop-types';
42
+ import {ReactDomFactories} from './react16/react-dom-factories';
43
+ import {ReactToReactDom} from './react16/react-to-react-dom';
44
+ import {ReplaceCreateFactory} from './react16/replace-create-factory';
45
+ import {UpgradeToReact16} from './react16/upgrade-to-react-16';
46
+
47
+ // Refactoring
48
+ import {CreateClassToES6} from './refactoring/create-class-to-es6';
49
+ import {ClassToFunctional} from './refactoring/class-to-functional';
50
+ import {ManualBindToArrow} from './refactoring/manual-bind-to-arrow';
51
+ import {CreateElementToJsx} from './refactoring/create-element-to-jsx';
52
+ import {SortComp} from './refactoring/sort-comp';
53
+ import {PureRenderMixin} from './refactoring/pure-render-mixin';
54
+
55
+ // React Native
56
+ import {ViewPropTypes} from './react-native/view-prop-types';
57
+
58
+ // Search & Analysis
59
+ import {FindReactComponent} from './search/find-react-component';
60
+ import {FindHookUsage} from './search/find-hook-usage';
61
+ import {FindPropUsage} from './search/find-prop-usage';
62
+ import {FindServerRenderingUsage} from './search/find-server-rendering-usage';
63
+
64
+ // General
65
+ import {SimplifyReactImports} from './simplify-react-imports';
66
+ import {SimplifyObjectPatternProperty} from './simplify-object-pattern-property';
67
+ import {ChangeComponentPropValue} from './migration/change-component-prop-value';
68
+
69
+ // Categories
70
+ const React: CategoryDescriptor[] = [...JavaScript, {displayName: "React"}];
71
+ const ReactMigrate: CategoryDescriptor[] = [...React, {displayName: "Migrate"}];
72
+ const React16: CategoryDescriptor[] = [...ReactMigrate, {displayName: "React 16"}];
73
+ const React17: CategoryDescriptor[] = [...ReactMigrate, {displayName: "React 17"}];
74
+ const React18: CategoryDescriptor[] = [...ReactMigrate, {displayName: "React 18"}];
75
+ const React19: CategoryDescriptor[] = [...ReactMigrate, {displayName: "React 19"}];
76
+ const ReactMigration: CategoryDescriptor[] = [...React, {displayName: "Migration"}];
77
+ const ReactRefactoring: CategoryDescriptor[] = [...React, {displayName: "Refactoring"}];
78
+ const ReactNative: CategoryDescriptor[] = [...React, {displayName: "React Native"}];
79
+ const ReactSearch: CategoryDescriptor[] = [...React, {displayName: "Search"}];
80
+ const JavaScriptCleanup: CategoryDescriptor[] = [...JavaScript, {displayName: "Cleanup"}];
81
+
82
+ export async function activate(marketplace: RecipeMarketplace) {
83
+ // React 19
84
+ await marketplace.install(RemoveForwardRef, React19);
85
+ await marketplace.install(RemoveReactFC, React19);
86
+ await marketplace.install(ReplaceActImport, React19);
87
+ await marketplace.install(RemoveContextProvider, React19);
88
+ await marketplace.install(UseContextHook, React19);
89
+ await marketplace.install(ReplaceUseFormState, React19);
90
+ await marketplace.install(ReplaceStringRef, React19);
91
+ await marketplace.install(ReplaceDefaultProps, React19);
92
+ await marketplace.install(ReplaceReactDomHydrate, React19);
93
+ await marketplace.install(UseRefRequiredInitial, React19);
94
+ await marketplace.install(RemovePropTypes, React19);
95
+ await marketplace.install(NoImplicitRefCallbackReturn, React19);
96
+ await marketplace.install(DeprecatedReactTypes, React19);
97
+ await marketplace.install(FindLegacyContextAPI, React19);
98
+ await marketplace.install(FindDeprecatedReactDomAPIs, React19);
99
+ await marketplace.install(FindContextConsumer, React19);
100
+ await marketplace.install(FindElementRef, React19);
101
+ await marketplace.install(ReplaceReactShallowRenderer, React19);
102
+ await marketplace.install(UpgradeToReact19, ReactMigrate);
103
+
104
+ // React 18
105
+ await marketplace.install(ReplaceReactDomRender, React18);
106
+ await marketplace.install(RemoveUnstableBatchedUpdates, React18);
107
+ await marketplace.install(ReplaceUnmountComponentAtNode, React18);
108
+ await marketplace.install(ReplaceRenderCallback, React18);
109
+ await marketplace.install(UpgradeToReact18, ReactMigrate);
110
+
111
+ // React 17
112
+ await marketplace.install(RenameUnsafeLifecycles, React17);
113
+ await marketplace.install(UpdateReactImports, React17);
114
+ await marketplace.install(RemoveEventPersist, React17);
115
+ await marketplace.install(UpgradeToReact17, ReactMigrate);
116
+
117
+ // React 16
118
+ await marketplace.install(ErrorBoundaries, React16);
119
+ await marketplace.install(FindDomNode, React16);
120
+ await marketplace.install(ReactPropTypes, React16);
121
+ await marketplace.install(ReactDomFactories, React16);
122
+ await marketplace.install(ReactToReactDom, React16);
123
+ await marketplace.install(ReplaceCreateFactory, React16);
124
+ await marketplace.install(UpgradeToReact16, ReactMigrate);
125
+
126
+ // Refactoring
127
+ await marketplace.install(CreateClassToES6, ReactRefactoring);
128
+ await marketplace.install(ClassToFunctional, ReactRefactoring);
129
+ await marketplace.install(ManualBindToArrow, ReactRefactoring);
130
+ await marketplace.install(CreateElementToJsx, ReactRefactoring);
131
+ await marketplace.install(SortComp, ReactRefactoring);
132
+ await marketplace.install(PureRenderMixin, ReactRefactoring);
133
+
134
+ // React Native
135
+ await marketplace.install(ViewPropTypes, ReactNative);
136
+
137
+ // Search & Analysis
138
+ await marketplace.install(FindReactComponent, ReactSearch);
139
+ await marketplace.install(FindHookUsage, ReactSearch);
140
+ await marketplace.install(FindPropUsage, ReactSearch);
141
+ await marketplace.install(FindServerRenderingUsage, ReactSearch);
142
+
143
+ // General
144
+ await marketplace.install(SimplifyReactImports, React);
145
+ await marketplace.install(SimplifyObjectPatternProperty, JavaScriptCleanup);
146
+ await marketplace.install(ChangeComponentPropValue, ReactMigration);
147
+ }
148
+
149
+ export {ChangeComponentPropValue} from './migration/change-component-prop-value';
@@ -0,0 +1,268 @@
1
+ import {ExecutionContext, Option, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS, JSX} from "@openrewrite/rewrite/javascript";
3
+ import {J, isIdentifier} from "@openrewrite/rewrite/java";
4
+ import {create} from "mutative";
5
+
6
+ /**
7
+ * Changes literal prop values on React components during library upgrades.
8
+ *
9
+ * This recipe is useful when upgrading UI libraries that rename prop values
10
+ * (e.g., Material-UI, Ant Design). It only transforms literal string values,
11
+ * skipping variables and expressions to avoid unintended changes.
12
+ *
13
+ * @example
14
+ * // Exact match
15
+ * const recipe = new ChangeComponentPropValue({
16
+ * componentName: "Button",
17
+ * propName: "variant",
18
+ * oldValue: "outlined",
19
+ * newValue: "outlined-primary"
20
+ * });
21
+ * // Before: <Button variant="outlined">Click me</Button>
22
+ * // After: <Button variant="outlined-primary">Click me</Button>
23
+ *
24
+ * @example
25
+ * // Regex with capture groups
26
+ * const recipe = new ChangeComponentPropValue({
27
+ * componentName: "Button",
28
+ * propName: "variant",
29
+ * oldValue: "^(outlined|text|contained)$",
30
+ * newValue: "$1-primary",
31
+ * regex: true
32
+ * });
33
+ * // Before: <Button variant="outlined">Click me</Button>
34
+ * // After: <Button variant="outlined-primary">Click me</Button>
35
+ *
36
+ * @example
37
+ * // Change all values (omit oldValue)
38
+ * const recipe = new ChangeComponentPropValue({
39
+ * componentName: "Button",
40
+ * propName: "variant",
41
+ * newValue: "primary"
42
+ * });
43
+ * // Before: <Button variant="outlined">Click me</Button>
44
+ * // After: <Button variant="primary">Click me</Button>
45
+ */
46
+ export class ChangeComponentPropValue extends Recipe {
47
+ readonly name = "org.openrewrite.react.migration.change-component-prop-value";
48
+ readonly displayName: string = "Change React component prop value";
49
+ readonly description: string = "Changes literal prop values on React components. Useful for library upgrades where prop values were renamed (e.g., Material-UI, Ant Design).";
50
+
51
+ @Option({
52
+ displayName: "Component name",
53
+ description: "The name of the React component to target",
54
+ example: "Button"
55
+ })
56
+ componentName!: string;
57
+
58
+ @Option({
59
+ displayName: "Prop name",
60
+ description: "The name of the prop whose value should be changed",
61
+ example: "variant"
62
+ })
63
+ propName!: string;
64
+
65
+ @Option({
66
+ displayName: "Old value",
67
+ description: "The old value to match. If `regex` is `true`, interpreted as a regular expression pattern. Supports `/pattern/flags` format for specifying regex flags (e.g., `/pattern/i` for case-insensitive). If not provided, matches all values.",
68
+ example: "outlined",
69
+ required: false
70
+ })
71
+ oldValue?: string;
72
+
73
+ @Option({
74
+ displayName: "New value",
75
+ description: "The new value to replace with. Can use `$1`, `$2`, etc. to reference capture groups if `regex` is `true`.",
76
+ example: "outlined-primary"
77
+ })
78
+ newValue!: string;
79
+
80
+ @Option({
81
+ displayName: "Regex",
82
+ description: "If `true`, `oldValue` is interpreted as a regex pattern. Capture groups can be referenced in newValue using `$1`, `$2`, etc.",
83
+ required: false
84
+ })
85
+ regex?: boolean;
86
+
87
+ constructor(options?: {
88
+ componentName?: string;
89
+ propName?: string;
90
+ oldValue?: string;
91
+ newValue?: string;
92
+ regex?: boolean;
93
+ }) {
94
+ super(options);
95
+ }
96
+
97
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
98
+ const componentName = this.componentName;
99
+ const propName = this.propName;
100
+ const oldValue = this.oldValue;
101
+ const newValue = this.newValue;
102
+ const isRegex = this.regex ?? false;
103
+
104
+ // Build matcher based on regex flag and oldValue
105
+ let matcher: ((value: string) => string | null);
106
+
107
+ if (!oldValue) {
108
+ // No old value specified - match all values
109
+ matcher = (value: string) => newValue;
110
+ } else if (isRegex) {
111
+ // Compile regex pattern, supporting /pattern/flags format
112
+ let pattern = oldValue;
113
+ let flags = '';
114
+
115
+ // Check if pattern is in /pattern/flags format
116
+ const regexMatch = oldValue.match(/^\/(.+?)\/([gimsuvy]*)$/);
117
+ if (regexMatch) {
118
+ pattern = regexMatch[1];
119
+ flags = regexMatch[2];
120
+ }
121
+
122
+ const regex = new RegExp(pattern, flags);
123
+
124
+ matcher = (value: string) => {
125
+ const match = value.match(regex);
126
+ if (match) {
127
+ // Replace $1, $2, etc. with capture groups
128
+ return newValue.replace(/\$(\d+)/g, (_, num) => match[parseInt(num)] || '');
129
+ }
130
+ return null;
131
+ };
132
+ } else {
133
+ // Literal string match
134
+ matcher = (value: string) => value === oldValue ? newValue : null;
135
+ }
136
+
137
+ return new class extends JavaScriptVisitor<ExecutionContext> {
138
+ // FIXME: This manual import tracking should be removed once type attribution preserves
139
+ // module/package origin information. Currently, JSX element types show as generic
140
+ // function types (𝑓) without preserving where they were imported from. Ideally, we
141
+ // should be able to match components by checking if their type originates from the
142
+ // expected module (e.g., @mui/material.Button), which would automatically handle
143
+ // all import patterns (named, aliased, default, namespace) without manual tracking.
144
+ private componentAliases = new Map<string, string>();
145
+
146
+ override async visitCompilationUnit(cu: J.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
147
+ // Reset aliases for each file
148
+ this.componentAliases.clear();
149
+ return super.visitCompilationUnit(cu, p);
150
+ }
151
+
152
+ override async visitImportSpecifier(spec: JS.ImportSpecifier, p: ExecutionContext): Promise<J | undefined> {
153
+ // Track named imports with potential aliases
154
+ const specifier = spec.specifier;
155
+
156
+ if (specifier.kind === JS.Kind.Alias) {
157
+ const alias = specifier as JS.Alias;
158
+ const propertyName = alias.propertyName.element;
159
+
160
+ if (isIdentifier(propertyName) && propertyName.simpleName === componentName) {
161
+ // Aliased import: import { Button as CustomButton }
162
+ const aliasIdent = alias.alias;
163
+ if (isIdentifier(aliasIdent)) {
164
+ this.componentAliases.set(aliasIdent.simpleName, componentName);
165
+ }
166
+ }
167
+ } else if (isIdentifier(specifier) && specifier.simpleName === componentName) {
168
+ // Direct import: import { Button }
169
+ this.componentAliases.set(componentName, componentName);
170
+ }
171
+
172
+ return super.visitImportSpecifier(spec, p);
173
+ }
174
+
175
+ override async visitImportClause(clause: JS.ImportClause, p: ExecutionContext): Promise<J | undefined> {
176
+ // Track default imports
177
+ if (clause.name) {
178
+ const identifier = clause.name.element;
179
+ if (isIdentifier(identifier) && identifier.simpleName === componentName) {
180
+ // Default import: import Button from '...'
181
+ this.componentAliases.set(componentName, componentName);
182
+ }
183
+ }
184
+
185
+ return super.visitImportClause(clause, p);
186
+ }
187
+
188
+ override async visitJsxAttribute(attribute: JSX.Attribute, p: ExecutionContext): Promise<J | undefined> {
189
+ // Visit children first
190
+ const attr = await super.visitJsxAttribute(attribute, p) as JSX.Attribute;
191
+
192
+ // Check if this is the prop we're looking for
193
+ const key = attr.key;
194
+ if (!isIdentifier(key) || key.simpleName !== propName) {
195
+ return attr;
196
+ }
197
+
198
+ // Verify this attribute belongs to the target component
199
+ const tag = this.cursor.firstEnclosing((t): t is JSX.Tag =>
200
+ t.kind === JS.Kind.JsxTag
201
+ );
202
+ if (!tag) {
203
+ return attr;
204
+ }
205
+
206
+ if (!this.isTargetComponent(tag)) {
207
+ return attr;
208
+ }
209
+
210
+ // Check if the value is a literal we should replace
211
+ if (!attr.value) {
212
+ return attr;
213
+ }
214
+
215
+ const value = attr.value.element;
216
+ const newValue = this.getReplacementValue(value);
217
+ if (!newValue) {
218
+ return attr;
219
+ }
220
+
221
+ // Replace the value
222
+ return create(attr, draft => {
223
+ if (draft.value) {
224
+ draft.value.element = newValue;
225
+ }
226
+ });
227
+ }
228
+
229
+ private isTargetComponent(tag: JSX.Tag): boolean {
230
+ const openName = tag.openName.element;
231
+
232
+ if (isIdentifier(openName)) {
233
+ const tagName = openName.simpleName;
234
+ // Check if tagName matches our component or is an alias for it
235
+ return tagName === componentName ||
236
+ this.componentAliases.get(tagName) === componentName;
237
+ } else if (openName.kind === J.Kind.FieldAccess) {
238
+ const fieldAccess = openName as J.FieldAccess;
239
+ const nameIdent = fieldAccess.name.element;
240
+ if (isIdentifier(nameIdent)) {
241
+ return nameIdent.simpleName === componentName;
242
+ }
243
+ }
244
+
245
+ return false;
246
+ }
247
+
248
+ private getReplacementValue(value: J): J | null {
249
+ // Only handle string literals (quoted values)
250
+ if (value.kind === J.Kind.Literal) {
251
+ const literal = value as J.Literal;
252
+ const literalValue = literal.value;
253
+ if (typeof literalValue === 'string') {
254
+ const replacement = matcher(literalValue);
255
+ if (replacement !== null) {
256
+ return create(literal, draft => {
257
+ draft.value = replacement;
258
+ draft.valueSource = `"${replacement}"`;
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ return null;
265
+ }
266
+ }();
267
+ }
268
+ }