@@ -274,101 +274,112 @@ function doLint(
274274 reactiveVariableReferences : TSESTree . Identifier [ ] ,
275275 pIsSameTask : boolean ,
276276) {
277- let isSameMicroTask = pIsSameTask
277+ const processed = new Set < TSESTree . Node > ( )
278+ verifyInternal ( ast , callFuncIdentifiers , pIsSameTask )
278279
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 )
280291
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
288293
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 ) ) {
295300 differentMicroTaskEnterNodes . push ( node )
296301 isSameMicroTask = false
297302 }
298- }
299303
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+ }
327313 }
328- }
329314
330- if ( ! isSameMicroTask ) {
315+ // left side of await block
331316 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
335320 ) {
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 ,
337329 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+ ) {
342346 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+ } )
349360 } )
350- } )
361+ }
351362 }
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 {
360373 isSameMicroTask = false
361374 }
362- } else {
363- isSameMicroTask = false
364375 }
365- }
366376
367- if ( differentMicroTaskEnterNodes . includes ( node ) ) {
368- isSameMicroTask = true
369- }
370- } ,
371- } )
377+ if ( differentMicroTaskEnterNodes . includes ( node ) ) {
378+ isSameMicroTask = true
379+ }
380+ } ,
381+ } )
382+ }
372383}
373384
374385export default createRule ( "infinite-reactive-loop" , {
0 commit comments