From c2a1edb346da4ed662787366f1635b54894ada7d Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Mon, 7 Nov 2022 01:24:47 +0900 Subject: [PATCH 01/12] feat: implement infinite-reactive-loop --- README.md | 1 + docs/rules.md | 1 + docs/rules/infinite-reactive-loop.md | 110 ++++++ src/rules/infinite-reactive-loop.ts | 374 ++++++++++++++++++ src/utils/rules.ts | 2 + .../invalid/test01-errors.yaml | 80 ++++ .../invalid/test01-input.svelte | 119 ++++++ .../invalid/test02-errors.yaml | 40 ++ .../invalid/test02-input.svelte | 39 ++ .../invalid/test03-errors.yaml | 4 + .../invalid/test03-input.svelte | 7 + .../invalid/test04-errors.yaml | 8 + .../invalid/test04-input.svelte | 16 + .../invalid/test05-errors.yaml | 8 + .../invalid/test05-input.svelte | 15 + .../invalid/test06-errors.yaml | 8 + .../invalid/test06-input.svelte | 15 + .../invalid/test07-errors.yaml | 4 + .../invalid/test07-input.svelte | 8 + .../invalid/test08-errors.yaml | 16 + .../invalid/test08-input.svelte | 20 + .../invalid/test09-errors.yaml | 12 + .../invalid/test09-input.svelte | 16 + .../invalid/test10-errors.yaml | 8 + .../invalid/test10-input.svelte | 14 + .../invalid/test11-errors.yaml | 12 + .../invalid/test11-input.svelte | 12 + .../invalid/test12-errors.yaml | 12 + .../invalid/test12-input.svelte | 13 + .../invalid/test13-errors.yaml | 8 + .../invalid/test13-input.svelte | 13 + .../invalid/test14-errors.yaml | 4 + .../invalid/test14-input.svelte | 14 + .../invalid/test15-errors.yaml | 12 + .../invalid/test15-input.svelte | 15 + .../invalid/test16-errors.yaml | 4 + .../invalid/test16-input.svelte | 13 + .../valid/test01-input.svelte | 19 + .../valid/test02-input.svelte | 10 + .../valid/test03-input.svelte | 7 + .../valid/test04-input.svelte | 8 + .../valid/test05-input.svelte | 11 + .../valid/test06-input.svelte | 13 + tests/src/rules/infinite-reactive-loop.ts | 20 + 44 files changed, 1165 insertions(+) create mode 100644 docs/rules/infinite-reactive-loop.md create mode 100644 src/rules/infinite-reactive-loop.ts create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test01-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test02-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test03-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test04-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test05-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test06-input.svelte create mode 100644 tests/src/rules/infinite-reactive-loop.ts diff --git a/README.md b/README.md index 7a557f6a0..07ea5647d 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | Rule ID | Description | | |:--------|:------------|:---| +| [svelte/infinite-reactive-loop](https://ota-meshi.github.io/eslint-plugin-svelte/rules/infinite-reactive-loop/) | Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. | | | [svelte/no-dom-manipulating](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/) | disallow DOM manipulating | | | [svelte/no-dupe-else-if-blocks](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: | | [svelte/no-dupe-on-directives](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-on-directives/) | disallow duplicate `on:` directives | | diff --git a/docs/rules.md b/docs/rules.md index 59e794afc..691035ce0 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -16,6 +16,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | Rule ID | Description | | |:--------|:------------|:---| +| [svelte/infinite-reactive-loop](./rules/infinite-reactive-loop.md) | Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. | | | [svelte/no-dom-manipulating](./rules/no-dom-manipulating.md) | disallow DOM manipulating | | | [svelte/no-dupe-else-if-blocks](./rules/no-dupe-else-if-blocks.md) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: | | [svelte/no-dupe-on-directives](./rules/no-dupe-on-directives.md) | disallow duplicate `on:` directives | | diff --git a/docs/rules/infinite-reactive-loop.md b/docs/rules/infinite-reactive-loop.md new file mode 100644 index 000000000..546c0a142 --- /dev/null +++ b/docs/rules/infinite-reactive-loop.md @@ -0,0 +1,110 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/infinite-reactive-loop" +description: "Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent." +--- + +# svelte/infinite-reactive-loop + +> Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent. + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +Svelte runtime prevents calling the same reactive statement twice in a microtask.
+But between different microtask, it doesn't prevent.
+This rule reports those possible infinite loop. + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further Reading + +- [Svelte - Docs > COMPONENT FORMAT > 3. $: marks a statement as reactive](https://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive) +- [Svelte - Docs > COMPONENT FORMAT > 4. Prefix stores with $ to access their values](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values) + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/infinite-reactive-loop.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/infinite-reactive-loop.ts) diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts new file mode 100644 index 000000000..53bfd0409 --- /dev/null +++ b/src/rules/infinite-reactive-loop.ts @@ -0,0 +1,374 @@ +import type { TSESTree } from "@typescript-eslint/types" +import type { AST } from "svelte-eslint-parser" +import { ReferenceTracker } from "eslint-utils" +import { createRule } from "../utils" +import type { RuleContext } from "../types" +import { getScope } from "../utils/ast-utils" +import { traverseNodes } from "svelte-eslint-parser" + +/** */ +function extractTickReferences( + context: RuleContext, +): { node: TSESTree.CallExpression; name: string }[] { + const referenceTracker = new ReferenceTracker(context.getScope()) + const a = referenceTracker.iterateEsmReferences({ + svelte: { + [ReferenceTracker.ESM]: true, + tick: { + [ReferenceTracker.CALL]: true, + }, + }, + }) + return Array.from(a).map(({ node, path }) => { + return { + node: node as TSESTree.CallExpression, + name: path[path.length - 1], + } + }) +} + +/** */ +function extractTaskReferences( + context: RuleContext, +): { node: TSESTree.CallExpression; name: string }[] { + const referenceTracker = new ReferenceTracker(context.getScope()) + const a = referenceTracker.iterateGlobalReferences({ + setTimeout: { [ReferenceTracker.CALL]: true }, + setInterval: { [ReferenceTracker.CALL]: true }, + queueMicrotask: { [ReferenceTracker.CALL]: true }, + }) + return Array.from(a).map(({ node, path }) => { + return { + node: node as TSESTree.CallExpression, + name: path[path.length - 1], + } + }) +} + +/** */ +function isChildNode( + maybeAncestorNode: TSESTree.Node | AST.SvelteNode, + node: TSESTree.Node, +) { + let parent = node.parent + while (parent) { + if (parent === maybeAncestorNode) return true + parent = parent.parent + } + return false +} + +/** */ +function isFunctionCall(node: TSESTree.Node): boolean { + if (node.type !== "Identifier") return false + const { parent } = node + if (parent?.type !== "CallExpression") return false + return parent.callee.type === "Identifier" && parent.callee.name === node.name +} + +/** */ +function isObjectNode(node: TSESTree.Identifier): boolean { + const { parent } = node + if (parent?.type !== "MemberExpression") return true + if ( + parent.type === "MemberExpression" && + parent.object.type !== "Identifier" + ) { + return false + } + + return parent.object.type !== "Identifier" + ? false + : parent.object.name === node.name +} + +/** */ +function isReactiveVariableNode( + context: RuleContext, + node: TSESTree.Node, +): node is TSESTree.Identifier { + if (node.type !== "Identifier") return false + if (!isObjectNode(node) || isFunctionCall(node)) return false + + // Variable name starts with `$` means Svelte store. + if (node.name.startsWith("$")) return true + const scope = getScope(context, node) + return scope.references.some((reference) => { + const { resolved } = reference + if (!resolved || resolved.name !== node.name) return false + + return resolved.defs.some((def) => { + return ( + (def as any).parent?.parent!.type === "SvelteScriptElement" && + def.name.type === "Identifier" && + def.name.name === node.name + ) + }) + }) +} + +/** */ +function isNodeUseForAssign(node: TSESTree.Identifier): boolean { + const { parent } = node + if (parent?.type === "AssignmentExpression") { + return parent.left.type === "Identifier" && parent.left.name === node.name + } + return ( + parent?.type === "MemberExpression" && + parent.parent?.type === "AssignmentExpression" && + parent.parent.left.type === "MemberExpression" && + parent.parent.left.object.type === "Identifier" && + parent.parent.left.object.name === node.name + ) +} + +/** */ +function isPromiseThenOrCatch(node: TSESTree.Node): boolean { + if (!getDeclarationBody(node)) return false + const { parent } = node + if ( + parent?.type !== "CallExpression" || + parent?.callee?.type !== "MemberExpression" + ) { + return false + } + const { property } = parent.callee + if (property?.type !== "Identifier") return false + return ["then", "catch"].includes(property.name) +} + +/** */ +function getTrackedVariableNodes( + context: RuleContext, + ast: AST.SvelteReactiveStatement, +) { + const reactiveVariableNodes: TSESTree.Identifier[] = [] + traverseNodes(ast.body, { + enterNode(node) { + if (isReactiveVariableNode(context, node)) { + reactiveVariableNodes.push(node) + } + }, + leaveNode() { + /* noop */ + }, + }) + return reactiveVariableNodes +} + +/** */ +function getDeclarationBody( + node: TSESTree.Node, + functionName?: string, +): TSESTree.BlockStatement | TSESTree.Expression | null { + if ( + node.type === "VariableDeclarator" && + node.id.type === "Identifier" && + (!functionName || node.id.name === functionName) + ) { + if ( + node.init?.type === "ArrowFunctionExpression" || + node.init?.type === "FunctionExpression" + ) { + return node.init.body + } + } else if ( + node.type === "FunctionDeclaration" && + node.id?.type === "Identifier" && + (!functionName || node.id?.name === functionName) + ) { + return node.body + } else if (!functionName && node.type === "ArrowFunctionExpression") { + return node.body + } + return null +} + +/** */ +function getFunctionDeclarationNode( + functionCall: TSESTree.Identifier, +): TSESTree.BlockStatement | TSESTree.Expression | null { + let parent: AST.SvelteScriptElement | TSESTree.Node | undefined = functionCall + let declaration: TSESTree.BlockStatement | TSESTree.Expression | null = null + + while (parent) { + if (declaration) return declaration + parent = parent.parent as + | AST.SvelteScriptElement + | TSESTree.Node + | undefined + if (parent && parent.type === "BlockStatement") { + traverseNodes(parent, { + // eslint-disable-next-line no-loop-func -- ignore + enterNode(node) { + declaration = getDeclarationBody(node, functionCall.name) + }, + leaveNode() { + /* noop */ + }, + }) + } else if (parent && parent.type === "SvelteScriptElement") { + for (const node of parent.body) { + if (declaration) break + if (node.type === "VariableDeclaration") { + for (const child of node.declarations) { + declaration = getDeclarationBody(child, functionCall.name) + if (declaration) break + } + } + } + } + } + + return declaration +} + +/** */ +function doLint( + context: RuleContext, + node: TSESTree.Node, + callFuncIdentifiers: TSESTree.Identifier[], + tickCallExpressions: { node: TSESTree.CallExpression; name: string }[], + taskReferences: { + node: TSESTree.CallExpression + name: string + }[], + reactiveVariableNames: string[], + pIsSameTask: boolean, +) { + let isSameMicroTask = pIsSameTask + + traverseNodes(node, { + enterNode(node) { + // Promise.then() or Promise.catch() is called. + if (isPromiseThenOrCatch(node)) { + isSameMicroTask = false + } + + // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called + for (const { node: callExpression } of [ + ...tickCallExpressions, + ...taskReferences, + ]) { + if (isChildNode(callExpression, node)) { + isSameMicroTask = false + } + } + + // left side of await block + if ( + node.parent?.type === "AssignmentExpression" && + node.parent?.right.type === "AwaitExpression" && + node.parent?.left === node + ) { + isSameMicroTask = false + } + + if (node.type === "Identifier" && isFunctionCall(node)) { + // traverse used functions body + const functionDeclarationNode = getFunctionDeclarationNode(node) + if (functionDeclarationNode) { + doLint( + context, + functionDeclarationNode, + [...callFuncIdentifiers, node], + tickCallExpressions, + taskReferences, + reactiveVariableNames, + isSameMicroTask, + ) + } + } + + if (!isSameMicroTask) { + if ( + isReactiveVariableNode(context, node) && + reactiveVariableNames.includes(node.name) && + isNodeUseForAssign(node) + ) { + context.report({ + node, + loc: node.loc, + messageId: "unexpected", + }) + callFuncIdentifiers.forEach((callFuncIdentifier) => { + context.report({ + node: callFuncIdentifier, + loc: callFuncIdentifier.loc, + messageId: "unexpectedCall", + data: { + variableName: node.name, + }, + }) + }) + } + } + }, + leaveNode(node) { + // After `await` statement runs on a different microtask. + if (node.type === "AwaitExpression") { + isSameMicroTask = false + } + + // Promise.then() or Promise.catch() is called. + if (isPromiseThenOrCatch(node)) { + isSameMicroTask = true + } + + // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called + for (const { node: callExpression } of [ + ...tickCallExpressions, + ...taskReferences, + ]) { + if (isChildNode(callExpression, node)) { + isSameMicroTask = true + } + } + + // left side of await block + if ( + node.parent?.type === "AssignmentExpression" && + node.parent?.right.type === "AwaitExpression" && + node.parent?.left === node + ) { + isSameMicroTask = true + } + }, + }) +} + +export default createRule("infinite-reactive-loop", { + meta: { + docs: { + description: + "Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent.", + category: "Possible Errors", + recommended: false, + }, + schema: [], + messages: { + unexpected: "Possibly it may occur an infinite reactive loop.", + unexpectedCall: + "Possibly it may occur an infinite reactive loop because this function may update `{{variableName}}`.", + }, + type: "suggestion", + }, + create(context) { + return { + ["SvelteReactiveStatement"]: (ast: AST.SvelteReactiveStatement) => { + const tickCallExpressions = extractTickReferences(context) + const taskReferences = extractTaskReferences(context) + const trackedVariableNodes = getTrackedVariableNodes(context, ast) + doLint( + context, + ast.body, + [], + tickCallExpressions, + taskReferences, + trackedVariableNodes.map((node) => node.name), + true, + ) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 5a9a8224e..6882f6bca 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -8,6 +8,7 @@ import htmlClosingBracketSpacing from "../rules/html-closing-bracket-spacing" import htmlQuotes from "../rules/html-quotes" import htmlSelfClosing from "../rules/html-self-closing" import indent from "../rules/indent" +import infiniteReactiveLoop from "../rules/infinite-reactive-loop" import maxAttributesPerLine from "../rules/max-attributes-per-line" import mustacheSpacing from "../rules/mustache-spacing" import noAtDebugTags from "../rules/no-at-debug-tags" @@ -58,6 +59,7 @@ export const rules = [ htmlQuotes, htmlSelfClosing, indent, + infiniteReactiveLoop, maxAttributesPerLine, mustacheSpacing, noAtDebugTags, diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml new file mode 100644 index 000000000..e60dcf181 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml @@ -0,0 +1,80 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 12 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 16 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 25 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 28 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 36 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 39 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 47 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 53 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 59 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 64 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 70 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 76 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 82 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 88 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 94 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 100 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 106 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 112 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 117 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte new file mode 100644 index 000000000..73205202e --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte @@ -0,0 +1,119 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml new file mode 100644 index 000000000..007c72455 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml @@ -0,0 +1,40 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 12 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 17 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 20 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 25 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 28 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 33 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 36 + column: 9 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte new file mode 100644 index 000000000..bb5395b5d --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte @@ -0,0 +1,39 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml new file mode 100644 index 000000000..48a6fc271 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 5 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte new file mode 100644 index 000000000..87a7e87ca --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml new file mode 100644 index 000000000..457e93042 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `a`. + line: 14 + column: 11 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte new file mode 100644 index 000000000..ef4bbac23 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml new file mode 100644 index 000000000..c6b46f0ad --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `a`. + line: 13 + column: 11 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte new file mode 100644 index 000000000..cc0da9f88 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml new file mode 100644 index 000000000..d59c7f746 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 12 + column: 24 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 25 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte new file mode 100644 index 000000000..3ba4e9b83 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml new file mode 100644 index 000000000..360ccfc7d --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 6 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte new file mode 100644 index 000000000..e9663f8d6 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml new file mode 100644 index 000000000..592ba8cb5 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml @@ -0,0 +1,16 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 6 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `a`. + line: 17 + column: 11 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `a`. + line: 18 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte new file mode 100644 index 000000000..dd3917cf9 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte @@ -0,0 +1,20 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml new file mode 100644 index 000000000..c0319a716 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 10 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte new file mode 100644 index 000000000..b16489719 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml new file mode 100644 index 000000000..90ec1cf42 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 6 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `a`. + line: 12 + column: 11 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte new file mode 100644 index 000000000..2ceb8ba99 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml new file mode 100644 index 000000000..c925e6039 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 5 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `obj`. + line: 9 + column: 11 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 10 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte new file mode 100644 index 000000000..3225fffac --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte @@ -0,0 +1,12 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml new file mode 100644 index 000000000..e9ec294fd --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 5 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `obj`. + line: 10 + column: 11 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte new file mode 100644 index 000000000..b36bfe876 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml new file mode 100644 index 000000000..7002c04b2 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 5 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `$store`. + line: 11 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte new file mode 100644 index 000000000..356c3b15b --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml new file mode 100644 index 000000000..4e4a3bd98 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 12 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte new file mode 100644 index 000000000..e0658f29e --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml new file mode 100644 index 000000000..7820aeb25 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop because this function may update `foo`. + line: 12 + column: 11 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte new file mode 100644 index 000000000..99f147851 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml new file mode 100644 index 000000000..f8ec40608 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte new file mode 100644 index 000000000..f80ae830f --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test01-input.svelte new file mode 100644 index 000000000..a70d3bbc9 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test01-input.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test02-input.svelte new file mode 100644 index 000000000..7cad960bc --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test02-input.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test03-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test03-input.svelte new file mode 100644 index 000000000..410b3da92 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test03-input.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test04-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test04-input.svelte new file mode 100644 index 000000000..746c1e4fc --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test04-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test05-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test05-input.svelte new file mode 100644 index 000000000..400cca9f3 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test05-input.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test06-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test06-input.svelte new file mode 100644 index 000000000..33842716a --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test06-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/src/rules/infinite-reactive-loop.ts b/tests/src/rules/infinite-reactive-loop.ts new file mode 100644 index 000000000..3eba1294c --- /dev/null +++ b/tests/src/rules/infinite-reactive-loop.ts @@ -0,0 +1,20 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/infinite-reactive-loop" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, + env: { + browser: true, + es2022: true, + }, +}) + +tester.run( + "infinite-reactive-loop", + rule as any, + loadTestCases("infinite-reactive-loop"), +) From 2699b72d95bd48c4e1f3d26ce88e650faad819c3 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 24 Nov 2022 02:33:08 +0900 Subject: [PATCH 02/12] chore: add changeset --- .changeset/slimy-brooms-report.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-brooms-report.md diff --git a/.changeset/slimy-brooms-report.md b/.changeset/slimy-brooms-report.md new file mode 100644 index 000000000..6521a8946 --- /dev/null +++ b/.changeset/slimy-brooms-report.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `svelte/Infinite-reactive-loop` rule From c5585b08f66055d50ba71f7f0324586798232dc8 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 29 Dec 2022 13:40:44 +0900 Subject: [PATCH 03/12] chore: refactor --- src/rules/infinite-reactive-loop.ts | 97 ++++++++++++++----- .../valid/test07-input.svelte | 16 +++ 2 files changed, 91 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test07-input.svelte diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 53bfd0409..a96a77005 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -6,7 +6,9 @@ import type { RuleContext } from "../types" import { getScope } from "../utils/ast-utils" import { traverseNodes } from "svelte-eslint-parser" -/** */ +/** + * Get usage of `tick` + */ function extractTickReferences( context: RuleContext, ): { node: TSESTree.CallExpression; name: string }[] { @@ -27,7 +29,9 @@ function extractTickReferences( }) } -/** */ +/** + * Get usage of `setTimeout`, `setInterval`, `queueMicrotask` + */ function extractTaskReferences( context: RuleContext, ): { node: TSESTree.CallExpression; name: string }[] { @@ -45,11 +49,13 @@ function extractTaskReferences( }) } -/** */ +/** + * If `node` is inside of `maybeAncestorNode`, return true. + */ function isChildNode( maybeAncestorNode: TSESTree.Node | AST.SvelteNode, node: TSESTree.Node, -) { +): boolean { let parent = node.parent while (parent) { if (parent === maybeAncestorNode) return true @@ -58,7 +64,9 @@ function isChildNode( return false } -/** */ +/** + * Return true if `node` is a function call. + */ function isFunctionCall(node: TSESTree.Node): boolean { if (node.type !== "Identifier") return false const { parent } = node @@ -66,8 +74,17 @@ function isFunctionCall(node: TSESTree.Node): boolean { return parent.callee.type === "Identifier" && parent.callee.name === node.name } -/** */ -function isObjectNode(node: TSESTree.Identifier): boolean { +/** + * Return true if `node` is a variable. + * + * e.g. foo.bar + * If node is `foo`, return true. + * If node is `bar`, return false. + * + * e.g. let baz = 1 + * If node is `baz`, return true. + */ +function isVariableNode(node: TSESTree.Identifier): boolean { const { parent } = node if (parent?.type !== "MemberExpression") return true if ( @@ -82,13 +99,15 @@ function isObjectNode(node: TSESTree.Identifier): boolean { : parent.object.name === node.name } -/** */ +/** + * Return true if `node` is a reactive variable. + */ function isReactiveVariableNode( context: RuleContext, node: TSESTree.Node, ): node is TSESTree.Identifier { if (node.type !== "Identifier") return false - if (!isObjectNode(node) || isFunctionCall(node)) return false + if (!isVariableNode(node) || isFunctionCall(node)) return false // Variable name starts with `$` means Svelte store. if (node.name.startsWith("$")) return true @@ -107,8 +126,12 @@ function isReactiveVariableNode( }) } -/** */ -function isNodeUseForAssign(node: TSESTree.Identifier): boolean { +/** + * e.g. foo.bar = baz + 1 + * If node is `foo`, return true. + * Otherwise, return false. + */ +function isNodeForAssign(node: TSESTree.Identifier): boolean { const { parent } = node if (parent?.type === "AssignmentExpression") { return parent.left.type === "Identifier" && parent.left.name === node.name @@ -122,8 +145,10 @@ function isNodeUseForAssign(node: TSESTree.Identifier): boolean { ) } -/** */ -function isPromiseThenOrCatch(node: TSESTree.Node): boolean { +/** + * Return true if `node` is inside of `then` or `catch`. + */ +function isPromiseThenOrCatchBody(node: TSESTree.Node): boolean { if (!getDeclarationBody(node)) return false const { parent } = node if ( @@ -137,7 +162,9 @@ function isPromiseThenOrCatch(node: TSESTree.Node): boolean { return ["then", "catch"].includes(property.name) } -/** */ +/** + * Get all tracked reactive variables. + */ function getTrackedVariableNodes( context: RuleContext, ast: AST.SvelteReactiveStatement, @@ -201,7 +228,9 @@ function getFunctionDeclarationNode( traverseNodes(parent, { // eslint-disable-next-line no-loop-func -- ignore enterNode(node) { - declaration = getDeclarationBody(node, functionCall.name) + if (!declaration) { + declaration = getDeclarationBody(node, functionCall.name) + } }, leaveNode() { /* noop */ @@ -223,10 +252,29 @@ function getFunctionDeclarationNode( return declaration } +/** */ +function isInsideOfFunction(node: TSESTree.Node) { + let parent: TSESTree.Node | AST.SvelteReactiveStatement | null = node + while (parent) { + parent = parent.parent as TSESTree.Node | AST.SvelteReactiveStatement | null + if (!parent) break + if (parent.type === "FunctionDeclaration" && parent.async) return true + if ( + parent.type === "VariableDeclarator" && + (parent.init?.type === "FunctionExpression" || + parent.init?.type === "ArrowFunctionExpression") && + parent.init?.async + ) { + return true + } + } + return false +} + /** */ function doLint( context: RuleContext, - node: TSESTree.Node, + ast: TSESTree.Node, callFuncIdentifiers: TSESTree.Identifier[], tickCallExpressions: { node: TSESTree.CallExpression; name: string }[], taskReferences: { @@ -238,10 +286,10 @@ function doLint( ) { let isSameMicroTask = pIsSameTask - traverseNodes(node, { + traverseNodes(ast, { enterNode(node) { // Promise.then() or Promise.catch() is called. - if (isPromiseThenOrCatch(node)) { + if (isPromiseThenOrCatchBody(node)) { isSameMicroTask = false } @@ -284,7 +332,7 @@ function doLint( if ( isReactiveVariableNode(context, node) && reactiveVariableNames.includes(node.name) && - isNodeUseForAssign(node) + isNodeForAssign(node) ) { context.report({ node, @@ -305,13 +353,18 @@ function doLint( } }, leaveNode(node) { - // After `await` statement runs on a different microtask. if (node.type === "AwaitExpression") { - isSameMicroTask = false + if ((ast.parent?.type as string) === "SvelteReactiveStatement") { + if (!isInsideOfFunction(node)) { + isSameMicroTask = false + } + } else { + isSameMicroTask = false + } } // Promise.then() or Promise.catch() is called. - if (isPromiseThenOrCatch(node)) { + if (isPromiseThenOrCatchBody(node)) { isSameMicroTask = true } diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test07-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test07-input.svelte new file mode 100644 index 000000000..10c4bc8b3 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test07-input.svelte @@ -0,0 +1,16 @@ + From 7f84429170cd68a31a616d23db7c7038db3dea65 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 29 Dec 2022 16:09:02 +0900 Subject: [PATCH 04/12] chore: refactor test --- src/rules/infinite-reactive-loop.ts | 2 +- .../test01-errors.yaml} | 2 +- .../invalid/await/test01-input.svelte | 9 ++ .../test01-errors.yaml} | 0 .../test01-input.svelte} | 0 .../test02-errors.yaml} | 0 .../test02-input.svelte} | 5 + .../test03-errors.yaml} | 0 .../test03-input.svelte} | 5 + .../test04-errors.yaml} | 0 .../test04-input.svelte} | 6 + .../test05-errors.yaml} | 0 .../test05-input.svelte} | 0 .../test06-errors.yaml} | 0 .../test06-input.svelte} | 0 .../test07-errors.yaml} | 0 .../test07-input.svelte} | 6 + .../test08-errors.yaml} | 2 +- .../test08-input.svelte} | 3 +- .../test09-errors.yaml} | 0 .../test09-input.svelte} | 0 .../invalid/function-call/test10-errors.yaml | 4 + .../invalid/function-call/test10-input.svelte | 11 ++ .../test01-errors.yaml} | 6 +- .../invalid/promise/test01-input.svelte | 14 +++ .../invalid/promise/test02-errors.yaml | 8 ++ .../invalid/promise/test02-input.svelte | 17 +++ .../invalid/promise/test03-errors.yaml | 8 ++ .../invalid/promise/test03-input.svelte | 15 +++ .../invalid/promise/test04-errors.yaml | 4 + .../invalid/promise/test04-input.svelte | 9 ++ .../invalid/promise/test05-errors.yaml | 8 ++ .../invalid/promise/test05-input.svelte | 13 ++ .../invalid/{ => promise}/test06-errors.yaml | 8 +- .../invalid/promise/test06-input.svelte | 13 ++ .../invalid/queueMicrotask/test01-errors.yaml | 8 ++ .../queueMicrotask/test01-input.svelte | 16 +++ .../invalid/setInterval/test01-errors.yaml | 8 ++ .../invalid/setInterval/test01-input.svelte | 16 +++ .../invalid/setTimeout/test01-errors.yaml | 8 ++ .../invalid/setTimeout/test01-input.svelte | 24 ++++ .../test02-errors.yaml} | 0 .../test02-input.svelte} | 0 .../invalid/test01-errors.yaml | 80 ------------ .../invalid/test01-input.svelte | 119 ------------------ .../invalid/test02-errors.yaml | 40 ------ .../invalid/test02-input.svelte | 39 ------ .../invalid/test06-input.svelte | 15 --- .../invalid/test09-input.svelte | 16 --- .../invalid/test12-input.svelte | 13 -- .../invalid/test14-input.svelte | 14 --- .../invalid/tick/test01-errors.yaml | 16 +++ .../invalid/tick/test01-input.svelte | 27 ++++ .../test02-errors.yaml} | 12 +- .../invalid/tick/test02-input.svelte | 30 +++++ .../valid/test08-input.svelte | 16 +++ 56 files changed, 342 insertions(+), 353 deletions(-) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test14-errors.yaml => await/test01-errors.yaml} (89%) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-input.svelte rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test07-errors.yaml => function-call/test01-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test07-input.svelte => function-call/test01-input.svelte} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test04-errors.yaml => function-call/test02-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test04-input.svelte => function-call/test02-input.svelte} (77%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test05-errors.yaml => function-call/test03-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test05-input.svelte => function-call/test03-input.svelte} (76%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test08-errors.yaml => function-call/test04-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test08-input.svelte => function-call/test04-input.svelte} (75%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test10-errors.yaml => function-call/test05-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test10-input.svelte => function-call/test05-input.svelte} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test11-errors.yaml => function-call/test06-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test11-input.svelte => function-call/test06-input.svelte} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test13-errors.yaml => function-call/test07-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test13-input.svelte => function-call/test07-input.svelte} (67%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test15-errors.yaml => function-call/test08-errors.yaml} (97%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test15-input.svelte => function-call/test08-input.svelte} (84%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test16-errors.yaml => function-call/test09-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test16-input.svelte => function-call/test09-input.svelte} (100%) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-input.svelte rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test09-errors.yaml => promise/test01-errors.yaml} (92%) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-input.svelte rename tests/fixtures/rules/infinite-reactive-loop/invalid/{ => promise}/test06-errors.yaml (76%) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-input.svelte rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test03-errors.yaml => setTimeout/test02-errors.yaml} (100%) rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test03-input.svelte => setTimeout/test02-input.svelte} (100%) delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte delete mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-input.svelte rename tests/fixtures/rules/infinite-reactive-loop/invalid/{test12-errors.yaml => tick/test02-errors.yaml} (58%) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-input.svelte create mode 100644 tests/fixtures/rules/infinite-reactive-loop/valid/test08-input.svelte diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index a96a77005..d41cb3289 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -118,7 +118,7 @@ function isReactiveVariableNode( return resolved.defs.some((def) => { return ( - (def as any).parent?.parent!.type === "SvelteScriptElement" && + (def as any).parent?.parent?.type === "SvelteScriptElement" && def.name.type === "Identifier" && def.name.name === node.name ) diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-errors.yaml similarity index 89% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-errors.yaml index 4e4a3bd98..3def66667 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-errors.yaml +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-errors.yaml @@ -1,4 +1,4 @@ - message: Possibly it may occur an infinite reactive loop. - line: 12 + line: 7 column: 5 suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-input.svelte new file mode 100644 index 000000000..33226aa52 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-input.svelte @@ -0,0 +1,9 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test07-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-input.svelte similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test07-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-input.svelte diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test04-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-input.svelte similarity index 77% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-input.svelte index ef4bbac23..83b8f9fbe 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test04-input.svelte +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-input.svelte @@ -13,4 +13,9 @@ console.log(a) await doSomething() })() + + $: (async () => { + // should not report here + await doSomething() + })() diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test05-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-input.svelte similarity index 76% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-input.svelte index cc0da9f88..a845142c1 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test05-input.svelte +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-input.svelte @@ -12,4 +12,9 @@ console.log(a) await doSomething() })() + + $: (async () => { + // should not report here + await doSomething() + })() diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test08-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-input.svelte similarity index 75% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-input.svelte index dd3917cf9..62fbca902 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test08-input.svelte +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-input.svelte @@ -17,4 +17,10 @@ await doSomething() doSomething2() })() + + $: (async () => { + // should not report here + await doSomething() + doSomething2() + })() diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test10-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-input.svelte similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test10-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-input.svelte diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test11-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-input.svelte similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test11-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-input.svelte diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test13-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-input.svelte similarity index 67% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-input.svelte index 356c3b15b..8905d82f5 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test13-input.svelte +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-input.svelte @@ -10,4 +10,10 @@ await fetch() doSomething() })() + + $: (async () => { + // should not report here + await fetch() + doSomething() + })() diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml similarity index 97% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml index 7820aeb25..62970cbbb 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-errors.yaml +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml @@ -1,5 +1,5 @@ - message: Possibly it may occur an infinite reactive loop. - line: 7 + line: 6 column: 5 suggestions: null - message: Possibly it may occur an infinite reactive loop because this function may update `foo`. diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-input.svelte similarity index 84% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-input.svelte index 99f147851..e763fe9ad 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test15-input.svelte +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-input.svelte @@ -1,5 +1,4 @@ diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test16-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-input.svelte similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test16-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-input.svelte diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-errors.yaml new file mode 100644 index 000000000..4b2f7de00 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 9 + column: 30 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-input.svelte new file mode 100644 index 000000000..397650fdf --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test10-input.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml similarity index 92% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml index c0319a716..be27e282c 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-errors.yaml +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml @@ -1,12 +1,12 @@ - message: Possibly it may occur an infinite reactive loop. - line: 7 + line: 6 column: 7 suggestions: null - message: Possibly it may occur an infinite reactive loop. - line: 10 + line: 7 column: 7 suggestions: null - message: Possibly it may occur an infinite reactive loop. - line: 13 + line: 11 column: 7 suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-input.svelte new file mode 100644 index 000000000..baaaeb50d --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-input.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-errors.yaml new file mode 100644 index 000000000..97af81ec5 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 10 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 9 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-input.svelte new file mode 100644 index 000000000..e915ee439 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test02-input.svelte @@ -0,0 +1,17 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-errors.yaml new file mode 100644 index 000000000..57555ae9e --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 9 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-input.svelte new file mode 100644 index 000000000..8b8db1788 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test03-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-errors.yaml new file mode 100644 index 000000000..3def66667 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-input.svelte new file mode 100644 index 000000000..6579b2373 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test04-input.svelte @@ -0,0 +1,9 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-errors.yaml new file mode 100644 index 000000000..8f77e5a5b --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 9 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 10 + column: 9 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-input.svelte new file mode 100644 index 000000000..9459a4ef3 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test05-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-errors.yaml similarity index 76% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-errors.yaml index d59c7f746..8f77e5a5b 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-errors.yaml +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-errors.yaml @@ -1,8 +1,8 @@ - message: Possibly it may occur an infinite reactive loop. - line: 12 - column: 24 + line: 7 + column: 9 suggestions: null - message: Possibly it may occur an infinite reactive loop. - line: 13 - column: 25 + line: 10 + column: 9 suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-input.svelte new file mode 100644 index 000000000..cea79c5df --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-input.svelte @@ -0,0 +1,13 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-errors.yaml new file mode 100644 index 000000000..dd23a84fa --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-input.svelte new file mode 100644 index 000000000..361ad448c --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/queueMicrotask/test01-input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-errors.yaml new file mode 100644 index 000000000..dd23a84fa --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 7 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 13 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-input.svelte new file mode 100644 index 000000000..e76f0bd5b --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setInterval/test01-input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-errors.yaml new file mode 100644 index 000000000..b1a41fb0f --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-errors.yaml @@ -0,0 +1,8 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 11 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 17 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-input.svelte new file mode 100644 index 000000000..bb968e665 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test01-input.svelte @@ -0,0 +1,24 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-errors.yaml similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test03-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-errors.yaml diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-input.svelte similarity index 100% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test03-input.svelte rename to tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-input.svelte diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml deleted file mode 100644 index e60dcf181..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-errors.yaml +++ /dev/null @@ -1,80 +0,0 @@ -- message: Possibly it may occur an infinite reactive loop. - line: 11 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 12 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 16 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 25 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 28 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 36 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 39 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 47 - column: 5 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 53 - column: 5 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 59 - column: 5 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 64 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 70 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 76 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 82 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 88 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 94 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 100 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 106 - column: 7 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 112 - column: 5 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 117 - column: 5 - suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte deleted file mode 100644 index 73205202e..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test01-input.svelte +++ /dev/null @@ -1,119 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml deleted file mode 100644 index 007c72455..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-errors.yaml +++ /dev/null @@ -1,40 +0,0 @@ -- message: Possibly it may occur an infinite reactive loop. - line: 7 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 8 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 11 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 12 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 17 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 20 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 25 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 28 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 33 - column: 9 - suggestions: null -- message: Possibly it may occur an infinite reactive loop. - line: 36 - column: 9 - suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte deleted file mode 100644 index bb5395b5d..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test02-input.svelte +++ /dev/null @@ -1,39 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte deleted file mode 100644 index 3ba4e9b83..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test06-input.svelte +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte deleted file mode 100644 index b16489719..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test09-input.svelte +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte deleted file mode 100644 index b36bfe876..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-input.svelte +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte deleted file mode 100644 index e0658f29e..000000000 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test14-input.svelte +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-errors.yaml new file mode 100644 index 000000000..ea025838f --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-errors.yaml @@ -0,0 +1,16 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 14 + column: 7 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 20 + column: 5 + suggestions: null +- message: Possibly it may occur an infinite reactive loop. + line: 25 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-input.svelte new file mode 100644 index 000000000..148ca8346 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test01-input.svelte @@ -0,0 +1,27 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-errors.yaml similarity index 58% rename from tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml rename to tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-errors.yaml index e9ec294fd..f5fb69487 100644 --- a/tests/fixtures/rules/infinite-reactive-loop/invalid/test12-errors.yaml +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-errors.yaml @@ -1,12 +1,12 @@ - message: Possibly it may occur an infinite reactive loop. - line: 5 - column: 5 + line: 17 + column: 7 suggestions: null -- message: Possibly it may occur an infinite reactive loop because this function may update `obj`. - line: 10 - column: 11 +- message: Possibly it may occur an infinite reactive loop. + line: 23 + column: 5 suggestions: null - message: Possibly it may occur an infinite reactive loop. - line: 11 + line: 28 column: 5 suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-input.svelte new file mode 100644 index 000000000..bf5784428 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-input.svelte @@ -0,0 +1,30 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/valid/test08-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/valid/test08-input.svelte new file mode 100644 index 000000000..1f6d0ea9a --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/valid/test08-input.svelte @@ -0,0 +1,16 @@ + From e1eec7e1e755c4e0da9bbf111e108e7359af4a59 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 29 Dec 2022 16:14:26 +0900 Subject: [PATCH 05/12] chore: should use old es version? --- tests/src/rules/infinite-reactive-loop.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/rules/infinite-reactive-loop.ts b/tests/src/rules/infinite-reactive-loop.ts index 3eba1294c..a59288b36 100644 --- a/tests/src/rules/infinite-reactive-loop.ts +++ b/tests/src/rules/infinite-reactive-loop.ts @@ -9,7 +9,7 @@ const tester = new RuleTester({ }, env: { browser: true, - es2022: true, + es2017: true, }, }) From 61558aa93a0c05c8b03b72db289379d2a6ba3015 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 29 Dec 2022 16:23:22 +0900 Subject: [PATCH 06/12] chore: update docs --- docs/rules/infinite-reactive-loop.md | 48 +++++++++++----------------- package.json | 2 +- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/docs/rules/infinite-reactive-loop.md b/docs/rules/infinite-reactive-loop.md index 546c0a142..c34aa6112 100644 --- a/docs/rules/infinite-reactive-loop.md +++ b/docs/rules/infinite-reactive-loop.md @@ -41,55 +41,45 @@ This rule reports those possible infinite loop. await new Promise((resolve) => setTimeout(resolve, 100)) })() - const doSomething = async () => { - await fetchFromServer() - } - $: (async () => { - await doSomething() + await doSomething_ok() })() + const doSomething_ok = async () => { + await fetchFromServer() + // You can update a state even in different microtask + // if you don't refer the state in reactive statement. + a += 1 + } + // ✗ BAD $: (async () => { await doSomething() + // Do not update a state in different micro task. a += 1 $count += 1 })() - $: Promise.resolve().then(() => { - a += 1 - $count += 1 - }) - - $: setTimeout(() => { + $: tick(() => { a = a + 1 $count += 1 - }, 100) - - const doSomething2_1 = () => { - a += 1 - } - - const doSomething2 = async () => { - a += 1 - await fetchFromServer() - doSomething2_1() - } + }) $: (async () => { console.log(a) - await doSomething2() + // This rule checks caller function recursively. + await doSomething_ng_1() })() - const doSomething3 = () => { + const doSomething_ng_1 = async () => { a += 1 - $count += 1 + await fetchFromServer() + doSomething_ng_2() } - $: (async () => { - console.log(a, $count) - tick(() => doSomething3()) - })() + const doSomething_ng_2 = () => { + a += 1 + } ``` diff --git a/package.json b/package.json index f38baa0d3..dd00edf55 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,7 @@ "access": "public" }, "typeCoverage": { - "atLeast": 99.05, + "atLeast": 99.06, "cache": true, "detail": true, "ignoreAsAssertion": true, From 75ab27b1242561c09c37f8cc87c9cd2834ec4a58 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 29 Dec 2022 16:55:51 +0900 Subject: [PATCH 07/12] chore: add comment and test --- src/rules/infinite-reactive-loop.ts | 16 ++++++++++++++-- .../invalid/function-call/test11-errors.yaml | 4 ++++ .../invalid/function-call/test11-input.svelte | 11 +++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-errors.yaml create mode 100644 tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-input.svelte diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index d41cb3289..4f5756d55 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -252,7 +252,16 @@ function getFunctionDeclarationNode( return declaration } -/** */ +/** + * If the node is inside of a function, return true. + * + * e.g. `$: await foo()` + * if `node` is `foo`, return false because reactive statement is not function. + * + * e.g. `const bar = () => foo()` + * if `node` is `foo`, return true. + * + */ function isInsideOfFunction(node: TSESTree.Node) { let parent: TSESTree.Node | AST.SvelteReactiveStatement | null = node while (parent) { @@ -271,7 +280,7 @@ function isInsideOfFunction(node: TSESTree.Node) { return false } -/** */ +/** Let's lint! */ function doLint( context: RuleContext, ast: TSESTree.Node, @@ -355,6 +364,8 @@ function doLint( 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 } @@ -396,6 +407,7 @@ export default createRule("infinite-reactive-loop", { description: "Svelte runtime prevents calling the same reactive statement twice in a microtask. But between different microtask, it doesn't prevent.", category: "Possible Errors", + // TODO Switch to recommended in the major version. recommended: false, }, schema: [], diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-errors.yaml new file mode 100644 index 000000000..14501f84c --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-errors.yaml @@ -0,0 +1,4 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 8 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-input.svelte new file mode 100644 index 000000000..73a77c985 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test11-input.svelte @@ -0,0 +1,11 @@ + From 64a78254a6df283c6d3242d21c30fdd8c8ba8ac6 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 5 Feb 2023 15:34:28 +0900 Subject: [PATCH 08/12] Update src/rules/infinite-reactive-loop.ts Co-authored-by: Yosuke Ota --- src/rules/infinite-reactive-loop.ts | 51 +++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 4f5756d55..f31f53e27 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -162,6 +162,37 @@ function isPromiseThenOrCatchBody(node: TSESTree.Node): boolean { return ["then", "catch"].includes(property.name) } + +/** + * Get all reactive variable reference. + */ +function getAllReactiveVariableReferences(context: RuleContext) { + const scopeManager = context.getSourceCode().scopeManager + // Find the top-level (module or global) scope. + // Any variable defined at the top-level (module scope or global scope) can be made reactive. + const toplevelScope = + scopeManager.globalScope?.childScopes.find( + (scope) => scope.type === "module", + ) || scopeManager.globalScope + if (!toplevelScope) { + return [] + } + + // Extracts all reactive references to variables defined in the top-level scope. + const reactiveVariableNodes: TSESTree.Identifier[] = [] + for (const variable of toplevelScope.variables) { + for (const reference of variable.references) { + if ( + reference.identifier.type === "Identifier" && + !isFunctionCall(reference.identifier) + ) { + reactiveVariableNodes.push(reference.identifier) + } + } + } + return reactiveVariableNodes +} + /** * Get all tracked reactive variables. */ @@ -170,16 +201,16 @@ function getTrackedVariableNodes( ast: AST.SvelteReactiveStatement, ) { const reactiveVariableNodes: TSESTree.Identifier[] = [] - traverseNodes(ast.body, { - enterNode(node) { - if (isReactiveVariableNode(context, node)) { - reactiveVariableNodes.push(node) - } - }, - leaveNode() { - /* noop */ - }, - }) + for (const identifier of getAllReactiveVariableReferences(context)) { + if ( + // If the identifier is within the reactive statement range, + // it is used within the reactive statement. + ast.range[0] <= identifier.range[0] && + identifier.range[1] <= ast.range[1] + ) { + reactiveVariableNodes.push(identifier) + } + } return reactiveVariableNodes } From 4d64ecfcda7d6526b04dc7c8662c911ac578f12e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 5 Feb 2023 15:44:10 +0900 Subject: [PATCH 09/12] Update src/rules/infinite-reactive-loop.ts Co-authored-by: Yosuke Ota --- src/rules/infinite-reactive-loop.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index f31f53e27..547dcd997 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -107,23 +107,7 @@ function isReactiveVariableNode( node: TSESTree.Node, ): node is TSESTree.Identifier { if (node.type !== "Identifier") return false - if (!isVariableNode(node) || isFunctionCall(node)) return false - - // Variable name starts with `$` means Svelte store. - if (node.name.startsWith("$")) return true - const scope = getScope(context, node) - return scope.references.some((reference) => { - const { resolved } = reference - if (!resolved || resolved.name !== node.name) return false - - return resolved.defs.some((def) => { - return ( - (def as any).parent?.parent?.type === "SvelteScriptElement" && - def.name.type === "Identifier" && - def.name.name === node.name - ) - }) - }) + return getAllReactiveVariableReferences(context).includes(node) } /** From 6cd570ecd568c0c28416f74233ff9a12dc8fd678 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 5 Feb 2023 15:48:05 +0900 Subject: [PATCH 10/12] Update src/rules/infinite-reactive-loop.ts Co-authored-by: Yosuke Ota --- src/rules/infinite-reactive-loop.ts | 51 +++++++++++------------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 547dcd997..a5280b1e2 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -228,43 +228,30 @@ function getDeclarationBody( /** */ function getFunctionDeclarationNode( + context: RuleContext, functionCall: TSESTree.Identifier, ): TSESTree.BlockStatement | TSESTree.Expression | null { - let parent: AST.SvelteScriptElement | TSESTree.Node | undefined = functionCall - let declaration: TSESTree.BlockStatement | TSESTree.Expression | null = null - - while (parent) { - if (declaration) return declaration - parent = parent.parent as - | AST.SvelteScriptElement - | TSESTree.Node - | undefined - if (parent && parent.type === "BlockStatement") { - traverseNodes(parent, { - // eslint-disable-next-line no-loop-func -- ignore - enterNode(node) { - if (!declaration) { - declaration = getDeclarationBody(node, functionCall.name) - } - }, - leaveNode() { - /* noop */ - }, - }) - } else if (parent && parent.type === "SvelteScriptElement") { - for (const node of parent.body) { - if (declaration) break - if (node.type === "VariableDeclaration") { - for (const child of node.declarations) { - declaration = getDeclarationBody(child, functionCall.name) - if (declaration) break - } - } + const variable = findVariable(context, functionCall) + if (!variable) { + return null + } + for (const def of variable.defs) { + if (def.type === "FunctionName") { + if (def.node.type === "FunctionDeclaration") { + return def.node.body + } + } + if (def.type === "Variable") { + if ( + def.node.init && + (def.node.init.type === "FunctionExpression" || + def.node.init.type === "ArrowFunctionExpression") + ) { + return def.node.init.body } } } - - return declaration + return null } /** From 1eab26bb26b2af838446dacb51399a61293165b0 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 5 Feb 2023 15:56:59 +0900 Subject: [PATCH 11/12] chore: refactor --- src/rules/infinite-reactive-loop.ts | 62 +++++++++++------------------ 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index a5280b1e2..1028f0200 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -3,7 +3,7 @@ import type { AST } from "svelte-eslint-parser" import { ReferenceTracker } from "eslint-utils" import { createRule } from "../utils" import type { RuleContext } from "../types" -import { getScope } from "../utils/ast-utils" +import { findVariable } from "../utils/ast-utils" import { traverseNodes } from "svelte-eslint-parser" /** @@ -74,40 +74,15 @@ function isFunctionCall(node: TSESTree.Node): boolean { return parent.callee.type === "Identifier" && parent.callee.name === node.name } -/** - * Return true if `node` is a variable. - * - * e.g. foo.bar - * If node is `foo`, return true. - * If node is `bar`, return false. - * - * e.g. let baz = 1 - * If node is `baz`, return true. - */ -function isVariableNode(node: TSESTree.Identifier): boolean { - const { parent } = node - if (parent?.type !== "MemberExpression") return true - if ( - parent.type === "MemberExpression" && - parent.object.type !== "Identifier" - ) { - return false - } - - return parent.object.type !== "Identifier" - ? false - : parent.object.name === node.name -} - /** * Return true if `node` is a reactive variable. */ function isReactiveVariableNode( - context: RuleContext, + reactiveVariableReferences: TSESTree.Identifier[], node: TSESTree.Node, ): node is TSESTree.Identifier { if (node.type !== "Identifier") return false - return getAllReactiveVariableReferences(context).includes(node) + return reactiveVariableReferences.includes(node) } /** @@ -146,11 +121,10 @@ function isPromiseThenOrCatchBody(node: TSESTree.Node): boolean { return ["then", "catch"].includes(property.name) } - /** * Get all reactive variable reference. */ -function getAllReactiveVariableReferences(context: RuleContext) { +function getReactiveVariableReferences(context: RuleContext) { const scopeManager = context.getSourceCode().scopeManager // Find the top-level (module or global) scope. // Any variable defined at the top-level (module scope or global scope) can be made reactive. @@ -181,18 +155,18 @@ function getAllReactiveVariableReferences(context: RuleContext) { * Get all tracked reactive variables. */ function getTrackedVariableNodes( - context: RuleContext, + reactiveVariableReferences: TSESTree.Identifier[], ast: AST.SvelteReactiveStatement, ) { - const reactiveVariableNodes: TSESTree.Identifier[] = [] - for (const identifier of getAllReactiveVariableReferences(context)) { + const reactiveVariableNodes: Set = new Set() + for (const identifier of reactiveVariableReferences) { if ( // If the identifier is within the reactive statement range, // it is used within the reactive statement. ast.range[0] <= identifier.range[0] && identifier.range[1] <= ast.range[1] ) { - reactiveVariableNodes.push(identifier) + reactiveVariableNodes.add(identifier) } } return reactiveVariableNodes @@ -293,6 +267,7 @@ function doLint( name: string }[], reactiveVariableNames: string[], + reactiveVariableReferences: TSESTree.Identifier[], pIsSameTask: boolean, ) { let isSameMicroTask = pIsSameTask @@ -325,7 +300,10 @@ function doLint( if (node.type === "Identifier" && isFunctionCall(node)) { // traverse used functions body - const functionDeclarationNode = getFunctionDeclarationNode(node) + const functionDeclarationNode = getFunctionDeclarationNode( + context, + node, + ) if (functionDeclarationNode) { doLint( context, @@ -334,6 +312,7 @@ function doLint( tickCallExpressions, taskReferences, reactiveVariableNames, + reactiveVariableReferences, isSameMicroTask, ) } @@ -341,7 +320,7 @@ function doLint( if (!isSameMicroTask) { if ( - isReactiveVariableNode(context, node) && + isReactiveVariableNode(reactiveVariableReferences, node) && reactiveVariableNames.includes(node.name) && isNodeForAssign(node) ) { @@ -425,14 +404,21 @@ export default createRule("infinite-reactive-loop", { ["SvelteReactiveStatement"]: (ast: AST.SvelteReactiveStatement) => { const tickCallExpressions = extractTickReferences(context) const taskReferences = extractTaskReferences(context) - const trackedVariableNodes = getTrackedVariableNodes(context, ast) + const reactiveVariableReferences = + getReactiveVariableReferences(context) + const trackedVariableNodes = getTrackedVariableNodes( + reactiveVariableReferences, + ast, + ) + doLint( context, ast.body, [], tickCallExpressions, taskReferences, - trackedVariableNodes.map((node) => node.name), + Array.from(trackedVariableNodes).map((node) => node.name), + reactiveVariableReferences, true, ) }, From 336dfa1949b39f6729b7ba3b2aaf63c0ce66b62e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 5 Feb 2023 16:08:13 +0900 Subject: [PATCH 12/12] chore: remove duplicate code --- package.json | 2 +- src/rules/infinite-reactive-loop.ts | 27 ++++++--------------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 1f1a4b835..55c8e498a 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,7 @@ "access": "public" }, "typeCoverage": { - "atLeast": 99.06, + "atLeast": 99.08, "cache": true, "detail": true, "ignoreAsAssertion": true, diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 1028f0200..83586ac77 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -272,10 +272,13 @@ function doLint( ) { let isSameMicroTask = pIsSameTask + const differentMicroTaskEnterNodes: TSESTree.Node[] = [] + traverseNodes(ast, { enterNode(node) { // Promise.then() or Promise.catch() is called. if (isPromiseThenOrCatchBody(node)) { + differentMicroTaskEnterNodes.push(node) isSameMicroTask = false } @@ -285,6 +288,7 @@ function doLint( ...taskReferences, ]) { if (isChildNode(callExpression, node)) { + differentMicroTaskEnterNodes.push(node) isSameMicroTask = false } } @@ -295,6 +299,7 @@ function doLint( node.parent?.right.type === "AwaitExpression" && node.parent?.left === node ) { + differentMicroTaskEnterNodes.push(node) isSameMicroTask = false } @@ -355,27 +360,7 @@ function doLint( } } - // Promise.then() or Promise.catch() is called. - if (isPromiseThenOrCatchBody(node)) { - isSameMicroTask = true - } - - // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called - for (const { node: callExpression } of [ - ...tickCallExpressions, - ...taskReferences, - ]) { - if (isChildNode(callExpression, node)) { - isSameMicroTask = true - } - } - - // left side of await block - if ( - node.parent?.type === "AssignmentExpression" && - node.parent?.right.type === "AwaitExpression" && - node.parent?.left === node - ) { + if (differentMicroTaskEnterNodes.includes(node)) { isSameMicroTask = true } },