@@ -54,13 +54,8 @@ function isChainedWithAnd(node: ts.Node): boolean {
5454 return false ;
5555}
5656
57- /* eslint-disable max-lines-per-function */
58- export function transformSpies ( node : ts . Node , refactorCtx : RefactorContext ) : ts . Node {
57+ function transformPrimarySpy ( node : ts . CallExpression , refactorCtx : RefactorContext ) : ts . Node {
5958 const { sourceFile, reporter, pendingVitestValueImports } = refactorCtx ;
60- if ( ! ts . isCallExpression ( node ) ) {
61- return node ;
62- }
63-
6459 if (
6560 ts . isIdentifier ( node . expression ) &&
6661 ( node . expression . text === 'spyOn' || node . expression . text === 'spyOnProperty' )
@@ -98,172 +93,186 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
9893 ) ;
9994 }
10095
101- if ( ts . isPropertyAccessExpression ( node . expression ) ) {
102- const pae = node . expression ;
96+ return node ;
97+ }
10398
104- let spyCall : ts . Expression | undefined ;
99+ function transformSpyStrategy ( node : ts . CallExpression , refactorCtx : RefactorContext ) : ts . Node {
100+ const { sourceFile, reporter } = refactorCtx ;
101+ if ( ! ts . isPropertyAccessExpression ( node . expression ) ) {
102+ return node ;
103+ }
105104
106- if (
107- ts . isPropertyAccessExpression ( pae . expression ) &&
108- ts . isIdentifier ( pae . expression . name ) &&
109- pae . expression . name . text === 'and'
110- ) {
111- spyCall = pae . expression . expression ;
112- } else if (
113- ts . isElementAccessExpression ( pae . expression ) &&
114- ts . isStringLiteralLike ( pae . expression . argumentExpression ) &&
115- pae . expression . argumentExpression . text === 'and'
116- ) {
117- spyCall = pae . expression . expression ;
118- }
105+ const pae = node . expression ;
106+ let spyCall : ts . Expression | undefined ;
119107
120- if ( spyCall ) {
121- let newMethodName : string | undefined ;
122- let args = node . arguments ;
123-
124- if ( ts . isIdentifier ( pae . name ) ) {
125- const strategyName = pae . name . text ;
126- switch ( strategyName ) {
127- case 'returnValue' :
128- {
129- const firstArg = args [ 0 ] ;
130- const result = firstArg ? getPromiseResolveRejectMethod ( firstArg ) : null ;
131- if ( result ) {
132- const methodMapping = {
133- 'resolve' : 'mockResolvedValue' ,
134- 'reject' : 'mockRejectedValue' ,
135- } ;
136- newMethodName = methodMapping [ result . methodName ] ;
137- args = result . arguments ;
138- } else {
139- newMethodName = 'mockReturnValue' ;
140- }
141- }
142- break ;
143- case 'resolveTo' :
144- newMethodName = 'mockResolvedValue' ;
145- break ;
146- case 'rejectWith' :
147- newMethodName = 'mockRejectedValue' ;
148- break ;
149- case 'returnValues' : {
150- reporter . reportTransformation (
151- sourceFile ,
152- node ,
153- 'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.' ,
154- ) ;
155- const returnValues = node . arguments ;
156- if ( returnValues . length === 0 ) {
157- // No values, so it's a no-op. Just transform the spyOn call.
158- return transformSpies ( spyCall , refactorCtx ) ;
159- }
160- // spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
161- let chainedCall : ts . Expression = spyCall ;
162- for ( const value of returnValues ) {
163- const mockCall = ts . factory . createCallExpression (
164- createPropertyAccess ( chainedCall , 'mockReturnValueOnce' ) ,
165- undefined ,
166- [ value ] ,
167- ) ;
168- chainedCall = mockCall ;
169- }
108+ if (
109+ ts . isPropertyAccessExpression ( pae . expression ) &&
110+ ts . isIdentifier ( pae . expression . name ) &&
111+ pae . expression . name . text === 'and'
112+ ) {
113+ spyCall = pae . expression . expression ;
114+ } else if (
115+ ts . isElementAccessExpression ( pae . expression ) &&
116+ ts . isStringLiteralLike ( pae . expression . argumentExpression ) &&
117+ pae . expression . argumentExpression . text === 'and'
118+ ) {
119+ spyCall = pae . expression . expression ;
120+ }
170121
171- return chainedCall ;
122+ if ( spyCall ) {
123+ let newMethodName : string | undefined ;
124+ let args = node . arguments ;
125+
126+ if ( ts . isIdentifier ( pae . name ) ) {
127+ const strategyName = pae . name . text ;
128+ switch ( strategyName ) {
129+ case 'returnValue' :
130+ {
131+ const firstArg = args [ 0 ] ;
132+ const result = firstArg ? getPromiseResolveRejectMethod ( firstArg ) : null ;
133+ if ( result ) {
134+ const methodMapping = {
135+ 'resolve' : 'mockResolvedValue' ,
136+ 'reject' : 'mockRejectedValue' ,
137+ } ;
138+ newMethodName = methodMapping [ result . methodName ] ;
139+ args = result . arguments ;
140+ } else {
141+ newMethodName = 'mockReturnValue' ;
142+ }
172143 }
173- case 'callFake' :
174- newMethodName = 'mockImplementation' ;
175- break ;
176- case 'callThrough' :
177- reporter . reportTransformation (
178- sourceFile ,
179- node ,
180- 'Removed redundant `.and.callThrough()` call.' ,
181- ) ;
182-
183- return transformSpies ( spyCall , refactorCtx ) ; // .and.callThrough() is redundant, just transform spyOn.
184- case 'stub' : {
185- reporter . reportTransformation (
186- sourceFile ,
187- node ,
188- 'Transformed `.and.stub()` to `.mockImplementation()`.' ,
189- ) ;
190- const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
191- const arrowFn = ts . factory . createArrowFunction (
192- undefined ,
193- undefined ,
194- [ ] ,
195- undefined ,
196- ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
197- ts . factory . createBlock ( [ ] , /* multiline */ true ) ,
198- ) ;
199-
200- return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFn ] ) ;
144+ break ;
145+ case 'resolveTo' :
146+ newMethodName = 'mockResolvedValue' ;
147+ break ;
148+ case 'rejectWith' :
149+ newMethodName = 'mockRejectedValue' ;
150+ break ;
151+ case 'returnValues' : {
152+ reporter . reportTransformation (
153+ sourceFile ,
154+ node ,
155+ 'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.' ,
156+ ) ;
157+ const returnValues = node . arguments ;
158+ if ( returnValues . length === 0 ) {
159+ // No values, so it's a no-op. Just transform the spyOn call.
160+ return transformSpies ( spyCall , refactorCtx ) ;
201161 }
202- case 'throwError' : {
203- reporter . reportTransformation (
204- sourceFile ,
205- node ,
206- 'Transformed `.and.throwError()` to `.mockImplementation()`.' ,
207- ) ;
208- const errorArg = node . arguments [ 0 ] ;
209- const throwStatement = ts . factory . createThrowStatement (
210- errorArg && ts . isNewExpression ( errorArg )
211- ? errorArg
212- : ts . factory . createNewExpression (
213- ts . factory . createIdentifier ( 'Error' ) ,
214- undefined ,
215- errorArg ? [ errorArg ] : [ ] ,
216- ) ,
217- ) ;
218- const arrowFunction = ts . factory . createArrowFunction (
162+ // spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
163+ let chainedCall : ts . Expression = spyCall ;
164+ for ( const value of returnValues ) {
165+ const mockCall = ts . factory . createCallExpression (
166+ createPropertyAccess ( chainedCall , 'mockReturnValueOnce' ) ,
219167 undefined ,
220- undefined ,
221- [ ] ,
222- undefined ,
223- ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
224- ts . factory . createBlock ( [ throwStatement ] , true ) ,
168+ [ value ] ,
225169 ) ;
226- const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
227-
228- return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFunction ] ) ;
170+ chainedCall = mockCall ;
229171 }
230- case 'identity' : {
231- reporter . reportTransformation (
232- sourceFile ,
233- node ,
234- 'Transformed `.and.identity()` to `.getMockName()`.' ,
235- ) ;
236- const newExpression = createPropertyAccess ( spyCall , 'getMockName' ) ;
237172
238- return ts . factory . createCallExpression ( newExpression , undefined , undefined ) ;
239- }
240- default : {
241- const category = 'unsupported-spy-strategy' ;
242- reporter . recordTodo ( category , sourceFile , node ) ;
243- addTodoComment ( node , category , { name : strategyName } ) ;
244- break ;
245- }
173+ return chainedCall ;
246174 }
175+ case 'callFake' :
176+ newMethodName = 'mockImplementation' ;
177+ break ;
178+ case 'callThrough' :
179+ reporter . reportTransformation (
180+ sourceFile ,
181+ node ,
182+ 'Removed redundant `.and.callThrough()` call.' ,
183+ ) ;
184+
185+ return transformSpies ( spyCall , refactorCtx ) ; // .and.callThrough() is redundant, just transform spyOn.
186+ case 'stub' : {
187+ reporter . reportTransformation (
188+ sourceFile ,
189+ node ,
190+ 'Transformed `.and.stub()` to `.mockImplementation()`.' ,
191+ ) ;
192+ const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
193+ const arrowFn = ts . factory . createArrowFunction (
194+ undefined ,
195+ undefined ,
196+ [ ] ,
197+ undefined ,
198+ ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
199+ ts . factory . createBlock ( [ ] , /* multiline */ true ) ,
200+ ) ;
247201
248- if ( newMethodName ) {
202+ return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFn ] ) ;
203+ }
204+ case 'throwError' : {
249205 reporter . reportTransformation (
250206 sourceFile ,
251207 node ,
252- `Transformed spy strategy \`.and.${ strategyName } ()\` to \`.${ newMethodName } ()\`.` ,
208+ 'Transformed `.and.throwError()` to `.mockImplementation()`.' ,
209+ ) ;
210+ const errorArg = node . arguments [ 0 ] ;
211+ const throwStatement = ts . factory . createThrowStatement (
212+ errorArg && ts . isNewExpression ( errorArg )
213+ ? errorArg
214+ : ts . factory . createNewExpression (
215+ ts . factory . createIdentifier ( 'Error' ) ,
216+ undefined ,
217+ errorArg ? [ errorArg ] : [ ] ,
218+ ) ,
219+ ) ;
220+ const arrowFunction = ts . factory . createArrowFunction (
221+ undefined ,
222+ undefined ,
223+ [ ] ,
224+ undefined ,
225+ ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
226+ ts . factory . createBlock ( [ throwStatement ] , true ) ,
253227 ) ;
228+ const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
254229
255- const newExpression = ts . factory . updatePropertyAccessExpression (
256- pae ,
257- spyCall ,
258- ts . factory . createIdentifier ( newMethodName ) ,
230+ return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFunction ] ) ;
231+ }
232+ case 'identity' : {
233+ reporter . reportTransformation (
234+ sourceFile ,
235+ node ,
236+ 'Transformed `.and.identity()` to `.getMockName()`.' ,
259237 ) ;
238+ const newExpression = createPropertyAccess ( spyCall , 'getMockName' ) ;
260239
261- return ts . factory . updateCallExpression ( node , newExpression , node . typeArguments , args ) ;
240+ return ts . factory . createCallExpression ( newExpression , undefined , undefined ) ;
241+ }
242+ default : {
243+ const category = 'unsupported-spy-strategy' ;
244+ reporter . recordTodo ( category , sourceFile , node ) ;
245+ addTodoComment ( node , category , { name : strategyName } ) ;
246+ break ;
262247 }
263248 }
249+
250+ if ( newMethodName ) {
251+ reporter . reportTransformation (
252+ sourceFile ,
253+ node ,
254+ `Transformed spy strategy \`.and.${ strategyName } ()\` to \`.${ newMethodName } ()\`.` ,
255+ ) ;
256+
257+ const newExpression = ts . factory . updatePropertyAccessExpression (
258+ pae ,
259+ spyCall ,
260+ ts . factory . createIdentifier ( newMethodName ) ,
261+ ) ;
262+
263+ return ts . factory . updateCallExpression ( node , newExpression , node . typeArguments , args ) ;
264+ }
264265 }
265266 }
266267
268+ return node ;
269+ }
270+
271+ function transformSpyOnAllFunctions (
272+ node : ts . CallExpression ,
273+ refactorCtx : RefactorContext ,
274+ ) : ts . Node {
275+ const { sourceFile, reporter } = refactorCtx ;
267276 if ( getJasmineMethodName ( node ) === 'spyOnAllFunctions' ) {
268277 reporter . reportTransformation (
269278 sourceFile ,
@@ -280,6 +289,24 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
280289 return node ;
281290}
282291
292+ export function transformSpies ( node : ts . Node , refactorCtx : RefactorContext ) : ts . Node {
293+ if ( ! ts . isCallExpression ( node ) ) {
294+ return node ;
295+ }
296+
297+ const primaryResult = transformPrimarySpy ( node , refactorCtx ) ;
298+ if ( primaryResult !== node ) {
299+ return primaryResult ;
300+ }
301+
302+ const strategyResult = transformSpyStrategy ( node , refactorCtx ) ;
303+ if ( strategyResult !== node ) {
304+ return strategyResult ;
305+ }
306+
307+ return transformSpyOnAllFunctions ( node , refactorCtx ) ;
308+ }
309+
283310export function transformCreateSpy ( node : ts . Node , ctx : RefactorContext ) : ts . Node {
284311 const { reporter, sourceFile, pendingVitestValueImports } = ctx ;
285312 if ( ! isJasmineCallExpression ( node , 'createSpy' ) ) {
0 commit comments