@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.
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +130 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/change-component-prop-value.d.ts +20 -0
- package/dist/migration/change-component-prop-value.d.ts.map +1 -0
- package/dist/migration/change-component-prop-value.js +217 -0
- package/dist/migration/change-component-prop-value.js.map +1 -0
- package/dist/react-native/view-prop-types.d.ts +8 -0
- package/dist/react-native/view-prop-types.d.ts.map +1 -0
- package/dist/react-native/view-prop-types.js +65 -0
- package/dist/react-native/view-prop-types.js.map +1 -0
- package/dist/react16/error-boundaries.d.ts +8 -0
- package/dist/react16/error-boundaries.d.ts.map +1 -0
- package/dist/react16/error-boundaries.js +42 -0
- package/dist/react16/error-boundaries.js.map +1 -0
- package/dist/react16/find-dom-node.d.ts +8 -0
- package/dist/react16/find-dom-node.d.ts.map +1 -0
- package/dist/react16/find-dom-node.js +48 -0
- package/dist/react16/find-dom-node.js.map +1 -0
- package/dist/react16/react-dom-factories.d.ts +8 -0
- package/dist/react16/react-dom-factories.d.ts.map +1 -0
- package/dist/react16/react-dom-factories.js +72 -0
- package/dist/react16/react-dom-factories.js.map +1 -0
- package/dist/react16/react-prop-types.d.ts +8 -0
- package/dist/react16/react-prop-types.d.ts.map +1 -0
- package/dist/react16/react-prop-types.js +69 -0
- package/dist/react16/react-prop-types.js.map +1 -0
- package/dist/react16/react-to-react-dom.d.ts +8 -0
- package/dist/react16/react-to-react-dom.d.ts.map +1 -0
- package/dist/react16/react-to-react-dom.js +97 -0
- package/dist/react16/react-to-react-dom.js.map +1 -0
- package/dist/react16/replace-create-factory.d.ts +8 -0
- package/dist/react16/replace-create-factory.d.ts.map +1 -0
- package/dist/react16/replace-create-factory.js +69 -0
- package/dist/react16/replace-create-factory.js.map +1 -0
- package/dist/react16/upgrade-to-react-16.d.ts +8 -0
- package/dist/react16/upgrade-to-react-16.d.ts.map +1 -0
- package/dist/react16/upgrade-to-react-16.js +41 -0
- package/dist/react16/upgrade-to-react-16.js.map +1 -0
- package/dist/react17/remove-event-persist.d.ts +8 -0
- package/dist/react17/remove-event-persist.d.ts.map +1 -0
- package/dist/react17/remove-event-persist.js +114 -0
- package/dist/react17/remove-event-persist.js.map +1 -0
- package/dist/react17/rename-unsafe-lifecycles.d.ts +8 -0
- package/dist/react17/rename-unsafe-lifecycles.d.ts.map +1 -0
- package/dist/react17/rename-unsafe-lifecycles.js +48 -0
- package/dist/react17/rename-unsafe-lifecycles.js.map +1 -0
- package/dist/react17/update-react-imports.d.ts +8 -0
- package/dist/react17/update-react-imports.d.ts.map +1 -0
- package/dist/react17/update-react-imports.js +40 -0
- package/dist/react17/update-react-imports.js.map +1 -0
- package/dist/react17/upgrade-to-react-17.d.ts +8 -0
- package/dist/react17/upgrade-to-react-17.d.ts.map +1 -0
- package/dist/react17/upgrade-to-react-17.js +37 -0
- package/dist/react17/upgrade-to-react-17.js.map +1 -0
- package/dist/react18/remove-unstable-batched-updates.d.ts +8 -0
- package/dist/react18/remove-unstable-batched-updates.d.ts.map +1 -0
- package/dist/react18/remove-unstable-batched-updates.js +170 -0
- package/dist/react18/remove-unstable-batched-updates.js.map +1 -0
- package/dist/react18/replace-reactdom-render.d.ts +8 -0
- package/dist/react18/replace-reactdom-render.d.ts.map +1 -0
- package/dist/react18/replace-reactdom-render.js +55 -0
- package/dist/react18/replace-reactdom-render.js.map +1 -0
- package/dist/react18/replace-render-callback.d.ts +8 -0
- package/dist/react18/replace-render-callback.d.ts.map +1 -0
- package/dist/react18/replace-render-callback.js +60 -0
- package/dist/react18/replace-render-callback.js.map +1 -0
- package/dist/react18/replace-unmount-component-at-node.d.ts +8 -0
- package/dist/react18/replace-unmount-component-at-node.d.ts.map +1 -0
- package/dist/react18/replace-unmount-component-at-node.js +54 -0
- package/dist/react18/replace-unmount-component-at-node.js.map +1 -0
- package/dist/react18/upgrade-to-react-18.d.ts +8 -0
- package/dist/react18/upgrade-to-react-18.d.ts.map +1 -0
- package/dist/react18/upgrade-to-react-18.js +39 -0
- package/dist/react18/upgrade-to-react-18.js.map +1 -0
- package/dist/react19/deprecated-react-types.d.ts +8 -0
- package/dist/react19/deprecated-react-types.d.ts.map +1 -0
- package/dist/react19/deprecated-react-types.js +135 -0
- package/dist/react19/deprecated-react-types.js.map +1 -0
- package/dist/react19/find-context-consumer.d.ts +9 -0
- package/dist/react19/find-context-consumer.d.ts.map +1 -0
- package/dist/react19/find-context-consumer.js +128 -0
- package/dist/react19/find-context-consumer.js.map +1 -0
- package/dist/react19/find-deprecated-reactdom-apis.d.ts +9 -0
- package/dist/react19/find-deprecated-reactdom-apis.d.ts.map +1 -0
- package/dist/react19/find-deprecated-reactdom-apis.js +132 -0
- package/dist/react19/find-deprecated-reactdom-apis.js.map +1 -0
- package/dist/react19/find-element-ref.d.ts +9 -0
- package/dist/react19/find-element-ref.d.ts.map +1 -0
- package/dist/react19/find-element-ref.js +88 -0
- package/dist/react19/find-element-ref.js.map +1 -0
- package/dist/react19/find-legacy-context-api.d.ts +9 -0
- package/dist/react19/find-legacy-context-api.d.ts.map +1 -0
- package/dist/react19/find-legacy-context-api.js +163 -0
- package/dist/react19/find-legacy-context-api.js.map +1 -0
- package/dist/react19/no-implicit-ref-callback-return.d.ts +8 -0
- package/dist/react19/no-implicit-ref-callback-return.d.ts.map +1 -0
- package/dist/react19/no-implicit-ref-callback-return.js +107 -0
- package/dist/react19/no-implicit-ref-callback-return.js.map +1 -0
- package/dist/react19/remove-context-provider.d.ts +8 -0
- package/dist/react19/remove-context-provider.d.ts.map +1 -0
- package/dist/react19/remove-context-provider.js +59 -0
- package/dist/react19/remove-context-provider.js.map +1 -0
- package/dist/react19/remove-forward-ref.d.ts +8 -0
- package/dist/react19/remove-forward-ref.d.ts.map +1 -0
- package/dist/react19/remove-forward-ref.js +73 -0
- package/dist/react19/remove-forward-ref.js.map +1 -0
- package/dist/react19/remove-prop-types.d.ts +8 -0
- package/dist/react19/remove-prop-types.d.ts.map +1 -0
- package/dist/react19/remove-prop-types.js +76 -0
- package/dist/react19/remove-prop-types.js.map +1 -0
- package/dist/react19/remove-react-fc.d.ts +8 -0
- package/dist/react19/remove-react-fc.d.ts.map +1 -0
- package/dist/react19/remove-react-fc.js +149 -0
- package/dist/react19/remove-react-fc.js.map +1 -0
- package/dist/react19/replace-act-import.d.ts +9 -0
- package/dist/react19/replace-act-import.d.ts.map +1 -0
- package/dist/react19/replace-act-import.js +34 -0
- package/dist/react19/replace-act-import.js.map +1 -0
- package/dist/react19/replace-default-props.d.ts +8 -0
- package/dist/react19/replace-default-props.d.ts.map +1 -0
- package/dist/react19/replace-default-props.js +195 -0
- package/dist/react19/replace-default-props.js.map +1 -0
- package/dist/react19/replace-react-shallow-renderer.d.ts +8 -0
- package/dist/react19/replace-react-shallow-renderer.d.ts.map +1 -0
- package/dist/react19/replace-react-shallow-renderer.js +69 -0
- package/dist/react19/replace-react-shallow-renderer.js.map +1 -0
- package/dist/react19/replace-reactdom-hydrate.d.ts +8 -0
- package/dist/react19/replace-reactdom-hydrate.d.ts.map +1 -0
- package/dist/react19/replace-reactdom-hydrate.js +55 -0
- package/dist/react19/replace-reactdom-hydrate.js.map +1 -0
- package/dist/react19/replace-string-ref.d.ts +8 -0
- package/dist/react19/replace-string-ref.d.ts.map +1 -0
- package/dist/react19/replace-string-ref.js +75 -0
- package/dist/react19/replace-string-ref.js.map +1 -0
- package/dist/react19/replace-use-form-state.d.ts +8 -0
- package/dist/react19/replace-use-form-state.d.ts.map +1 -0
- package/dist/react19/replace-use-form-state.js +54 -0
- package/dist/react19/replace-use-form-state.js.map +1 -0
- package/dist/react19/upgrade-to-react-19.d.ts +8 -0
- package/dist/react19/upgrade-to-react-19.d.ts.map +1 -0
- package/dist/react19/upgrade-to-react-19.js +59 -0
- package/dist/react19/upgrade-to-react-19.js.map +1 -0
- package/dist/react19/use-context-hook.d.ts +8 -0
- package/dist/react19/use-context-hook.d.ts.map +1 -0
- package/dist/react19/use-context-hook.js +54 -0
- package/dist/react19/use-context-hook.js.map +1 -0
- package/dist/react19/use-ref-required-initial.d.ts +8 -0
- package/dist/react19/use-ref-required-initial.d.ts.map +1 -0
- package/dist/react19/use-ref-required-initial.js +74 -0
- package/dist/react19/use-ref-required-initial.js.map +1 -0
- package/dist/refactoring/class-to-functional.d.ts +8 -0
- package/dist/refactoring/class-to-functional.d.ts.map +1 -0
- package/dist/refactoring/class-to-functional.js +205 -0
- package/dist/refactoring/class-to-functional.js.map +1 -0
- package/dist/refactoring/create-class-to-es6.d.ts +8 -0
- package/dist/refactoring/create-class-to-es6.d.ts.map +1 -0
- package/dist/refactoring/create-class-to-es6.js +289 -0
- package/dist/refactoring/create-class-to-es6.js.map +1 -0
- package/dist/refactoring/create-element-to-jsx.d.ts +8 -0
- package/dist/refactoring/create-element-to-jsx.d.ts.map +1 -0
- package/dist/refactoring/create-element-to-jsx.js +167 -0
- package/dist/refactoring/create-element-to-jsx.js.map +1 -0
- package/dist/refactoring/manual-bind-to-arrow.d.ts +8 -0
- package/dist/refactoring/manual-bind-to-arrow.d.ts.map +1 -0
- package/dist/refactoring/manual-bind-to-arrow.js +134 -0
- package/dist/refactoring/manual-bind-to-arrow.js.map +1 -0
- package/dist/refactoring/pure-render-mixin.d.ts +8 -0
- package/dist/refactoring/pure-render-mixin.d.ts.map +1 -0
- package/dist/refactoring/pure-render-mixin.js +253 -0
- package/dist/refactoring/pure-render-mixin.js.map +1 -0
- package/dist/refactoring/sort-comp.d.ts +8 -0
- package/dist/refactoring/sort-comp.d.ts.map +1 -0
- package/dist/refactoring/sort-comp.js +128 -0
- package/dist/refactoring/sort-comp.js.map +1 -0
- package/dist/search/find-hook-usage.d.ts +9 -0
- package/dist/search/find-hook-usage.d.ts.map +1 -0
- package/dist/search/find-hook-usage.js +262 -0
- package/dist/search/find-hook-usage.js.map +1 -0
- package/dist/search/find-prop-usage.d.ts +15 -0
- package/dist/search/find-prop-usage.d.ts.map +1 -0
- package/dist/search/find-prop-usage.js +177 -0
- package/dist/search/find-prop-usage.js.map +1 -0
- package/dist/search/find-react-component.d.ts +15 -0
- package/dist/search/find-react-component.d.ts.map +1 -0
- package/dist/search/find-react-component.js +260 -0
- package/dist/search/find-react-component.js.map +1 -0
- package/dist/search/find-server-rendering-usage.d.ts +9 -0
- package/dist/search/find-server-rendering-usage.d.ts.map +1 -0
- package/dist/search/find-server-rendering-usage.js +131 -0
- package/dist/search/find-server-rendering-usage.js.map +1 -0
- package/dist/simplify-object-pattern-property.d.ts +8 -0
- package/dist/simplify-object-pattern-property.d.ts.map +1 -0
- package/dist/simplify-object-pattern-property.js +59 -0
- package/dist/simplify-object-pattern-property.js.map +1 -0
- package/dist/simplify-react-imports.d.ts +8 -0
- package/dist/simplify-react-imports.d.ts.map +1 -0
- package/dist/simplify-react-imports.js +199 -0
- package/dist/simplify-react-imports.js.map +1 -0
- package/package.json +39 -0
- package/src/index.ts +149 -0
- package/src/migration/change-component-prop-value.ts +268 -0
- package/src/react-native/view-prop-types.ts +63 -0
- package/src/react16/error-boundaries.ts +46 -0
- package/src/react16/find-dom-node.ts +55 -0
- package/src/react16/react-dom-factories.ts +99 -0
- package/src/react16/react-prop-types.ts +71 -0
- package/src/react16/react-to-react-dom.ts +104 -0
- package/src/react16/replace-create-factory.ts +96 -0
- package/src/react16/upgrade-to-react-16.ts +37 -0
- package/src/react17/remove-event-persist.ts +121 -0
- package/src/react17/rename-unsafe-lifecycles.ts +57 -0
- package/src/react17/update-react-imports.ts +50 -0
- package/src/react17/upgrade-to-react-17.ts +30 -0
- package/src/react18/remove-unstable-batched-updates.ts +192 -0
- package/src/react18/replace-reactdom-render.ts +68 -0
- package/src/react18/replace-render-callback.ts +66 -0
- package/src/react18/replace-unmount-component-at-node.ts +66 -0
- package/src/react18/upgrade-to-react-18.ts +33 -0
- package/src/react19/deprecated-react-types.ts +120 -0
- package/src/react19/find-context-consumer.ts +127 -0
- package/src/react19/find-deprecated-reactdom-apis.ts +125 -0
- package/src/react19/find-element-ref.ts +86 -0
- package/src/react19/find-legacy-context-api.ts +157 -0
- package/src/react19/no-implicit-ref-callback-return.ts +123 -0
- package/src/react19/remove-context-provider.ts +87 -0
- package/src/react19/remove-forward-ref.ts +69 -0
- package/src/react19/remove-prop-types.ts +86 -0
- package/src/react19/remove-react-fc.ts +247 -0
- package/src/react19/replace-act-import.ts +36 -0
- package/src/react19/replace-default-props.ts +220 -0
- package/src/react19/replace-react-shallow-renderer.ts +75 -0
- package/src/react19/replace-reactdom-hydrate.ts +67 -0
- package/src/react19/replace-string-ref.ts +89 -0
- package/src/react19/replace-use-form-state.ts +66 -0
- package/src/react19/upgrade-to-react-19.ts +66 -0
- package/src/react19/use-context-hook.ts +67 -0
- package/src/react19/use-ref-required-initial.ts +75 -0
- package/src/refactoring/class-to-functional.ts +229 -0
- package/src/refactoring/create-class-to-es6.ts +309 -0
- package/src/refactoring/create-element-to-jsx.ts +200 -0
- package/src/refactoring/manual-bind-to-arrow.ts +139 -0
- package/src/refactoring/pure-render-mixin.ts +346 -0
- package/src/refactoring/sort-comp.ts +135 -0
- package/src/search/find-hook-usage.ts +226 -0
- package/src/search/find-prop-usage.ts +176 -0
- package/src/search/find-react-component.ts +254 -0
- package/src/search/find-server-rendering-usage.ts +120 -0
- package/src/simplify-object-pattern-property.ts +71 -0
- package/src/simplify-react-imports.ts +241 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {ExecutionContext, printer, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS, raw, template} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts `React.createElement()` calls to JSX syntax.
|
|
7
|
+
*
|
|
8
|
+
* Handles:
|
|
9
|
+
* - String tags: `createElement('div', ...)` → `<div ... />`
|
|
10
|
+
* - Component tags: `createElement(MyComponent, ...)` → `<MyComponent ... />`
|
|
11
|
+
* - Props objects: `{ className: 'foo' }` → `className="foo"`
|
|
12
|
+
* - String children: `'Hello'` → text node
|
|
13
|
+
* - Expression children: `{expr}` → `{expr}`
|
|
14
|
+
* - Nested createElement calls are handled recursively
|
|
15
|
+
*
|
|
16
|
+
* Before:
|
|
17
|
+
* ```tsx
|
|
18
|
+
* React.createElement('div', { className: 'foo' }, 'Hello')
|
|
19
|
+
* React.createElement(MyComponent, { name: 'World' })
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* After:
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <div className="foo">Hello</div>
|
|
25
|
+
* <MyComponent name="World" />
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @see https://github.com/reactjs/react-codemod#create-element-to-jsx
|
|
29
|
+
*/
|
|
30
|
+
export class CreateElementToJsx extends Recipe {
|
|
31
|
+
readonly name = "org.openrewrite.react.refactoring.create-element-to-jsx";
|
|
32
|
+
readonly displayName: string = "Convert `createElement` to JSX";
|
|
33
|
+
readonly description: string = "Converts `React.createElement()` calls to JSX syntax for improved readability.";
|
|
34
|
+
|
|
35
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
36
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
37
|
+
override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
|
|
38
|
+
let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
|
|
39
|
+
|
|
40
|
+
if (!this.isCreateElementCall(m)) return m;
|
|
41
|
+
|
|
42
|
+
const args = m.arguments.elements;
|
|
43
|
+
if (args.length === 0) return m;
|
|
44
|
+
|
|
45
|
+
// Extract tag name
|
|
46
|
+
const tagName = this.getTagName(args[0].element);
|
|
47
|
+
if (!tagName) return m;
|
|
48
|
+
|
|
49
|
+
// Extract props
|
|
50
|
+
const propsStr = args.length > 1
|
|
51
|
+
? await this.serializeProps(args[1].element)
|
|
52
|
+
: '';
|
|
53
|
+
|
|
54
|
+
// Extract children
|
|
55
|
+
const childArgs = args.length > 2
|
|
56
|
+
? args.slice(2).map(a => a.element)
|
|
57
|
+
: [];
|
|
58
|
+
const childrenStr = await this.serializeChildren(childArgs);
|
|
59
|
+
|
|
60
|
+
// Build JSX
|
|
61
|
+
let jsxSource: string;
|
|
62
|
+
if (childrenStr) {
|
|
63
|
+
jsxSource = `<${tagName}${propsStr}>${childrenStr}</${tagName}>`;
|
|
64
|
+
} else {
|
|
65
|
+
jsxSource = `<${tagName}${propsStr} />`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return await template`${raw(jsxSource)}`.apply(m, this.cursor);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private isCreateElementCall(method: J.MethodInvocation): boolean {
|
|
72
|
+
if (method.name.simpleName !== 'createElement') return false;
|
|
73
|
+
|
|
74
|
+
// React.createElement(...)
|
|
75
|
+
if (method.select?.element?.kind === J.Kind.Identifier) {
|
|
76
|
+
return (method.select.element as J.Identifier).simpleName === 'React';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private getTagName(expr: any): string | undefined {
|
|
83
|
+
// String literal: 'div', 'span', etc.
|
|
84
|
+
if (expr.kind === J.Kind.Literal) {
|
|
85
|
+
const lit = expr as J.Literal;
|
|
86
|
+
if (typeof lit.value === 'string') {
|
|
87
|
+
return lit.value;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Identifier: MyComponent
|
|
93
|
+
if (expr.kind === J.Kind.Identifier) {
|
|
94
|
+
return (expr as J.Identifier).simpleName;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// FieldAccess: Foo.Bar
|
|
98
|
+
if (expr.kind === J.Kind.FieldAccess) {
|
|
99
|
+
const fa = expr as J.FieldAccess;
|
|
100
|
+
if (fa.target.kind === J.Kind.Identifier) {
|
|
101
|
+
return `${(fa.target as J.Identifier).simpleName}.${fa.name.element.simpleName}`;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async serializeProps(propsExpr: any): Promise<string> {
|
|
110
|
+
// null or undefined → no props
|
|
111
|
+
if (propsExpr.kind === J.Kind.Literal) {
|
|
112
|
+
const lit = propsExpr as J.Literal;
|
|
113
|
+
if (lit.value === null) return '';
|
|
114
|
+
}
|
|
115
|
+
if (propsExpr.kind === J.Kind.Identifier) {
|
|
116
|
+
if ((propsExpr as J.Identifier).simpleName === 'undefined') return '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Object literal (represented as J.NewClass with body)
|
|
120
|
+
if (propsExpr.kind === J.Kind.NewClass) {
|
|
121
|
+
const obj = propsExpr as J.NewClass;
|
|
122
|
+
if (!obj.body) return '';
|
|
123
|
+
|
|
124
|
+
const attrs: string[] = [];
|
|
125
|
+
for (const stmt of obj.body.statements) {
|
|
126
|
+
const s = stmt.element;
|
|
127
|
+
|
|
128
|
+
if (s.kind === JS.Kind.PropertyAssignment) {
|
|
129
|
+
const prop = s as JS.PropertyAssignment;
|
|
130
|
+
const keyExpr = prop.name.element;
|
|
131
|
+
let key: string | undefined;
|
|
132
|
+
|
|
133
|
+
if (keyExpr.kind === J.Kind.Identifier) {
|
|
134
|
+
key = (keyExpr as J.Identifier).simpleName;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (key && prop.initializer) {
|
|
138
|
+
const value = prop.initializer;
|
|
139
|
+
|
|
140
|
+
// String literal values → key="value"
|
|
141
|
+
if (value.kind === J.Kind.Literal) {
|
|
142
|
+
const lit = value as J.Literal;
|
|
143
|
+
if (typeof lit.value === 'string') {
|
|
144
|
+
attrs.push(`${key}="${lit.value}"`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Other values → key={value}
|
|
150
|
+
const valueSource = await printer(this.cursor).print(value);
|
|
151
|
+
attrs.push(`${key}={${valueSource.trim()}}`);
|
|
152
|
+
} else if (key && !prop.initializer) {
|
|
153
|
+
// Shorthand: { disabled } → disabled
|
|
154
|
+
attrs.push(key);
|
|
155
|
+
}
|
|
156
|
+
} else if (s.kind === JS.Kind.Spread) {
|
|
157
|
+
// Spread: {...rest} → {...rest}
|
|
158
|
+
const spread = s as JS.Spread;
|
|
159
|
+
const spreadSource = await printer(this.cursor).print(spread.expression);
|
|
160
|
+
attrs.push(`{...${spreadSource.trim()}}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Non-object props (variable reference) → {...props}
|
|
168
|
+
const source = await printer(this.cursor).print(propsExpr);
|
|
169
|
+
return ` {...${source.trim()}}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async serializeChildren(children: any[]): Promise<string> {
|
|
173
|
+
if (children.length === 0) return '';
|
|
174
|
+
|
|
175
|
+
const parts: string[] = [];
|
|
176
|
+
for (const child of children) {
|
|
177
|
+
// String literal children → text node
|
|
178
|
+
if (child.kind === J.Kind.Literal) {
|
|
179
|
+
const lit = child as J.Literal;
|
|
180
|
+
if (typeof lit.value === 'string') {
|
|
181
|
+
// Escape HTML special characters
|
|
182
|
+
const escaped = (lit.value as string)
|
|
183
|
+
.replace(/&/g, '&')
|
|
184
|
+
.replace(/</g, '<')
|
|
185
|
+
.replace(/>/g, '>');
|
|
186
|
+
parts.push(escaped);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Other expressions → {expression}
|
|
192
|
+
const source = await printer(this.cursor).print(child);
|
|
193
|
+
parts.push(`{${source.trim()}}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return parts.join('');
|
|
197
|
+
}
|
|
198
|
+
}();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts manual `.bind(this)` in constructors to class field arrow functions.
|
|
7
|
+
*
|
|
8
|
+
* Before:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* class MyComponent extends React.Component {
|
|
11
|
+
* constructor(props) {
|
|
12
|
+
* super(props);
|
|
13
|
+
* this.handleClick = this.handleClick.bind(this);
|
|
14
|
+
* }
|
|
15
|
+
* handleClick() { ... }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* After:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* class MyComponent extends React.Component {
|
|
22
|
+
* constructor(props) {
|
|
23
|
+
* super(props);
|
|
24
|
+
* }
|
|
25
|
+
* handleClick = () => { ... };
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @see https://github.com/reactjs/react-codemod#manual-bind-to-arrow-transform
|
|
30
|
+
*/
|
|
31
|
+
export class ManualBindToArrow extends Recipe {
|
|
32
|
+
readonly name = "org.openrewrite.react.refactoring.manual-bind-to-arrow";
|
|
33
|
+
readonly displayName: string = "Convert manual `.bind(this)` to arrow functions";
|
|
34
|
+
readonly description: string = "Converts `this.method = this.method.bind(this)` in constructors to class field arrow function syntax.";
|
|
35
|
+
|
|
36
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
37
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
38
|
+
private boundMethods = new Set<string>();
|
|
39
|
+
|
|
40
|
+
override async visitClassDeclaration(classDecl: J.ClassDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
|
|
41
|
+
this.boundMethods.clear();
|
|
42
|
+
|
|
43
|
+
// First pass: find bind statements in constructor
|
|
44
|
+
await this.findBindStatements(classDecl);
|
|
45
|
+
|
|
46
|
+
if (this.boundMethods.size === 0) {
|
|
47
|
+
return classDecl;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Second pass: remove bind statements and convert methods to arrow functions
|
|
51
|
+
return await super.visitClassDeclaration(classDecl, ctx);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async findBindStatements(classDecl: J.ClassDeclaration): Promise<void> {
|
|
55
|
+
const body = classDecl.body;
|
|
56
|
+
if (!body) return;
|
|
57
|
+
|
|
58
|
+
for (const stmt of body.statements) {
|
|
59
|
+
const s = stmt.element;
|
|
60
|
+
if (!s) continue;
|
|
61
|
+
|
|
62
|
+
if (s.kind === J.Kind.MethodDeclaration) {
|
|
63
|
+
const method = s as J.MethodDeclaration;
|
|
64
|
+
if (method.name.simpleName === 'constructor') {
|
|
65
|
+
// Look for this.x = this.x.bind(this) in constructor body
|
|
66
|
+
if (method.body) {
|
|
67
|
+
for (const bodyStmt of method.body.statements) {
|
|
68
|
+
const bs = bodyStmt.element;
|
|
69
|
+
if (!bs) continue;
|
|
70
|
+
this.checkForBindStatement(bs);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private checkForBindStatement(stmt: J): void {
|
|
79
|
+
// Match: this.x = this.x.bind(this)
|
|
80
|
+
if (stmt.kind !== J.Kind.Assignment) return;
|
|
81
|
+
const assignment = stmt as J.Assignment;
|
|
82
|
+
|
|
83
|
+
// LHS: this.x
|
|
84
|
+
if (assignment.variable.kind !== J.Kind.FieldAccess) return;
|
|
85
|
+
const lhs = assignment.variable as J.FieldAccess;
|
|
86
|
+
if (lhs.target.kind !== J.Kind.Identifier) return;
|
|
87
|
+
if ((lhs.target as J.Identifier).simpleName !== 'this') return;
|
|
88
|
+
const methodName = lhs.name.element.simpleName;
|
|
89
|
+
|
|
90
|
+
// RHS: this.x.bind(this)
|
|
91
|
+
const rhs = assignment.assignment.element;
|
|
92
|
+
if (!rhs || rhs.kind !== J.Kind.MethodInvocation) return;
|
|
93
|
+
const call = rhs as J.MethodInvocation;
|
|
94
|
+
if (call.name.simpleName !== 'bind') return;
|
|
95
|
+
|
|
96
|
+
// Check select is this.x
|
|
97
|
+
if (!call.select) return;
|
|
98
|
+
const select = (call.select as any).element ?? call.select;
|
|
99
|
+
if (select.kind !== J.Kind.FieldAccess) return;
|
|
100
|
+
const selectFa = select as J.FieldAccess;
|
|
101
|
+
if (selectFa.target.kind !== J.Kind.Identifier) return;
|
|
102
|
+
if ((selectFa.target as J.Identifier).simpleName !== 'this') return;
|
|
103
|
+
if (selectFa.name.element.simpleName !== methodName) return;
|
|
104
|
+
|
|
105
|
+
this.boundMethods.add(methodName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
override async visitMethodDeclaration(method: J.MethodDeclaration, ctx: ExecutionContext): Promise<J | undefined> {
|
|
109
|
+
let m = await super.visitMethodDeclaration(method, ctx) as J.MethodDeclaration;
|
|
110
|
+
|
|
111
|
+
// Remove bind statements from constructor
|
|
112
|
+
if (m.name.simpleName === 'constructor' && m.body && this.boundMethods.size > 0) {
|
|
113
|
+
const filteredStatements = m.body.statements.filter(stmt => {
|
|
114
|
+
const s = stmt.element;
|
|
115
|
+
if (!s || s.kind !== J.Kind.Assignment) return true;
|
|
116
|
+
const assignment = s as J.Assignment;
|
|
117
|
+
if (assignment.variable.kind !== J.Kind.FieldAccess) return true;
|
|
118
|
+
const fa = assignment.variable as J.FieldAccess;
|
|
119
|
+
if (fa.target.kind !== J.Kind.Identifier) return true;
|
|
120
|
+
if ((fa.target as J.Identifier).simpleName !== 'this') return true;
|
|
121
|
+
return !this.boundMethods.has(fa.name.element.simpleName);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (filteredStatements.length !== m.body.statements.length) {
|
|
125
|
+
m = {
|
|
126
|
+
...m,
|
|
127
|
+
body: {
|
|
128
|
+
...m.body,
|
|
129
|
+
statements: filteredStatements
|
|
130
|
+
}
|
|
131
|
+
} as any as J.MethodDeclaration;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return m;
|
|
136
|
+
}
|
|
137
|
+
}();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {ExecutionContext, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
2
|
+
import {JavaScriptVisitor, JS, maybeRemoveImport, raw, template} from "@openrewrite/rewrite/javascript";
|
|
3
|
+
import {J} from "@openrewrite/rewrite/java";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Removes `PureRenderMixin` from `React.createClass` components and replaces it
|
|
7
|
+
* with an explicit `shouldComponentUpdate` method using `shallowCompare`.
|
|
8
|
+
*
|
|
9
|
+
* Before:
|
|
10
|
+
* ```tsx
|
|
11
|
+
* var PureRenderMixin = require('react-addons-pure-render-mixin');
|
|
12
|
+
* var MyComponent = React.createClass({
|
|
13
|
+
* mixins: [PureRenderMixin],
|
|
14
|
+
* render() { return <div />; }
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* After:
|
|
19
|
+
* ```tsx
|
|
20
|
+
* var MyComponent = React.createClass({
|
|
21
|
+
* shouldComponentUpdate(nextProps, nextState) {
|
|
22
|
+
* return React.addons.shallowCompare(this, nextProps, nextState);
|
|
23
|
+
* },
|
|
24
|
+
* render() { return <div />; }
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @see https://github.com/reactjs/react-codemod#pure-render-mixin
|
|
29
|
+
*/
|
|
30
|
+
export class PureRenderMixin extends Recipe {
|
|
31
|
+
readonly name = "org.openrewrite.react.refactoring.pure-render-mixin";
|
|
32
|
+
readonly displayName: string = "Remove `PureRenderMixin`";
|
|
33
|
+
readonly description: string = "Removes `PureRenderMixin` from `React.createClass` mixins and adds an explicit `shouldComponentUpdate` method.";
|
|
34
|
+
|
|
35
|
+
async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
36
|
+
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
37
|
+
private transformed = false;
|
|
38
|
+
|
|
39
|
+
override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
|
|
40
|
+
this.transformed = false;
|
|
41
|
+
const result = await super.visitJsCompilationUnit(cu, ctx);
|
|
42
|
+
if (this.transformed) {
|
|
43
|
+
maybeRemoveImport(this, "react-addons-pure-render-mixin", "default");
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
|
|
49
|
+
let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
|
|
50
|
+
|
|
51
|
+
if (!this.isCreateClassCall(m)) return m;
|
|
52
|
+
|
|
53
|
+
const args = m.arguments.elements;
|
|
54
|
+
if (args.length === 0) return m;
|
|
55
|
+
|
|
56
|
+
const specArg = args[0].element;
|
|
57
|
+
if (specArg.kind !== J.Kind.NewClass) return m;
|
|
58
|
+
|
|
59
|
+
const specObj = specArg as J.NewClass;
|
|
60
|
+
if (!specObj.body) return m;
|
|
61
|
+
|
|
62
|
+
// Find the mixins property and check for PureRenderMixin
|
|
63
|
+
const mixinsInfo = this.findPureRenderMixin(specObj.body);
|
|
64
|
+
if (!mixinsInfo) return m;
|
|
65
|
+
|
|
66
|
+
// Check that shouldComponentUpdate doesn't already exist
|
|
67
|
+
if (this.hasShouldComponentUpdate(specObj.body)) return m;
|
|
68
|
+
|
|
69
|
+
// Get member indent from existing members
|
|
70
|
+
const memberIndent = this.getMemberIndent(specObj.body);
|
|
71
|
+
|
|
72
|
+
// Build shouldComponentUpdate by cloning an existing method's structure
|
|
73
|
+
// and replacing its internals with the desired content
|
|
74
|
+
const existingMethod = this.findAnyMethod(specObj.body);
|
|
75
|
+
if (!existingMethod) return m;
|
|
76
|
+
|
|
77
|
+
// Create the return expression via template
|
|
78
|
+
const returnExpr = await template`React.addons.shallowCompare(this, nextProps, nextState)`.apply(specObj, this.cursor);
|
|
79
|
+
if (!returnExpr) return m;
|
|
80
|
+
|
|
81
|
+
// Create parameter identifiers via template
|
|
82
|
+
const nextPropsId = await template`nextProps`.apply(specObj, this.cursor);
|
|
83
|
+
const nextStateId = await template`nextState`.apply(specObj, this.cursor);
|
|
84
|
+
if (!nextPropsId || !nextStateId) return m;
|
|
85
|
+
|
|
86
|
+
// Build the shouldComponentUpdate method by cloning structure from existing method
|
|
87
|
+
const scuMethod = this.buildMethodFromTemplate(
|
|
88
|
+
existingMethod,
|
|
89
|
+
'shouldComponentUpdate',
|
|
90
|
+
[nextPropsId, nextStateId],
|
|
91
|
+
returnExpr
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Build new statements: remove mixins (or PureRenderMixin from it), add shouldComponentUpdate
|
|
95
|
+
const newStatements = this.buildNewStatements(
|
|
96
|
+
specObj.body.statements,
|
|
97
|
+
mixinsInfo,
|
|
98
|
+
scuMethod,
|
|
99
|
+
memberIndent
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
this.transformed = true;
|
|
103
|
+
|
|
104
|
+
// Return updated method invocation with modified spec object
|
|
105
|
+
return {
|
|
106
|
+
...m,
|
|
107
|
+
arguments: {
|
|
108
|
+
...m.arguments,
|
|
109
|
+
elements: [
|
|
110
|
+
{
|
|
111
|
+
...args[0],
|
|
112
|
+
element: {
|
|
113
|
+
...specObj,
|
|
114
|
+
body: {
|
|
115
|
+
...specObj.body,
|
|
116
|
+
statements: newStatements
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
...args.slice(1)
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
} as any as J.MethodInvocation;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private isCreateClassCall(method: J.MethodInvocation): boolean {
|
|
127
|
+
if (method.name.simpleName === 'createClass' && method.select) {
|
|
128
|
+
const select = (method.select as any).element ?? method.select;
|
|
129
|
+
if (select.kind === J.Kind.Identifier) {
|
|
130
|
+
return (select as J.Identifier).simpleName === 'React';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (method.name.simpleName === 'createReactClass' && !method.select) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private findPureRenderMixin(body: J.Block): { stmtIndex: number; isOnlyMixin: boolean } | undefined {
|
|
140
|
+
for (let i = 0; i < body.statements.length; i++) {
|
|
141
|
+
const s = body.statements[i].element;
|
|
142
|
+
if (s.kind !== JS.Kind.PropertyAssignment) continue;
|
|
143
|
+
|
|
144
|
+
const prop = s as JS.PropertyAssignment;
|
|
145
|
+
const keyExpr = prop.name.element;
|
|
146
|
+
if (keyExpr.kind !== J.Kind.Identifier) continue;
|
|
147
|
+
if ((keyExpr as J.Identifier).simpleName !== 'mixins') continue;
|
|
148
|
+
|
|
149
|
+
// Check if the initializer is an array containing PureRenderMixin
|
|
150
|
+
if (!prop.initializer) continue;
|
|
151
|
+
const init = prop.initializer;
|
|
152
|
+
if (init.kind !== J.Kind.NewArray) continue;
|
|
153
|
+
|
|
154
|
+
const arr = init as J.NewArray;
|
|
155
|
+
if (!arr.initializer) continue;
|
|
156
|
+
|
|
157
|
+
const elements = arr.initializer.elements ?? [];
|
|
158
|
+
const hasPRM = elements.some((el: any) => {
|
|
159
|
+
const elem = el.element ?? el;
|
|
160
|
+
return elem.kind === J.Kind.Identifier &&
|
|
161
|
+
(elem as J.Identifier).simpleName === 'PureRenderMixin';
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (hasPRM) {
|
|
165
|
+
return {
|
|
166
|
+
stmtIndex: i,
|
|
167
|
+
isOnlyMixin: elements.length === 1
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private hasShouldComponentUpdate(body: J.Block): boolean {
|
|
175
|
+
return body.statements.some(stmt => {
|
|
176
|
+
const s = stmt.element;
|
|
177
|
+
if (s.kind === J.Kind.MethodDeclaration) {
|
|
178
|
+
return (s as J.MethodDeclaration).name.simpleName === 'shouldComponentUpdate';
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private findAnyMethod(body: J.Block): J.MethodDeclaration | undefined {
|
|
185
|
+
for (const stmt of body.statements) {
|
|
186
|
+
if (stmt.element?.kind === J.Kind.MethodDeclaration) {
|
|
187
|
+
return stmt.element as J.MethodDeclaration;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private buildMethodFromTemplate(
|
|
194
|
+
existingMethod: J.MethodDeclaration,
|
|
195
|
+
name: string,
|
|
196
|
+
params: J[],
|
|
197
|
+
returnExpr: J
|
|
198
|
+
): J.MethodDeclaration {
|
|
199
|
+
// Clone the existing method structure and replace name, params, and body
|
|
200
|
+
const nameId = {
|
|
201
|
+
...existingMethod.name,
|
|
202
|
+
simpleName: name,
|
|
203
|
+
} as J.Identifier;
|
|
204
|
+
|
|
205
|
+
const emptySpace = {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: '', comments: []};
|
|
206
|
+
const singleSpace = {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: ' ', comments: []};
|
|
207
|
+
const emptyMarkers = existingMethod.markers;
|
|
208
|
+
|
|
209
|
+
// Build parameter list with proper spacing between params
|
|
210
|
+
const paramElements = params.map((param, i) => ({
|
|
211
|
+
kind: 'org.openrewrite.java.tree.JRightPadded' as const,
|
|
212
|
+
element: i > 0 ? {...(param as any), prefix: singleSpace} : param,
|
|
213
|
+
after: emptySpace,
|
|
214
|
+
markers: emptyMarkers,
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
const bodyPrefix = existingMethod.body?.prefix ?? existingMethod.prefix;
|
|
218
|
+
|
|
219
|
+
// Build return statement by wrapping the expression
|
|
220
|
+
// Add a space prefix to the expression so it renders as "return expr" not "returnexpr"
|
|
221
|
+
const returnExprWithSpace = {...(returnExpr as any), prefix: singleSpace};
|
|
222
|
+
const returnStmt: any = {
|
|
223
|
+
kind: J.Kind.Return,
|
|
224
|
+
id: (returnExpr as any).id,
|
|
225
|
+
prefix: {
|
|
226
|
+
kind: 'org.openrewrite.java.tree.Space' as const,
|
|
227
|
+
whitespace: '\n ',
|
|
228
|
+
comments: []
|
|
229
|
+
},
|
|
230
|
+
markers: emptyMarkers,
|
|
231
|
+
expression: returnExprWithSpace,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const body: J.Block = {
|
|
235
|
+
kind: J.Kind.Block,
|
|
236
|
+
id: existingMethod.body?.id ?? (existingMethod as any).id,
|
|
237
|
+
prefix: bodyPrefix,
|
|
238
|
+
markers: existingMethod.markers,
|
|
239
|
+
static: existingMethod.body?.static ?? {
|
|
240
|
+
kind: 'org.openrewrite.java.tree.JRightPadded' as const,
|
|
241
|
+
element: false,
|
|
242
|
+
after: {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: '', comments: []},
|
|
243
|
+
markers: existingMethod.markers,
|
|
244
|
+
},
|
|
245
|
+
statements: [{
|
|
246
|
+
kind: 'org.openrewrite.java.tree.JRightPadded' as const,
|
|
247
|
+
element: returnStmt,
|
|
248
|
+
after: {kind: 'org.openrewrite.java.tree.Space' as const, whitespace: '', comments: []},
|
|
249
|
+
markers: existingMethod.markers,
|
|
250
|
+
}],
|
|
251
|
+
end: {
|
|
252
|
+
kind: 'org.openrewrite.java.tree.Space' as const,
|
|
253
|
+
whitespace: '\n ',
|
|
254
|
+
comments: []
|
|
255
|
+
},
|
|
256
|
+
} as any as J.Block;
|
|
257
|
+
|
|
258
|
+
const parameters = {
|
|
259
|
+
...existingMethod.parameters,
|
|
260
|
+
elements: paramElements,
|
|
261
|
+
} as any;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
...existingMethod,
|
|
265
|
+
name: nameId,
|
|
266
|
+
parameters: parameters,
|
|
267
|
+
body: body,
|
|
268
|
+
} as any as J.MethodDeclaration;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private getMemberIndent(body: J.Block): any {
|
|
272
|
+
for (const stmt of body.statements) {
|
|
273
|
+
if (stmt.element?.prefix) return stmt.element.prefix;
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private buildNewStatements(
|
|
279
|
+
statements: any[],
|
|
280
|
+
mixinsInfo: { stmtIndex: number; isOnlyMixin: boolean },
|
|
281
|
+
scuMethod: J,
|
|
282
|
+
indent: any
|
|
283
|
+
): any[] {
|
|
284
|
+
const result: any[] = [];
|
|
285
|
+
let scuInserted = false;
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < statements.length; i++) {
|
|
288
|
+
if (i === mixinsInfo.stmtIndex) {
|
|
289
|
+
if (mixinsInfo.isOnlyMixin) {
|
|
290
|
+
// Remove the entire mixins property
|
|
291
|
+
continue;
|
|
292
|
+
} else {
|
|
293
|
+
// Remove PureRenderMixin from the array but keep other mixins
|
|
294
|
+
const stmt = statements[i];
|
|
295
|
+
const prop = stmt.element as JS.PropertyAssignment;
|
|
296
|
+
const arr = prop.initializer as J.NewArray;
|
|
297
|
+
const filteredElements = (arr.initializer!.elements as any[]).filter((el: any) => {
|
|
298
|
+
const elem = el.element ?? el;
|
|
299
|
+
return !(elem.kind === J.Kind.Identifier &&
|
|
300
|
+
(elem as J.Identifier).simpleName === 'PureRenderMixin');
|
|
301
|
+
});
|
|
302
|
+
result.push({
|
|
303
|
+
...stmt,
|
|
304
|
+
element: {
|
|
305
|
+
...prop,
|
|
306
|
+
initializer: {
|
|
307
|
+
...arr,
|
|
308
|
+
initializer: {
|
|
309
|
+
...arr.initializer,
|
|
310
|
+
elements: filteredElements
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const s = statements[i].element;
|
|
320
|
+
// Insert shouldComponentUpdate before render
|
|
321
|
+
if (!scuInserted && s.kind === J.Kind.MethodDeclaration &&
|
|
322
|
+
(s as J.MethodDeclaration).name.simpleName === 'render') {
|
|
323
|
+
const scuStmt = {
|
|
324
|
+
...statements[i],
|
|
325
|
+
element: indent ? {...(scuMethod as any), prefix: indent} : scuMethod
|
|
326
|
+
};
|
|
327
|
+
result.push(scuStmt);
|
|
328
|
+
scuInserted = true;
|
|
329
|
+
}
|
|
330
|
+
result.push(statements[i]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// If render wasn't found, append at the end
|
|
334
|
+
if (!scuInserted) {
|
|
335
|
+
const lastStmt = statements[statements.length - 1];
|
|
336
|
+
result.push({
|
|
337
|
+
...lastStmt,
|
|
338
|
+
element: indent ? {...(scuMethod as any), prefix: indent} : scuMethod
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
}();
|
|
345
|
+
}
|
|
346
|
+
}
|