@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,254 @@
1
+ import {Column, DataTable, ExecutionContext, foundSearchResult, Option, Recipe, TreeVisitor} from "@openrewrite/rewrite";
2
+ import {JavaScriptVisitor, JS, JSX} from "@openrewrite/rewrite/javascript";
3
+ import {Expression, isIdentifier, J} from "@openrewrite/rewrite/java";
4
+
5
+ class FindReactComponentRow {
6
+ @Column({displayName: "Component name", description: "The name of the React component"})
7
+ componentName!: string;
8
+
9
+ @Column({displayName: "File path", description: "The file path where the component is used"})
10
+ filePath!: string;
11
+
12
+ @Column({displayName: "Usage type", description: "How the component is referenced: import, jsx-element, or export"})
13
+ usageType!: string;
14
+
15
+ @Column({displayName: "Import path", description: "The module path the component is imported from"})
16
+ importPath!: string;
17
+
18
+ @Column({displayName: "Is default import", description: "Whether the component is imported as a default export"})
19
+ isDefaultImport!: boolean;
20
+
21
+ @Column({displayName: "Alias name", description: "Local alias name if the component is imported with a different name"})
22
+ aliasName!: string;
23
+ }
24
+
25
+ /**
26
+ * Finds usages of a specific React component across source files.
27
+ *
28
+ * Detects:
29
+ * - Named imports: `import { Button } from 'lib'`
30
+ * - Default imports: `import Button from 'lib'`
31
+ * - Aliased imports: `import { Button as Btn } from 'lib'`
32
+ * - Namespace access: `<Lib.Button />`
33
+ * - JSX elements: `<Button />` and `<Button>...</Button>`
34
+ * - Re-exports: `export { Button } from 'lib'`
35
+ */
36
+ export class FindReactComponent extends Recipe {
37
+ readonly name = "org.openrewrite.react.search.find-react-component";
38
+ readonly displayName: string = "Find React component";
39
+ readonly description: string = "Finds all usages of a specific React component including imports, JSX elements, and exports.";
40
+
41
+ @Option({
42
+ displayName: "Component name",
43
+ description: "The name of the React component to find (e.g., 'Button', 'TextField')",
44
+ example: "Button"
45
+ })
46
+ componentName!: string;
47
+
48
+ @Option({
49
+ displayName: "Import path",
50
+ description: "Optional module path to narrow the search (e.g., '@mui/material'). Supports glob patterns.",
51
+ required: false,
52
+ example: "@mui/material"
53
+ })
54
+ importPath?: string;
55
+
56
+ private dataTable = new DataTable<FindReactComponentRow>(
57
+ "org.openrewrite.react.search.FindReactComponentTable",
58
+ "React component usages",
59
+ "Table of all React component usages found in the codebase.",
60
+ FindReactComponentRow
61
+ );
62
+
63
+ constructor(options?: { componentName?: string; importPath?: string }) {
64
+ super(options);
65
+ }
66
+
67
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
68
+ const componentName = this.componentName;
69
+ const importPathFilter = this.importPath;
70
+ const dataTable = this.dataTable;
71
+
72
+ return new class extends JavaScriptVisitor<ExecutionContext> {
73
+ // Track component aliases within the current file
74
+ private aliases = new Map<string, { importPath: string; isDefault: boolean; alias: string }>();
75
+ private filePath = "";
76
+
77
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
78
+ this.aliases.clear();
79
+ this.filePath = cu.sourcePath ?? "";
80
+ return super.visitJsCompilationUnit(cu, ctx);
81
+ }
82
+
83
+ // --- Import Detection ---
84
+
85
+ override async visitImportSpecifier(spec: JS.ImportSpecifier, ctx: ExecutionContext): Promise<J | undefined> {
86
+ const specifier = spec.specifier;
87
+ const importDecl = this.findEnclosingImport();
88
+ const modulePath = importDecl ? this.getModuleSpecifier(importDecl) : "";
89
+
90
+ if (importPathFilter && modulePath && !this.matchesPath(modulePath, importPathFilter)) {
91
+ return super.visitImportSpecifier(spec, ctx);
92
+ }
93
+
94
+ if (specifier.kind === JS.Kind.Alias) {
95
+ const alias = specifier as JS.Alias;
96
+ const propertyName = alias.propertyName.element;
97
+ if (isIdentifier(propertyName) && propertyName.simpleName === componentName) {
98
+ const aliasIdent = alias.alias;
99
+ const aliasName = isIdentifier(aliasIdent) ? aliasIdent.simpleName : componentName;
100
+ this.aliases.set(aliasName, {importPath: modulePath, isDefault: false, alias: aliasName});
101
+ dataTable.insertRow(ctx, {
102
+ componentName, filePath: this.filePath, usageType: "import",
103
+ importPath: modulePath, isDefaultImport: false, aliasName
104
+ });
105
+ return foundSearchResult(spec, componentName);
106
+ }
107
+ } else if (isIdentifier(specifier) && specifier.simpleName === componentName) {
108
+ this.aliases.set(componentName, {importPath: modulePath, isDefault: false, alias: ""});
109
+ dataTable.insertRow(ctx, {
110
+ componentName, filePath: this.filePath, usageType: "import",
111
+ importPath: modulePath, isDefaultImport: false, aliasName: ""
112
+ });
113
+ return foundSearchResult(spec, componentName);
114
+ }
115
+
116
+ return super.visitImportSpecifier(spec, ctx);
117
+ }
118
+
119
+ override async visitImportClause(clause: JS.ImportClause, ctx: ExecutionContext): Promise<J | undefined> {
120
+ if (clause.name) {
121
+ const identifier = clause.name.element;
122
+ if (isIdentifier(identifier) && identifier.simpleName === componentName) {
123
+ const importDecl = this.findEnclosingImport();
124
+ const modulePath = importDecl ? this.getModuleSpecifier(importDecl) : "";
125
+
126
+ if (!importPathFilter || !modulePath || this.matchesPath(modulePath, importPathFilter)) {
127
+ this.aliases.set(componentName, {importPath: modulePath, isDefault: true, alias: ""});
128
+ dataTable.insertRow(ctx, {
129
+ componentName, filePath: this.filePath, usageType: "import",
130
+ importPath: modulePath, isDefaultImport: true, aliasName: ""
131
+ });
132
+ return foundSearchResult(clause, componentName);
133
+ }
134
+ }
135
+ }
136
+ return super.visitImportClause(clause, ctx);
137
+ }
138
+
139
+ // --- JSX Element Detection ---
140
+
141
+ override async visitJsxTag(tag: JSX.Tag, ctx: ExecutionContext): Promise<J | undefined> {
142
+ const t = await super.visitJsxTag(tag, ctx) as JSX.Tag;
143
+ const openName = t.openName.element;
144
+
145
+ if (isIdentifier(openName)) {
146
+ const tagName = openName.simpleName;
147
+ // When importPathFilter is set, only match components from matching imports
148
+ const isTracked = this.aliases.has(tagName);
149
+ const matchesName = tagName === componentName;
150
+ if (isTracked || (!importPathFilter && matchesName)) {
151
+ const info = this.aliases.get(tagName);
152
+ dataTable.insertRow(ctx, {
153
+ componentName, filePath: this.filePath, usageType: "jsx-element",
154
+ importPath: info?.importPath ?? "", isDefaultImport: info?.isDefault ?? false,
155
+ aliasName: info?.alias ?? ""
156
+ });
157
+ return foundSearchResult(t, componentName);
158
+ }
159
+ } else if (openName.kind === J.Kind.FieldAccess) {
160
+ // Handle <Namespace.Component />
161
+ const fieldAccess = openName as J.FieldAccess;
162
+ const nameIdent = fieldAccess.name.element;
163
+ if (isIdentifier(nameIdent) && nameIdent.simpleName === componentName && !importPathFilter) {
164
+ dataTable.insertRow(ctx, {
165
+ componentName, filePath: this.filePath, usageType: "jsx-element",
166
+ importPath: "", isDefaultImport: false, aliasName: ""
167
+ });
168
+ return foundSearchResult(t, componentName);
169
+ }
170
+ }
171
+
172
+ return t;
173
+ }
174
+
175
+ // --- Export Detection ---
176
+
177
+ override async visitExportDeclaration(exportDecl: JS.ExportDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
178
+ const e = await super.visitExportDeclaration(exportDecl, ctx) as JS.ExportDeclaration;
179
+
180
+ // Check if this export re-exports our component
181
+ const exportClause = e.exportClause;
182
+ if (!exportClause) return e;
183
+
184
+ // NamedExports: export { Component } from 'lib'
185
+ if (exportClause.kind === JS.Kind.NamedExports) {
186
+ const namedExports = exportClause as JS.NamedExports;
187
+ for (const elem of namedExports.elements.elements) {
188
+ const el = (elem as any).element ?? elem;
189
+ if (el.kind === JS.Kind.ExportSpecifier) {
190
+ const spec = el as JS.ExportSpecifier;
191
+ const specifier = spec.specifier;
192
+ if (isIdentifier(specifier) && specifier.simpleName === componentName) {
193
+ const modulePath = this.getExportModuleSpecifier(e);
194
+ dataTable.insertRow(ctx, {
195
+ componentName, filePath: this.filePath, usageType: "export",
196
+ importPath: modulePath, isDefaultImport: false, aliasName: ""
197
+ });
198
+ return foundSearchResult(e, componentName);
199
+ }
200
+ if (specifier.kind === JS.Kind.Alias) {
201
+ const alias = specifier as unknown as JS.Alias;
202
+ const propName = alias.propertyName.element;
203
+ if (isIdentifier(propName) && propName.simpleName === componentName) {
204
+ const aliasName = isIdentifier(alias.alias) ? alias.alias.simpleName : "";
205
+ const modulePath = this.getExportModuleSpecifier(e);
206
+ dataTable.insertRow(ctx, {
207
+ componentName, filePath: this.filePath, usageType: "export",
208
+ importPath: modulePath, isDefaultImport: false, aliasName
209
+ });
210
+ return foundSearchResult(e, componentName);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ return e;
218
+ }
219
+
220
+ // --- Helpers ---
221
+
222
+ private findEnclosingImport(): JS.Import | undefined {
223
+ return this.cursor.firstEnclosing((t): t is JS.Import =>
224
+ (t as any).kind === JS.Kind.Import
225
+ );
226
+ }
227
+
228
+ private getModuleSpecifier(importDecl: JS.Import): string {
229
+ if (!importDecl.moduleSpecifier) return "";
230
+ const spec = importDecl.moduleSpecifier.element;
231
+ if (spec.kind === J.Kind.Literal) {
232
+ return String((spec as J.Literal).value ?? "");
233
+ }
234
+ return "";
235
+ }
236
+
237
+ private getExportModuleSpecifier(exportDecl: JS.ExportDeclaration): string {
238
+ if (!exportDecl.moduleSpecifier) return "";
239
+ const spec = exportDecl.moduleSpecifier.element;
240
+ if (spec.kind === J.Kind.Literal) {
241
+ return String((spec as J.Literal).value ?? "");
242
+ }
243
+ return "";
244
+ }
245
+
246
+ private matchesPath(actual: string, pattern: string): boolean {
247
+ if (pattern === actual) return true;
248
+ // Simple glob: * matches any segment
249
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
250
+ return regex.test(actual);
251
+ }
252
+ }();
253
+ }
254
+ }
@@ -0,0 +1,120 @@
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 ServerRenderingRow {
6
+ @Column({displayName: "File path", description: "The file path where the SSR API is used"})
7
+ filePath!: string;
8
+
9
+ @Column({displayName: "API name", description: "The name of the server rendering API"})
10
+ apiName!: string;
11
+
12
+ @Column({displayName: "Replacement", description: "The recommended replacement API"})
13
+ replacement!: string;
14
+ }
15
+
16
+ const SSR_APIS: Record<string, string> = {
17
+ 'renderToString': 'Consider using renderToPipeableStream or renderToReadableStream for streaming SSR',
18
+ 'renderToStaticMarkup': 'Consider using renderToPipeableStream or renderToReadableStream for streaming SSR',
19
+ 'renderToNodeStream': 'Use renderToPipeableStream instead (removed in React 19)',
20
+ 'renderToStaticNodeStream': 'Use renderToPipeableStream or renderToReadableStream instead (removed in React 19)',
21
+ };
22
+
23
+ /**
24
+ * Finds usage of React server-side rendering APIs from `react-dom/server`.
25
+ *
26
+ * Detects:
27
+ * - `renderToString(element)` -- synchronous SSR, consider streaming alternatives
28
+ * - `renderToStaticMarkup(element)` -- synchronous static markup, consider streaming
29
+ * - `renderToNodeStream(element)` -- removed in React 19, use `renderToPipeableStream`
30
+ * - `renderToStaticNodeStream(element)` -- removed in React 19, use streaming APIs
31
+ *
32
+ * Also detects named imports and namespace calls (ReactDOMServer.renderToString, ReactDOM.renderToString).
33
+ */
34
+ export class FindServerRenderingUsage extends Recipe {
35
+ readonly name = "org.openrewrite.react.search.find-server-rendering-usage";
36
+ readonly displayName = "Find server-side rendering API usage";
37
+ readonly description = "Finds usage of React server-side rendering APIs from `react-dom/server` including `renderToString`, `renderToStaticMarkup`, `renderToNodeStream`, and `renderToStaticNodeStream` to help plan SSR migration.";
38
+
39
+ private dataTable = new DataTable<ServerRenderingRow>(
40
+ "org.openrewrite.react.search.ServerRenderingUsageTable",
41
+ "Server rendering API usages",
42
+ "Table of all server-side rendering API usages found in the codebase.",
43
+ ServerRenderingRow
44
+ );
45
+
46
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
47
+ const dataTable = this.dataTable;
48
+
49
+ return new class extends JavaScriptVisitor<ExecutionContext> {
50
+ private filePath = "";
51
+ private importedSsrApis = new Set<string>();
52
+
53
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
54
+ this.filePath = cu.sourcePath ?? "";
55
+ this.importedSsrApis.clear();
56
+ return super.visitJsCompilationUnit(cu, ctx);
57
+ }
58
+
59
+ // Detect imports of SSR APIs
60
+ override async visitImportSpecifier(spec: JS.ImportSpecifier, ctx: ExecutionContext): Promise<J | undefined> {
61
+ const specifier = spec.specifier;
62
+
63
+ let importedName: string | undefined;
64
+ if (specifier.kind === JS.Kind.Alias) {
65
+ const alias = specifier as JS.Alias;
66
+ const propName = alias.propertyName.element;
67
+ if (isIdentifier(propName)) {
68
+ importedName = propName.simpleName;
69
+ }
70
+ } else if (isIdentifier(specifier)) {
71
+ importedName = specifier.simpleName;
72
+ }
73
+
74
+ if (importedName && SSR_APIS[importedName]) {
75
+ this.importedSsrApis.add(importedName);
76
+ dataTable.insertRow(ctx, {
77
+ filePath: this.filePath,
78
+ apiName: importedName,
79
+ replacement: SSR_APIS[importedName]
80
+ });
81
+ return foundSearchResult(spec, `SSR API: ${importedName}`);
82
+ }
83
+
84
+ return super.visitImportSpecifier(spec, ctx);
85
+ }
86
+
87
+ // Detect method calls: renderToString(...), ReactDOMServer.renderToString(...), etc.
88
+ override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
89
+ let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
90
+
91
+ const methodName = m.name.simpleName;
92
+
93
+ if (!SSR_APIS[methodName]) return m;
94
+
95
+ // Direct call from named import: renderToString(...)
96
+ if (!m.select && this.importedSsrApis.has(methodName)) {
97
+ return foundSearchResult(m, `SSR API: ${methodName}`);
98
+ }
99
+
100
+ // Namespace call: ReactDOMServer.renderToString(...), ReactDOM.renderToString(...)
101
+ if (m.select) {
102
+ const select = (m.select as any).element ?? m.select;
103
+ if (isIdentifier(select)) {
104
+ const ns = select.simpleName;
105
+ if (ns === 'ReactDOMServer' || ns === 'ReactDOM') {
106
+ dataTable.insertRow(ctx, {
107
+ filePath: this.filePath,
108
+ apiName: `${ns}.${methodName}`,
109
+ replacement: SSR_APIS[methodName]
110
+ });
111
+ return foundSearchResult(m, `SSR API: ${ns}.${methodName}`);
112
+ }
113
+ }
114
+ }
115
+
116
+ return m;
117
+ }
118
+ }();
119
+ }
120
+ }
@@ -0,0 +1,71 @@
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
+ * Simplifies object pattern properties where the key and value have the same name.
8
+ *
9
+ * Examples:
10
+ * - `{ x: x }` → `{ x }`
11
+ * - `{ ref: ref, ...props }` → `{ ref, ...props }`
12
+ * - `{ foo: foo, bar: bar }` → `{ foo, bar }`
13
+ */
14
+ export class SimplifyObjectPatternProperty extends Recipe {
15
+ readonly name = "org.openrewrite.javascript.cleanup.simplify-object-pattern-property";
16
+ readonly displayName: string = "Simplify object pattern properties";
17
+ readonly description: string = "Simplifies object destructuring patterns where the property name and variable name are the same (e.g., `{ x: x }` becomes `{ x }`).";
18
+
19
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
20
+ return new class extends JavaScriptVisitor<ExecutionContext> {
21
+ protected async visitObjectBindingPattern(pattern: JS.ObjectBindingPattern, p: ExecutionContext): Promise<J | undefined> {
22
+ // First, recursively visit children
23
+ const visited = await super.visitObjectBindingPattern(pattern, p) as JS.ObjectBindingPattern;
24
+
25
+ // Now simplify any properties where key === value
26
+ const simplifiedBindings = visited.bindings.elements.map(right => {
27
+ const element = right.element;
28
+
29
+ // Check if this is a property assignment pattern (e.g., { x: x })
30
+ if (element.kind === JS.Kind.BindingElement) {
31
+ const binding = element as JS.BindingElement;
32
+
33
+ // Check if the property name is an identifier
34
+ if (binding.propertyName && binding.propertyName.element.kind === J.Kind.Identifier) {
35
+ const propName = (binding.propertyName.element as J.Identifier).simpleName;
36
+
37
+ // Check if the assignment is also an identifier with the same name
38
+ if (binding.name && binding.name.kind === J.Kind.Identifier) {
39
+ const assignmentName = (binding.name as J.Identifier).simpleName;
40
+
41
+ // If they match, simplify to just the identifier
42
+ if (propName === assignmentName) {
43
+ // Create a new binding element with propertyName set to undefined (shorthand syntax)
44
+ // Also need to update the name's prefix to remove extra spacing
45
+ return {
46
+ ...right,
47
+ element: {
48
+ ...binding,
49
+ propertyName: undefined,
50
+ name: {
51
+ ...binding.name,
52
+ prefix: binding.propertyName!.element.prefix
53
+ }
54
+ }
55
+ } as J.RightPadded<JS.BindingElement>;
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ return right;
62
+ });
63
+
64
+ // Return the pattern with simplified bindings using Mutative
65
+ return create(visited, draft => {
66
+ draft.bindings.elements = simplifiedBindings;
67
+ });
68
+ }
69
+ }();
70
+ }
71
+ }