1
1
import type {
2
2
API ,
3
+ ArrowFunctionExpression ,
4
+ CallExpression ,
3
5
FileInfo ,
6
+ FunctionExpression ,
4
7
Identifier ,
5
8
JSCodeshift ,
6
9
TSTypeReference ,
@@ -32,23 +35,19 @@ const buildPropsAndRefIntersectionTypeAnnotation = (
32
35
] ) ,
33
36
) ;
34
37
35
- // const { ref } = props;
36
- const buildRefArgVariableDeclaration = (
38
+ // { ref: refName, ...propsName }
39
+ const buildRefAndPropsObjectPattern = (
37
40
j : JSCodeshift ,
38
41
refArgName : string ,
39
42
propArgName : string ,
40
43
) =>
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 ) ) ,
52
51
] ) ;
53
52
54
53
// React.ForwardedRef<HTMLButtonElement> => HTMLButtonElement
@@ -57,7 +56,7 @@ const getRefTypeFromRefArg = (j: JSCodeshift, refArg: Identifier) => {
57
56
58
57
if (
59
58
! j . TSTypeReference . check ( typeReference ) ||
60
- ! j . TSQualifiedName . check ( typeReference . typeName )
59
+ ! j . TSQualifiedName . check ( typeReference . typeName )
61
60
) {
62
61
return null ;
63
62
}
@@ -77,6 +76,22 @@ const getRefTypeFromRefArg = (j: JSCodeshift, refArg: Identifier) => {
77
76
return firstTypeParameter ;
78
77
} ;
79
78
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
+
80
95
export default function transform ( file : FileInfo , api : API ) {
81
96
const j = api . jscodeshift ;
82
97
@@ -92,121 +107,107 @@ export default function transform(file: FileInfo, api: API) {
92
107
} ,
93
108
} )
94
109
. replaceWith ( ( callExpressionPath ) => {
95
- const [ renderFunctionArg ] = callExpressionPath . node . arguments ;
110
+ const originalCallExpression = callExpressionPath . value ;
96
111
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 ;
102
121
}
103
122
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.' ) ;
105
130
106
- if ( ! j . Identifier . check ( refArg ) || propsArg === undefined ) {
107
- return null ;
131
+ return originalCallExpression ;
108
132
}
109
133
110
134
const refArgTypeReference = getRefTypeFromRefArg ( j , refArg ) ;
135
+ const refArgName = refArg . name ;
111
136
112
- // remove refArg
113
- renderFunctionArg . params . splice ( 1 , 1 ) ;
137
+ const propsArgTypeReference = propsArg . typeAnnotation ?. typeAnnotation ;
114
138
115
- const refArgName = refArg . name ;
139
+ // remove refArg
140
+ renderFunction . params . splice ( 1 , 1 ) ;
116
141
117
- // if props are ObjectPattern, push ref as ObjectProperty
142
+ // if propsArg is ObjectPattern, add ref as new ObjectProperty
118
143
if ( j . ObjectPattern . check ( propsArg ) ) {
119
144
propsArg . properties . unshift (
120
145
j . objectProperty . from ( {
121
146
shorthand : true ,
122
- key : j . identifier ( refArgName ) ,
147
+ key : j . identifier ( 'ref' ) ,
123
148
value : j . identifier ( refArgName ) ,
124
149
} ) ,
125
150
) ;
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
- }
140
151
}
141
152
142
153
// if props arg is Identifier, push ref variable declaration to the function body
143
154
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 (
155
156
j ,
156
157
refArg . name ,
157
158
propsArg . name ,
158
159
) ;
160
+ }
159
161
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
+ */
163
165
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 (
169
174
j ,
170
175
propsArgTypeReference ,
171
176
refArgTypeReference ,
172
177
) ;
173
- }
174
178
}
175
179
176
180
/**
177
- * Transform ts types: forwardRef type arguments are used
178
- */
181
+ * Transform ts types: forwardRef type arguments are used
182
+ */
179
183
180
184
const typeParameters = callExpressionPath . node . typeParameters ;
181
185
182
186
// if typeParameters are used in forwardRef generic, reuse them to annotate props type
183
187
// forwardRef<Ref, Props>((props) => { ... }) ====> (props: Props & { ref: React.RefObject<Ref> }) => { ... }
184
188
if (
185
189
j . TSTypeParameterInstantiation . check ( typeParameters ) &&
186
- propsArg !== undefined &&
187
- 'typeAnnotation' in propsArg
190
+ renderFunction . params ?. [ 0 ] &&
191
+ 'typeAnnotation' in renderFunction . params [ 0 ]
188
192
) {
189
193
const [ refType , propType ] = typeParameters . params ;
190
194
191
195
if (
192
196
j . TSTypeReference . check ( refType ) &&
193
- j . TSTypeReference . check ( propType )
197
+ j . TSTypeReference . check ( propType )
194
198
) {
195
- propsArg . typeAnnotation = buildPropsAndRefIntersectionTypeAnnotation (
196
- j ,
197
- propType ,
198
- refType ,
199
- ) ;
199
+ renderFunction . params [ 0 ] . typeAnnotation =
200
+ buildPropsAndRefIntersectionTypeAnnotation ( j , propType , refType ) ;
200
201
}
201
202
}
202
203
203
204
dirtyFlag = true ;
204
- return renderFunctionArg ;
205
+ return renderFunction ;
205
206
} ) ;
206
207
207
208
/**
208
- * handle import
209
- */
209
+ * handle import
210
+ */
210
211
if ( dirtyFlag ) {
211
212
root
212
213
. find ( j . ImportDeclaration , {
@@ -242,4 +243,3 @@ export default function transform(file: FileInfo, api: API) {
242
243
return root . toSource ( ) ;
243
244
}
244
245
245
-
0 commit comments