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 diff --git a/README.md b/README.md index 7f4340e4e..534bc6460 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,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 eab73c0bf..1b53df68e 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..c34aa6112 --- /dev/null +++ b/docs/rules/infinite-reactive-loop.md @@ -0,0 +1,100 @@ +--- +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/package.json b/package.json index d78e8525a..55c8e498a 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,7 @@ "access": "public" }, "typeCoverage": { - "atLeast": 99.05, + "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 new file mode 100644 index 000000000..83586ac77 --- /dev/null +++ b/src/rules/infinite-reactive-loop.ts @@ -0,0 +1,412 @@ +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 { findVariable } from "../utils/ast-utils" +import { traverseNodes } from "svelte-eslint-parser" + +/** + * Get usage of `tick` + */ +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], + } + }) +} + +/** + * Get usage of `setTimeout`, `setInterval`, `queueMicrotask` + */ +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], + } + }) +} + +/** + * 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 + parent = parent.parent + } + 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 + if (parent?.type !== "CallExpression") return false + return parent.callee.type === "Identifier" && parent.callee.name === node.name +} + +/** + * Return true if `node` is a reactive variable. + */ +function isReactiveVariableNode( + reactiveVariableReferences: TSESTree.Identifier[], + node: TSESTree.Node, +): node is TSESTree.Identifier { + if (node.type !== "Identifier") return false + return reactiveVariableReferences.includes(node) +} + +/** + * 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 + } + 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 + ) +} + +/** + * 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 ( + 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) +} + +/** + * Get all reactive variable reference. + */ +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. + 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. + */ +function getTrackedVariableNodes( + reactiveVariableReferences: TSESTree.Identifier[], + ast: AST.SvelteReactiveStatement, +) { + 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.add(identifier) + } + } + 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( + context: RuleContext, + functionCall: TSESTree.Identifier, +): TSESTree.BlockStatement | TSESTree.Expression | null { + 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 null +} + +/** + * 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) { + 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 +} + +/** Let's lint! */ +function doLint( + context: RuleContext, + ast: TSESTree.Node, + callFuncIdentifiers: TSESTree.Identifier[], + tickCallExpressions: { node: TSESTree.CallExpression; name: string }[], + taskReferences: { + node: TSESTree.CallExpression + name: string + }[], + reactiveVariableNames: string[], + reactiveVariableReferences: TSESTree.Identifier[], + pIsSameTask: boolean, +) { + 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 + } + + // `tick`, `setTimeout`, `setInterval` , `queueMicrotask` is called + for (const { node: callExpression } of [ + ...tickCallExpressions, + ...taskReferences, + ]) { + if (isChildNode(callExpression, node)) { + differentMicroTaskEnterNodes.push(node) + isSameMicroTask = false + } + } + + // left side of await block + if ( + node.parent?.type === "AssignmentExpression" && + node.parent?.right.type === "AwaitExpression" && + node.parent?.left === node + ) { + differentMicroTaskEnterNodes.push(node) + isSameMicroTask = false + } + + if (node.type === "Identifier" && isFunctionCall(node)) { + // traverse used functions body + const functionDeclarationNode = getFunctionDeclarationNode( + context, + node, + ) + if (functionDeclarationNode) { + doLint( + context, + functionDeclarationNode, + [...callFuncIdentifiers, node], + tickCallExpressions, + taskReferences, + reactiveVariableNames, + reactiveVariableReferences, + isSameMicroTask, + ) + } + } + + if (!isSameMicroTask) { + if ( + isReactiveVariableNode(reactiveVariableReferences, node) && + reactiveVariableNames.includes(node.name) && + isNodeForAssign(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) { + if (node.type === "AwaitExpression") { + if ((ast.parent?.type as string) === "SvelteReactiveStatement") { + // MEMO: It checks that `await` is used in reactive statement directly or not. + // If `await` is used in inner function of a reactive statement, result of `isInsideOfFunction` will be `true`. + if (!isInsideOfFunction(node)) { + isSameMicroTask = false + } + } else { + isSameMicroTask = false + } + } + + if (differentMicroTaskEnterNodes.includes(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", + // TODO Switch to recommended in the major version. + 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 reactiveVariableReferences = + getReactiveVariableReferences(context) + const trackedVariableNodes = getTrackedVariableNodes( + reactiveVariableReferences, + ast, + ) + + doLint( + context, + ast.body, + [], + tickCallExpressions, + taskReferences, + Array.from(trackedVariableNodes).map((node) => node.name), + reactiveVariableReferences, + true, + ) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index ceba1f76b..e9d10b55d 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" @@ -59,6 +60,7 @@ export const rules = [ htmlQuotes, htmlSelfClosing, indent, + infiniteReactiveLoop, maxAttributesPerLine, mustacheSpacing, noAtDebugTags, diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-errors.yaml new file mode 100644 index 000000000..3def66667 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/await/test01-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/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/function-call/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-errors.yaml new file mode 100644 index 000000000..360ccfc7d --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-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/function-call/test01-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-input.svelte new file mode 100644 index 000000000..e9663f8d6 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-errors.yaml new file mode 100644 index 000000000..457e93042 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-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/function-call/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-input.svelte new file mode 100644 index 000000000..83b8f9fbe --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test02-input.svelte @@ -0,0 +1,21 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-errors.yaml new file mode 100644 index 000000000..c6b46f0ad --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-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/function-call/test03-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-input.svelte new file mode 100644 index 000000000..a845142c1 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test03-input.svelte @@ -0,0 +1,20 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-errors.yaml new file mode 100644 index 000000000..592ba8cb5 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-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/function-call/test04-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-input.svelte new file mode 100644 index 000000000..62fbca902 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test04-input.svelte @@ -0,0 +1,26 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-errors.yaml new file mode 100644 index 000000000..90ec1cf42 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-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/function-call/test05-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-input.svelte new file mode 100644 index 000000000..2ceb8ba99 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test05-input.svelte @@ -0,0 +1,14 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-errors.yaml new file mode 100644 index 000000000..c925e6039 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-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/function-call/test06-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-input.svelte new file mode 100644 index 000000000..3225fffac --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test06-input.svelte @@ -0,0 +1,12 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-errors.yaml new file mode 100644 index 000000000..7002c04b2 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-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/function-call/test07-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-input.svelte new file mode 100644 index 000000000..8905d82f5 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test07-input.svelte @@ -0,0 +1,19 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml new file mode 100644 index 000000000..62970cbbb --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-errors.yaml @@ -0,0 +1,12 @@ +- 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 `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/function-call/test08-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-input.svelte new file mode 100644 index 000000000..e763fe9ad --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test08-input.svelte @@ -0,0 +1,16 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-errors.yaml new file mode 100644 index 000000000..f8ec40608 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-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/function-call/test09-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-input.svelte new file mode 100644 index 000000000..f80ae830f --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/function-call/test09-input.svelte @@ -0,0 +1,13 @@ + 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/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 @@ + diff --git a/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml new file mode 100644 index 000000000..be27e282c --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test01-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 6 + column: 7 + suggestions: null +- 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: 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/promise/test06-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-errors.yaml new file mode 100644 index 000000000..8f77e5a5b --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/promise/test06-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/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/setTimeout/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-errors.yaml new file mode 100644 index 000000000..48a6fc271 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-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/setTimeout/test02-input.svelte b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-input.svelte new file mode 100644 index 000000000..87a7e87ca --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/setTimeout/test02-input.svelte @@ -0,0 +1,7 @@ + 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/tick/test02-errors.yaml b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-errors.yaml new file mode 100644 index 000000000..f5fb69487 --- /dev/null +++ b/tests/fixtures/rules/infinite-reactive-loop/invalid/tick/test02-errors.yaml @@ -0,0 +1,12 @@ +- message: Possibly it may occur an infinite reactive loop. + line: 17 + column: 7 + suggestions: null +- 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: 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/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/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 @@ + 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 @@ + diff --git a/tests/src/rules/infinite-reactive-loop.ts b/tests/src/rules/infinite-reactive-loop.ts new file mode 100644 index 000000000..a59288b36 --- /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, + es2017: true, + }, +}) + +tester.run( + "infinite-reactive-loop", + rule as any, + loadTestCases("infinite-reactive-loop"), +)