From b528314318e2853515d53c9f6a49676a9de4baaa Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Thu, 23 Mar 2023 17:07:36 +0900 Subject: [PATCH 1/2] fix: maximum call stack error for `svelte/infinite-reactive-loop` rule --- src/rules/infinite-reactive-loop.ts | 163 ++++++++++-------- .../valid/recursive-reference-input.svelte | 5 + 2 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/recursive-reference-input.svelte diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 63eec0137..aecf3fc1c 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -274,101 +274,112 @@ function doLint( reactiveVariableReferences: TSESTree.Identifier[], pIsSameTask: boolean, ) { - let isSameMicroTask = pIsSameTask + const processed = new Set() + verifyInternal(ast, callFuncIdentifiers, pIsSameTask) - const differentMicroTaskEnterNodes: TSESTree.Node[] = [] + /** verify for node */ + function verifyInternal( + ast: TSESTree.Node, + callFuncIdentifiers: TSESTree.Identifier[], + pIsSameTask: boolean, + ) { + if (processed.has(ast)) { + // Avoid infinite recursion with recursive references. + return + } + processed.add(ast) - traverseNodes(ast, { - enterNode(node) { - // Promise.then() or Promise.catch() is called. - if (isPromiseThenOrCatchBody(node)) { - differentMicroTaskEnterNodes.push(node) - isSameMicroTask = false - } + let isSameMicroTask = pIsSameTask - // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called - for (const { node: callExpression } of [ - ...tickCallExpressions, - ...taskReferences, - ]) { - if (isChildNode(callExpression, node)) { + const differentMicroTaskEnterNodes: TSESTree.Node[] = [] + + traverseNodes(ast, { + enterNode(node) { + // Promise.then() or Promise.catch() is called. + if (isPromiseThenOrCatchBody(node)) { differentMicroTaskEnterNodes.push(node) isSameMicroTask = false } - } - // left side of await block - if ( - node.parent?.type === "AssignmentExpression" && - node.parent?.right.type === "AwaitExpression" && - node.parent?.left === node - ) { - differentMicroTaskEnterNodes.push(node) - isSameMicroTask = false - } - - if (node.type === "Identifier" && isFunctionCall(node)) { - // traverse used functions body - const functionDeclarationNode = getFunctionDeclarationNode( - context, - node, - ) - if (functionDeclarationNode) { - doLint( - context, - functionDeclarationNode, - [...callFuncIdentifiers, node], - tickCallExpressions, - taskReferences, - reactiveVariableNames, - reactiveVariableReferences, - isSameMicroTask, - ) + // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called + for (const { node: callExpression } of [ + ...tickCallExpressions, + ...taskReferences, + ]) { + if (isChildNode(callExpression, node)) { + differentMicroTaskEnterNodes.push(node) + isSameMicroTask = false + } } - } - if (!isSameMicroTask) { + // left side of await block if ( - isReactiveVariableNode(reactiveVariableReferences, node) && - reactiveVariableNames.includes(node.name) && - isNodeForAssign(node) + node.parent?.type === "AssignmentExpression" && + node.parent?.right.type === "AwaitExpression" && + node.parent?.left === node ) { - context.report({ + differentMicroTaskEnterNodes.push(node) + isSameMicroTask = false + } + + if (node.type === "Identifier" && isFunctionCall(node)) { + // traverse used functions body + const functionDeclarationNode = getFunctionDeclarationNode( + context, node, - loc: node.loc, - messageId: "unexpected", - }) - callFuncIdentifiers.forEach((callFuncIdentifier) => { + ) + if (functionDeclarationNode) { + verifyInternal( + functionDeclarationNode, + [...callFuncIdentifiers, node], + isSameMicroTask, + ) + } + } + + if (!isSameMicroTask) { + if ( + isReactiveVariableNode(reactiveVariableReferences, node) && + reactiveVariableNames.includes(node.name) && + isNodeForAssign(node) + ) { context.report({ - node: callFuncIdentifier, - loc: callFuncIdentifier.loc, - messageId: "unexpectedCall", - data: { - variableName: node.name, - }, + node, + loc: node.loc, + messageId: "unexpected", + }) + callFuncIdentifiers.forEach((callFuncIdentifier) => { + context.report({ + node: callFuncIdentifier, + loc: callFuncIdentifier.loc, + messageId: "unexpectedCall", + data: { + variableName: node.name, + }, + }) }) - }) + } } - } - }, - leaveNode(node) { - if (node.type === "AwaitExpression") { - if ((ast.parent?.type as string) === "SvelteReactiveStatement") { - // MEMO: It checks that `await` is used in reactive statement directly or not. - // If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`. - if (!isInsideOfFunction(node)) { + }, + leaveNode(node) { + if (node.type === "AwaitExpression") { + if ((ast.parent?.type as string) === "SvelteReactiveStatement") { + // MEMO: It checks that `await` is used in reactive statement directly or not. + // If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`. + if (!isInsideOfFunction(node)) { + isSameMicroTask = false + } + } else { isSameMicroTask = false } - } else { - isSameMicroTask = false } - } - if (differentMicroTaskEnterNodes.includes(node)) { - isSameMicroTask = true - } - }, - }) + if (differentMicroTaskEnterNodes.includes(node)) { + isSameMicroTask = true + } + }, + }) + } } export default createRule("infinite-reactive-loop", { diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/recursive-reference-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/recursive-reference-input.svelte new file mode 100644 index 000000000..b23f4734b --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/recursive-reference-input.svelte @@ -0,0 +1,5 @@ + From 7f1992df0a0696fb6f41213b6e75e3c652c16205 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Thu, 23 Mar 2023 17:08:53 +0900 Subject: [PATCH 2/2] Create .changeset/pretty-emus-hammer.md --- .changeset/pretty-emus-hammer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pretty-emus-hammer.md diff --git a/.changeset/pretty-emus-hammer.md b/.changeset/pretty-emus-hammer.md new file mode 100644 index 000000000..3ad193775 --- /dev/null +++ b/.changeset/pretty-emus-hammer.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": patch +--- + +fix: maximum call stack error in `svelte/infinite-reactive-loop` rule