@@ -274,101 +274,112 @@ function doLint(
274
274
reactiveVariableReferences : TSESTree . Identifier [ ] ,
275
275
pIsSameTask : boolean ,
276
276
) {
277
- let isSameMicroTask = pIsSameTask
277
+ const processed = new Set < TSESTree . Node > ( )
278
+ verifyInternal ( ast , callFuncIdentifiers , pIsSameTask )
278
279
279
- const differentMicroTaskEnterNodes : TSESTree . Node [ ] = [ ]
280
+ /** verify for node */
281
+ function verifyInternal (
282
+ ast : TSESTree . Node ,
283
+ callFuncIdentifiers : TSESTree . Identifier [ ] ,
284
+ pIsSameTask : boolean ,
285
+ ) {
286
+ if ( processed . has ( ast ) ) {
287
+ // Avoid infinite recursion with recursive references.
288
+ return
289
+ }
290
+ processed . add ( ast )
280
291
281
- traverseNodes ( ast , {
282
- enterNode ( node ) {
283
- // Promise.then() or Promise.catch() is called.
284
- if ( isPromiseThenOrCatchBody ( node ) ) {
285
- differentMicroTaskEnterNodes . push ( node )
286
- isSameMicroTask = false
287
- }
292
+ let isSameMicroTask = pIsSameTask
288
293
289
- // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called
290
- for ( const { node : callExpression } of [
291
- ... tickCallExpressions ,
292
- ... taskReferences ,
293
- ] ) {
294
- if ( isChildNode ( callExpression , node ) ) {
294
+ const differentMicroTaskEnterNodes : TSESTree . Node [ ] = [ ]
295
+
296
+ traverseNodes ( ast , {
297
+ enterNode ( node ) {
298
+ // Promise.then() or Promise.catch() is called.
299
+ if ( isPromiseThenOrCatchBody ( node ) ) {
295
300
differentMicroTaskEnterNodes . push ( node )
296
301
isSameMicroTask = false
297
302
}
298
- }
299
303
300
- // left side of await block
301
- if (
302
- node . parent ?. type === "AssignmentExpression" &&
303
- node . parent ?. right . type === "AwaitExpression" &&
304
- node . parent ?. left === node
305
- ) {
306
- differentMicroTaskEnterNodes . push ( node )
307
- isSameMicroTask = false
308
- }
309
-
310
- if ( node . type === "Identifier" && isFunctionCall ( node ) ) {
311
- // traverse used functions body
312
- const functionDeclarationNode = getFunctionDeclarationNode (
313
- context ,
314
- node ,
315
- )
316
- if ( functionDeclarationNode ) {
317
- doLint (
318
- context ,
319
- functionDeclarationNode ,
320
- [ ...callFuncIdentifiers , node ] ,
321
- tickCallExpressions ,
322
- taskReferences ,
323
- reactiveVariableNames ,
324
- reactiveVariableReferences ,
325
- isSameMicroTask ,
326
- )
304
+ // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called
305
+ for ( const { node : callExpression } of [
306
+ ...tickCallExpressions ,
307
+ ...taskReferences ,
308
+ ] ) {
309
+ if ( isChildNode ( callExpression , node ) ) {
310
+ differentMicroTaskEnterNodes . push ( node )
311
+ isSameMicroTask = false
312
+ }
327
313
}
328
- }
329
314
330
- if ( ! isSameMicroTask ) {
315
+ // left side of await block
331
316
if (
332
- isReactiveVariableNode ( reactiveVariableReferences , node ) &&
333
- reactiveVariableNames . includes ( node . name ) &&
334
- isNodeForAssign ( node )
317
+ node . parent ?. type === "AssignmentExpression" &&
318
+ node . parent ?. right . type === "AwaitExpression" &&
319
+ node . parent ?. left === node
335
320
) {
336
- context . report ( {
321
+ differentMicroTaskEnterNodes . push ( node )
322
+ isSameMicroTask = false
323
+ }
324
+
325
+ if ( node . type === "Identifier" && isFunctionCall ( node ) ) {
326
+ // traverse used functions body
327
+ const functionDeclarationNode = getFunctionDeclarationNode (
328
+ context ,
337
329
node ,
338
- loc : node . loc ,
339
- messageId : "unexpected" ,
340
- } )
341
- callFuncIdentifiers . forEach ( ( callFuncIdentifier ) => {
330
+ )
331
+ if ( functionDeclarationNode ) {
332
+ verifyInternal (
333
+ functionDeclarationNode ,
334
+ [ ...callFuncIdentifiers , node ] ,
335
+ isSameMicroTask ,
336
+ )
337
+ }
338
+ }
339
+
340
+ if ( ! isSameMicroTask ) {
341
+ if (
342
+ isReactiveVariableNode ( reactiveVariableReferences , node ) &&
343
+ reactiveVariableNames . includes ( node . name ) &&
344
+ isNodeForAssign ( node )
345
+ ) {
342
346
context . report ( {
343
- node : callFuncIdentifier ,
344
- loc : callFuncIdentifier . loc ,
345
- messageId : "unexpectedCall" ,
346
- data : {
347
- variableName : node . name ,
348
- } ,
347
+ node,
348
+ loc : node . loc ,
349
+ messageId : "unexpected" ,
350
+ } )
351
+ callFuncIdentifiers . forEach ( ( callFuncIdentifier ) => {
352
+ context . report ( {
353
+ node : callFuncIdentifier ,
354
+ loc : callFuncIdentifier . loc ,
355
+ messageId : "unexpectedCall" ,
356
+ data : {
357
+ variableName : node . name ,
358
+ } ,
359
+ } )
349
360
} )
350
- } )
361
+ }
351
362
}
352
- }
353
- } ,
354
- leaveNode ( node ) {
355
- if ( node . type === "AwaitExpression" ) {
356
- if ( ( ast . parent ?. type as string ) === "SvelteReactiveStatement" ) {
357
- // MEMO: It checks that `await` is used in reactive statement directly or not.
358
- // If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`.
359
- if ( ! isInsideOfFunction ( node ) ) {
363
+ } ,
364
+ leaveNode ( node ) {
365
+ if ( node . type === "AwaitExpression" ) {
366
+ if ( ( ast . parent ?. type as string ) === "SvelteReactiveStatement" ) {
367
+ // MEMO: It checks that `await` is used in reactive statement directly or not.
368
+ // If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`.
369
+ if ( ! isInsideOfFunction ( node ) ) {
370
+ isSameMicroTask = false
371
+ }
372
+ } else {
360
373
isSameMicroTask = false
361
374
}
362
- } else {
363
- isSameMicroTask = false
364
375
}
365
- }
366
376
367
- if ( differentMicroTaskEnterNodes . includes ( node ) ) {
368
- isSameMicroTask = true
369
- }
370
- } ,
371
- } )
377
+ if ( differentMicroTaskEnterNodes . includes ( node ) ) {
378
+ isSameMicroTask = true
379
+ }
380
+ } ,
381
+ } )
382
+ }
372
383
}
373
384
374
385
export default createRule ( "infinite-reactive-loop" , {
0 commit comments