@@ -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,166 +93,180 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
9893 ) ;
9994 }
10095
101- if ( ts . isPropertyAccessExpression ( node . expression ) ) {
102- const pae = node . expression ;
103-
104- const isChainedWithAndProperty =
105- ( ts . isPropertyAccessExpression ( pae . expression ) &&
106- ts . isIdentifier ( pae . expression . name ) &&
107- pae . expression . name . text === 'and' ) ||
108- ( ts . isElementAccessExpression ( pae . expression ) &&
109- ts . isStringLiteral ( pae . expression . argumentExpression ) &&
110- pae . expression . argumentExpression . text === 'and' ) ;
111-
112- if ( isChainedWithAndProperty ) {
113- const spyCall = ts . isPropertyAccessExpression ( pae . expression )
114- ? pae . expression . expression
115- : ( pae . expression as ts . ElementAccessExpression ) . expression ;
116- let newMethodName : string | undefined ;
117- let args = node . arguments ;
118-
119- if ( ts . isIdentifier ( pae . name ) ) {
120- const strategyName = pae . name . text ;
121- switch ( strategyName ) {
122- case 'returnValue' :
123- {
124- const result = getPromiseResolveRejectMethod ( args [ 0 ] ) ;
125- if ( result ) {
126- const methodMapping = {
127- 'resolve' : 'mockResolvedValue' ,
128- 'reject' : 'mockRejectedValue' ,
129- } ;
130- newMethodName = methodMapping [ result . methodName ] ;
131- args = result . arguments ;
132- } else {
133- newMethodName = 'mockReturnValue' ;
134- }
135- }
136- break ;
137- case 'resolveTo' :
138- newMethodName = 'mockResolvedValue' ;
139- break ;
140- case 'rejectWith' :
141- newMethodName = 'mockRejectedValue' ;
142- break ;
143- case 'returnValues' : {
144- reporter . reportTransformation (
145- sourceFile ,
146- node ,
147- 'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.' ,
148- ) ;
149- const returnValues = node . arguments ;
150- if ( returnValues . length === 0 ) {
151- // No values, so it's a no-op. Just transform the spyOn call.
152- return transformSpies ( spyCall , refactorCtx ) ;
153- }
154- // spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
155- let chainedCall : ts . Expression = spyCall ;
156- for ( const value of returnValues ) {
157- const mockCall = ts . factory . createCallExpression (
158- createPropertyAccess ( chainedCall , 'mockReturnValueOnce' ) ,
159- undefined ,
160- [ value ] ,
161- ) ;
162- chainedCall = mockCall ;
163- }
164-
165- return chainedCall ;
166- }
167- case 'callFake' :
168- newMethodName = 'mockImplementation' ;
169- break ;
170- case 'callThrough' :
171- reporter . reportTransformation (
172- sourceFile ,
173- node ,
174- 'Removed redundant `.and.callThrough()` call.' ,
175- ) ;
96+ return node ;
97+ }
17698
177- return transformSpies ( spyCall , refactorCtx ) ; // .and.callThrough() is redundant, just transform spyOn.
178- case 'stub' : {
179- reporter . reportTransformation (
180- sourceFile ,
181- node ,
182- 'Transformed `.and.stub()` to `.mockImplementation()`.' ,
183- ) ;
184- const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
185- const arrowFn = ts . factory . createArrowFunction (
186- undefined ,
187- undefined ,
188- [ ] ,
189- undefined ,
190- ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
191- ts . factory . createBlock ( [ ] , /* multiline */ true ) ,
192- ) ;
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+ }
193104
194- return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFn ] ) ;
105+ const pae = node . expression ;
106+ const isChainedWithAndProperty =
107+ ( ts . isPropertyAccessExpression ( pae . expression ) &&
108+ ts . isIdentifier ( pae . expression . name ) &&
109+ pae . expression . name . text === 'and' ) ||
110+ ( ts . isElementAccessExpression ( pae . expression ) &&
111+ ts . isStringLiteral ( pae . expression . argumentExpression ) &&
112+ pae . expression . argumentExpression . text === 'and' ) ;
113+
114+ if ( isChainedWithAndProperty ) {
115+ const spyCall = ts . isPropertyAccessExpression ( pae . expression )
116+ ? pae . expression . expression
117+ : ( pae . expression as ts . ElementAccessExpression ) . expression ;
118+ let newMethodName : string | undefined ;
119+ let args = node . arguments ;
120+
121+ if ( ts . isIdentifier ( pae . name ) ) {
122+ const strategyName = pae . name . text ;
123+ switch ( strategyName ) {
124+ case 'returnValue' :
125+ {
126+ const result = getPromiseResolveRejectMethod ( args [ 0 ] ) ;
127+ if ( result ) {
128+ const methodMapping = {
129+ 'resolve' : 'mockResolvedValue' ,
130+ 'reject' : 'mockRejectedValue' ,
131+ } ;
132+ newMethodName = methodMapping [ result . methodName ] ;
133+ args = result . arguments ;
134+ } else {
135+ newMethodName = 'mockReturnValue' ;
136+ }
195137 }
196- case 'throwError' : {
197- reporter . reportTransformation (
198- sourceFile ,
199- node ,
200- 'Transformed `.and.throwError()` to `.mockImplementation()`.' ,
201- ) ;
202- const errorArg = node . arguments [ 0 ] ;
203- const throwStatement = ts . factory . createThrowStatement (
204- ts . isNewExpression ( errorArg )
205- ? errorArg
206- : ts . factory . createNewExpression (
207- ts . factory . createIdentifier ( 'Error' ) ,
208- undefined ,
209- node . arguments ,
210- ) ,
211- ) ;
212- const arrowFunction = ts . factory . createArrowFunction (
213- undefined ,
214- undefined ,
215- [ ] ,
138+ break ;
139+ case 'resolveTo' :
140+ newMethodName = 'mockResolvedValue' ;
141+ break ;
142+ case 'rejectWith' :
143+ newMethodName = 'mockRejectedValue' ;
144+ break ;
145+ case 'returnValues' : {
146+ reporter . reportTransformation (
147+ sourceFile ,
148+ node ,
149+ 'Transformed `.and.returnValues()` to chained `.mockReturnValueOnce()` calls.' ,
150+ ) ;
151+ const returnValues = node . arguments ;
152+ if ( returnValues . length === 0 ) {
153+ // No values, so it's a no-op. Just transform the spyOn call.
154+ return transformSpies ( spyCall , refactorCtx ) ;
155+ }
156+ // spy.and.returnValues(a, b) -> spy.mockReturnValueOnce(a).mockReturnValueOnce(b)
157+ let chainedCall : ts . Expression = spyCall ;
158+ for ( const value of returnValues ) {
159+ const mockCall = ts . factory . createCallExpression (
160+ createPropertyAccess ( chainedCall , 'mockReturnValueOnce' ) ,
216161 undefined ,
217- ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
218- ts . factory . createBlock ( [ throwStatement ] , true ) ,
162+ [ value ] ,
219163 ) ;
220- const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
221-
222- return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFunction ] ) ;
164+ chainedCall = mockCall ;
223165 }
224- case 'identity' : {
225- reporter . reportTransformation (
226- sourceFile ,
227- node ,
228- 'Transformed `.and.identity()` to `.getMockName()`.' ,
229- ) ;
230- const newExpression = createPropertyAccess ( spyCall , 'getMockName' ) ;
231166
232- return ts . factory . createCallExpression ( newExpression , undefined , undefined ) ;
233- }
234- default : {
235- const category = 'unsupported-spy-strategy' ;
236- reporter . recordTodo ( category , sourceFile , node ) ;
237- addTodoComment ( node , category , { name : strategyName } ) ;
238- break ;
239- }
167+ return chainedCall ;
240168 }
169+ case 'callFake' :
170+ newMethodName = 'mockImplementation' ;
171+ break ;
172+ case 'callThrough' :
173+ reporter . reportTransformation (
174+ sourceFile ,
175+ node ,
176+ 'Removed redundant `.and.callThrough()` call.' ,
177+ ) ;
241178
242- if ( newMethodName ) {
179+ return transformSpies ( spyCall , refactorCtx ) ; // .and.callThrough() is redundant, just transform spyOn.
180+ case 'stub' : {
243181 reporter . reportTransformation (
244182 sourceFile ,
245183 node ,
246- ` Transformed spy strategy \ `.and.${ strategyName } ()\ ` to \`. ${ newMethodName } ()\`.` ,
184+ ' Transformed `.and.stub() ` to `.mockImplementation()`.' ,
247185 ) ;
186+ const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
187+ const arrowFn = ts . factory . createArrowFunction (
188+ undefined ,
189+ undefined ,
190+ [ ] ,
191+ undefined ,
192+ ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
193+ ts . factory . createBlock ( [ ] , /* multiline */ true ) ,
194+ ) ;
195+
196+ return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFn ] ) ;
197+ }
198+ case 'throwError' : {
199+ reporter . reportTransformation (
200+ sourceFile ,
201+ node ,
202+ 'Transformed `.and.throwError()` to `.mockImplementation()`.' ,
203+ ) ;
204+ const errorArg = node . arguments [ 0 ] ;
205+ const throwStatement = ts . factory . createThrowStatement (
206+ ts . isNewExpression ( errorArg )
207+ ? errorArg
208+ : ts . factory . createNewExpression (
209+ ts . factory . createIdentifier ( 'Error' ) ,
210+ undefined ,
211+ node . arguments ,
212+ ) ,
213+ ) ;
214+ const arrowFunction = ts . factory . createArrowFunction (
215+ undefined ,
216+ undefined ,
217+ [ ] ,
218+ undefined ,
219+ ts . factory . createToken ( ts . SyntaxKind . EqualsGreaterThanToken ) ,
220+ ts . factory . createBlock ( [ throwStatement ] , true ) ,
221+ ) ;
222+ const newExpression = createPropertyAccess ( spyCall , 'mockImplementation' ) ;
248223
249- const newExpression = ts . factory . updatePropertyAccessExpression (
250- pae ,
251- spyCall ,
252- ts . factory . createIdentifier ( newMethodName ) ,
224+ return ts . factory . createCallExpression ( newExpression , undefined , [ arrowFunction ] ) ;
225+ }
226+ case 'identity' : {
227+ reporter . reportTransformation (
228+ sourceFile ,
229+ node ,
230+ 'Transformed `.and.identity()` to `.getMockName()`.' ,
253231 ) ;
232+ const newExpression = createPropertyAccess ( spyCall , 'getMockName' ) ;
254233
255- return ts . factory . updateCallExpression ( node , newExpression , node . typeArguments , args ) ;
234+ return ts . factory . createCallExpression ( newExpression , undefined , undefined ) ;
256235 }
236+ default : {
237+ const category = 'unsupported-spy-strategy' ;
238+ reporter . recordTodo ( category , sourceFile , node ) ;
239+ addTodoComment ( node , category , { name : strategyName } ) ;
240+ break ;
241+ }
242+ }
243+
244+ if ( newMethodName ) {
245+ reporter . reportTransformation (
246+ sourceFile ,
247+ node ,
248+ `Transformed spy strategy \`.and.${ strategyName } ()\` to \`.${ newMethodName } ()\`.` ,
249+ ) ;
250+
251+ const newExpression = ts . factory . updatePropertyAccessExpression (
252+ pae ,
253+ spyCall ,
254+ ts . factory . createIdentifier ( newMethodName ) ,
255+ ) ;
256+
257+ return ts . factory . updateCallExpression ( node , newExpression , node . typeArguments , args ) ;
257258 }
258259 }
259260 }
260261
262+ return node ;
263+ }
264+
265+ function transformSpyOnAllFunctions (
266+ node : ts . CallExpression ,
267+ refactorCtx : RefactorContext ,
268+ ) : ts . Node {
269+ const { sourceFile, reporter } = refactorCtx ;
261270 if ( getJasmineMethodName ( node ) === 'spyOnAllFunctions' ) {
262271 reporter . reportTransformation (
263272 sourceFile ,
@@ -274,6 +283,24 @@ export function transformSpies(node: ts.Node, refactorCtx: RefactorContext): ts.
274283 return node ;
275284}
276285
286+ export function transformSpies ( node : ts . Node , refactorCtx : RefactorContext ) : ts . Node {
287+ if ( ! ts . isCallExpression ( node ) ) {
288+ return node ;
289+ }
290+
291+ const primaryResult = transformPrimarySpy ( node , refactorCtx ) ;
292+ if ( primaryResult !== node ) {
293+ return primaryResult ;
294+ }
295+
296+ const strategyResult = transformSpyStrategy ( node , refactorCtx ) ;
297+ if ( strategyResult !== node ) {
298+ return strategyResult ;
299+ }
300+
301+ return transformSpyOnAllFunctions ( node , refactorCtx ) ;
302+ }
303+
277304export function transformCreateSpy ( node : ts . Node , ctx : RefactorContext ) : ts . Node {
278305 const { reporter, sourceFile, pendingVitestValueImports } = ctx ;
279306 if ( ! isJasmineCallExpression ( node , 'createSpy' ) ) {
0 commit comments