Skip to content

Commit f651f2e

Browse files
committed
improve remove forward ref codemod
1 parent dd5dea5 commit f651f2e

12 files changed

+115
-129
lines changed
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const MyInput = props => {
2-
const {
3-
ref
4-
} = props;
5-
1+
const MyInput = (
2+
{
3+
ref,
4+
...props
5+
}
6+
) => {
67
return null;
78
};
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useState } from 'react';
22

3-
const MyInput = function MyInput(props) {
4-
const {
5-
ref
6-
} = props;
7-
3+
const MyInput = function MyInput(
4+
{
5+
ref,
6+
...props
7+
}
8+
) {
89
return null;
910
};
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const MyInput = function MyInput(props) {
2-
const {
3-
ref
4-
} = props;
5-
1+
const MyInput = function MyInput(
2+
{
3+
ref,
4+
...props
5+
}
6+
) {
67
return null;
78
};
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const MyInput = function A(props) {
2-
const {
3-
ref
4-
} = props;
5-
1+
const MyInput = function A(
2+
{
3+
ref,
4+
...props
5+
}
6+
) {
67
return null;
78
};
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const MyInput = function MyInput(props) {
2-
const {
3-
ref
4-
} = props;
5-
1+
const MyInput = function MyInput(
2+
{
3+
ref,
4+
...props
5+
}
6+
) {
67
return <input ref={ref} onChange={props.onChange} />
78
};

transforms/__testfixtures__/remove-forward-ref/typescript/forward-ref-import.input.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

transforms/__testfixtures__/remove-forward-ref/typescript/forward-ref-import.output.js

Lines changed: 0 additions & 10 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
const MyComponent = function Component(
2-
myProps: Props & {
2+
{
3+
ref: myRef,
4+
...myProps
5+
}: Props & {
36
ref: React.RefObject<HTMLButtonElement>
47
}
58
) {
6-
const {
7-
ref: myRef
8-
} = myProps;
9-
109
return null;
1110
};
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
type Props = { a: 1 };
22

33
const MyInput = (
4-
props: Props & {
4+
{
5+
ref,
6+
...props
7+
}: Props & {
58
ref: React.RefObject<HTMLInputElement>
69
}
710
) => {
8-
const {
9-
ref
10-
} = props;
11-
1211
return null;
1312
};

transforms/__tests__/remove-forward-ref.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ const jsTests = [
1111
];
1212

1313
const tsTests = [
14-
// 'forward-ref-import',
1514
'type-arguments',
16-
'type-arguments-2',
15+
'type-arguments-custom-names',
1716
];
1817

1918
const defineTest = require('jscodeshift/dist/testUtils').defineTest;

transforms/remove-forward-ref.ts

Lines changed: 76 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type {
22
API,
3+
ArrowFunctionExpression,
4+
CallExpression,
35
FileInfo,
6+
FunctionExpression,
47
Identifier,
58
JSCodeshift,
69
TSTypeReference,
@@ -32,23 +35,19 @@ const buildPropsAndRefIntersectionTypeAnnotation = (
3235
]),
3336
);
3437

35-
// const { ref } = props;
36-
const buildRefArgVariableDeclaration = (
38+
// { ref: refName, ...propsName }
39+
const buildRefAndPropsObjectPattern = (
3740
j: JSCodeshift,
3841
refArgName: string,
3942
propArgName: string,
4043
) =>
41-
j.variableDeclaration('const', [
42-
j.variableDeclarator(
43-
j.objectPattern([
44-
j.objectProperty.from({
45-
shorthand: true,
46-
key: j.identifier('ref'),
47-
value: j.identifier(refArgName),
48-
}),
49-
]),
50-
j.identifier(propArgName),
51-
),
44+
j.objectPattern([
45+
j.objectProperty.from({
46+
shorthand: true,
47+
key: j.identifier('ref'),
48+
value: j.identifier(refArgName),
49+
}),
50+
j.restProperty(j.identifier(propArgName)),
5251
]);
5352

5453
// React.ForwardedRef<HTMLButtonElement> => HTMLButtonElement
@@ -57,7 +56,7 @@ const getRefTypeFromRefArg = (j: JSCodeshift, refArg: Identifier) => {
5756

5857
if (
5958
!j.TSTypeReference.check(typeReference) ||
60-
!j.TSQualifiedName.check(typeReference.typeName)
59+
!j.TSQualifiedName.check(typeReference.typeName)
6160
) {
6261
return null;
6362
}
@@ -77,6 +76,22 @@ const getRefTypeFromRefArg = (j: JSCodeshift, refArg: Identifier) => {
7776
return firstTypeParameter;
7877
};
7978

79+
const getForwardRefRenderFunction = (
80+
j: JSCodeshift,
81+
callExpression: CallExpression,
82+
): FunctionExpression | ArrowFunctionExpression | null => {
83+
const [renderFunction] = callExpression.arguments;
84+
85+
if (
86+
!j.FunctionExpression.check(renderFunction) &&
87+
!j.ArrowFunctionExpression.check(renderFunction)
88+
) {
89+
return null;
90+
}
91+
92+
return renderFunction;
93+
};
94+
8095
export default function transform(file: FileInfo, api: API) {
8196
const j = api.jscodeshift;
8297

@@ -92,121 +107,107 @@ export default function transform(file: FileInfo, api: API) {
92107
},
93108
})
94109
.replaceWith((callExpressionPath) => {
95-
const [renderFunctionArg] = callExpressionPath.node.arguments;
110+
const originalCallExpression = callExpressionPath.value;
96111

97-
if (
98-
!j.FunctionExpression.check(renderFunctionArg) &&
99-
!j.ArrowFunctionExpression.check(renderFunctionArg)
100-
) {
101-
return null;
112+
const renderFunction = getForwardRefRenderFunction(
113+
j,
114+
callExpressionPath.node,
115+
);
116+
117+
if (renderFunction === null) {
118+
console.warn('Could not detect render function.');
119+
120+
return originalCallExpression;
102121
}
103122

104-
const [propsArg, refArg] = renderFunctionArg.params;
123+
const [propsArg, refArg] = renderFunction.params;
124+
125+
if (
126+
!j.Identifier.check(refArg) ||
127+
!(j.Identifier.check(propsArg) || j.ObjectPattern.check(propsArg))
128+
) {
129+
console.warn('Could not detect ref or props arguments.');
105130

106-
if (!j.Identifier.check(refArg) || propsArg === undefined) {
107-
return null;
131+
return originalCallExpression;
108132
}
109133

110134
const refArgTypeReference = getRefTypeFromRefArg(j, refArg);
135+
const refArgName = refArg.name;
111136

112-
// remove refArg
113-
renderFunctionArg.params.splice(1, 1);
137+
const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation;
114138

115-
const refArgName = refArg.name;
139+
// remove refArg
140+
renderFunction.params.splice(1, 1);
116141

117-
// if props are ObjectPattern, push ref as ObjectProperty
142+
// if propsArg is ObjectPattern, add ref as new ObjectProperty
118143
if (j.ObjectPattern.check(propsArg)) {
119144
propsArg.properties.unshift(
120145
j.objectProperty.from({
121146
shorthand: true,
122-
key: j.identifier(refArgName),
147+
key: j.identifier('ref'),
123148
value: j.identifier(refArgName),
124149
}),
125150
);
126-
127-
// update prop arg type
128-
const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation;
129-
130-
if (
131-
j.TSTypeReference.check(propsArgTypeReference) &&
132-
j.TSTypeReference.check(refArgTypeReference)
133-
) {
134-
propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation(
135-
j,
136-
propsArgTypeReference,
137-
refArgTypeReference,
138-
);
139-
}
140151
}
141152

142153
// if props arg is Identifier, push ref variable declaration to the function body
143154
if (j.Identifier.check(propsArg)) {
144-
// if we have arrow function with implicit return, we want to wrap it with BlockStatement
145-
if (
146-
j.ArrowFunctionExpression.check(renderFunctionArg) &&
147-
!j.BlockStatement.check(renderFunctionArg.body)
148-
) {
149-
renderFunctionArg.body = j.blockStatement.from({
150-
body: [j.returnStatement(renderFunctionArg.body)],
151-
});
152-
}
153-
154-
const newDeclaration = buildRefArgVariableDeclaration(
155+
renderFunction.params[0] = buildRefAndPropsObjectPattern(
155156
j,
156157
refArg.name,
157158
propsArg.name,
158159
);
160+
}
159161

160-
renderFunctionArg.body.body.unshift(newDeclaration);
161-
162-
const propsArgTypeReference = propsArg.typeAnnotation?.typeAnnotation;
162+
/**
163+
* Transform ts types: render function props and ref are typed
164+
*/
163165

164-
if (
165-
j.TSTypeReference.check(propsArgTypeReference) &&
166-
j.TSTypeReference.check(refArgTypeReference)
167-
) {
168-
propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation(
166+
if (
167+
j.TSTypeReference.check(propsArgTypeReference) &&
168+
j.TSTypeReference.check(refArgTypeReference) &&
169+
renderFunction.params?.[0] &&
170+
'typeAnnotation' in renderFunction.params[0]
171+
) {
172+
renderFunction.params[0].typeAnnotation =
173+
buildPropsAndRefIntersectionTypeAnnotation(
169174
j,
170175
propsArgTypeReference,
171176
refArgTypeReference,
172177
);
173-
}
174178
}
175179

176180
/**
177-
* Transform ts types: forwardRef type arguments are used
178-
*/
181+
* Transform ts types: forwardRef type arguments are used
182+
*/
179183

180184
const typeParameters = callExpressionPath.node.typeParameters;
181185

182186
// if typeParameters are used in forwardRef generic, reuse them to annotate props type
183187
// forwardRef<Ref, Props>((props) => { ... }) ====> (props: Props & { ref: React.RefObject<Ref> }) => { ... }
184188
if (
185189
j.TSTypeParameterInstantiation.check(typeParameters) &&
186-
propsArg !== undefined &&
187-
'typeAnnotation' in propsArg
190+
renderFunction.params?.[0] &&
191+
'typeAnnotation' in renderFunction.params[0]
188192
) {
189193
const [refType, propType] = typeParameters.params;
190194

191195
if (
192196
j.TSTypeReference.check(refType) &&
193-
j.TSTypeReference.check(propType)
197+
j.TSTypeReference.check(propType)
194198
) {
195-
propsArg.typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation(
196-
j,
197-
propType,
198-
refType,
199-
);
199+
renderFunction.params[0].typeAnnotation =
200+
buildPropsAndRefIntersectionTypeAnnotation(j, propType, refType);
200201
}
201202
}
202203

203204
dirtyFlag = true;
204-
return renderFunctionArg;
205+
return renderFunction;
205206
});
206207

207208
/**
208-
* handle import
209-
*/
209+
* handle import
210+
*/
210211
if (dirtyFlag) {
211212
root
212213
.find(j.ImportDeclaration, {
@@ -242,4 +243,3 @@ export default function transform(file: FileInfo, api: API) {
242243
return root.toSource();
243244
}
244245

245-

0 commit comments

Comments
 (0)