From 7e51ff3394bb2c28d2f8fb3a389018f38a5ecf86 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 14 May 2023 20:04:05 +0900 Subject: [PATCH 01/12] implement --- README.md | 1 + docs/rules.md | 1 + docs/rules/valid-context-access.md | 67 +++++++++++ src/rules/reference-helpers/svelte-context.ts | 42 +++++++ src/rules/valid-context-access.ts | 113 ++++++++++++++++++ src/utils/rules.ts | 2 + .../invalid/case01-errors.yaml | 36 ++++++ .../invalid/case01-input.svelte | 36 ++++++ .../invalid/case02-errors.yaml | 3 + .../invalid/case02-input.svelte | 7 ++ .../invalid/case03-errors.yaml | 4 + .../invalid/case03-input.svelte | 10 ++ .../invalid/case04-errors.yaml | 4 + .../invalid/case04-input.svelte | 13 ++ .../invalid/case05-errors.yaml | 4 + .../invalid/case05-input.svelte | 12 ++ .../valid/case01-input.svelte | 8 ++ .../valid/case02-input.svelte | 6 + .../valid/case03-input.svelte | 8 ++ .../valid/case04-input.svelte | 15 +++ .../valid/case05-input.svelte | 6 + .../valid/case06-input.svelte | 15 +++ .../valid/case07-input.svelte | 12 ++ tests/src/rules/valid-context-access.ts | 16 +++ 24 files changed, 441 insertions(+) create mode 100644 docs/rules/valid-context-access.md create mode 100644 src/rules/reference-helpers/svelte-context.ts create mode 100644 src/rules/valid-context-access.ts create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case01-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case01-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case02-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case02-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case03-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case03-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case04-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case04-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case05-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case05-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case01-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case02-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case03-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case04-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case05-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case06-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case07-input.svelte create mode 100644 tests/src/rules/valid-context-access.ts diff --git a/README.md b/README.md index a5661af1c..17599fee5 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/require-store-callbacks-use-set-param](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-callbacks-use-set-param/) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-store-reactive-access/) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: | | [svelte/valid-compile](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-compile/) | disallow warnings when compiling. | :star: | +| [svelte/valid-context-access](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-context-access/) | context functions must be called during component initialization. | | | [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props other than data or errors in SvelteKit page components. | | ## Security Vulnerability diff --git a/docs/rules.md b/docs/rules.md index ef85cb467..968875980 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -33,6 +33,7 @@ These rules relate to possible syntax or logic errors in Svelte code: | [svelte/require-store-callbacks-use-set-param](./rules/require-store-callbacks-use-set-param.md) | store callbacks must use `set` param | | | [svelte/require-store-reactive-access](./rules/require-store-reactive-access.md) | disallow to use of the store itself as an operand. Need to use $ prefix or get function. | :wrench: | | [svelte/valid-compile](./rules/valid-compile.md) | disallow warnings when compiling. | :star: | +| [svelte/valid-context-access](./rules/valid-context-access.md) | context functions must be called during component initialization. | | | [svelte/valid-prop-names-in-kit-pages](./rules/valid-prop-names-in-kit-pages.md) | disallow props other than data or errors in SvelteKit page components. | | ## Security Vulnerability diff --git a/docs/rules/valid-context-access.md b/docs/rules/valid-context-access.md new file mode 100644 index 000000000..ff113111f --- /dev/null +++ b/docs/rules/valid-context-access.md @@ -0,0 +1,67 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/valid-context-access" +description: "context functions must be called during component initialization." +--- + +# svelte/valid-context-access + +> context functions must be called during component initialization. + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule reports where context API is called except during component initialization. + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further Reading + +- [Svelte - Docs > RUN TIME > svelte > setContext](https://svelte.dev/docs#run-time-svelte-setcontext) +- [Svelte - Docs > RUN TIME > svelte > getContext](https://svelte.dev/docs#run-time-svelte-getContext) +- [Svelte - Docs > RUN TIME > svelte > hasContext](https://svelte.dev/docs#run-time-svelte-hasContext) +- [Svelte - Docs > RUN TIME > svelte > getAllContexts](https://svelte.dev/docs#run-time-svelte-getAllContexts) + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/valid-context-access.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/valid-context-access.ts) diff --git a/src/rules/reference-helpers/svelte-context.ts b/src/rules/reference-helpers/svelte-context.ts new file mode 100644 index 000000000..ebeb8f57a --- /dev/null +++ b/src/rules/reference-helpers/svelte-context.ts @@ -0,0 +1,42 @@ +import type { TSESTree } from "@typescript-eslint/types" +import { ReferenceTracker } from "@eslint-community/eslint-utils" +import type { RuleContext } from "../../types" + +type ContextName = "setContext" | "getContext" | "hasContext" | "getAllContexts" + +/** Extract svelte's context API references */ +export function* extractContextReferences( + context: RuleContext, + contextNames: ContextName[] = [ + "setContext", + "getContext", + "hasContext", + "getAllContexts", + ], +): Generator<{ node: TSESTree.CallExpression; name: string }, void> { + const referenceTracker = new ReferenceTracker( + context.getSourceCode().scopeManager.globalScope!, + ) + for (const { node, path } of referenceTracker.iterateEsmReferences({ + svelte: { + [ReferenceTracker.ESM]: true, + setContext: { + [ReferenceTracker.CALL]: contextNames.includes("setContext"), + }, + getContext: { + [ReferenceTracker.CALL]: contextNames.includes("getContext"), + }, + hasContext: { + [ReferenceTracker.CALL]: contextNames.includes("hasContext"), + }, + getAllContexts: { + [ReferenceTracker.CALL]: contextNames.includes("getAllContexts"), + }, + }, + })) { + yield { + node: node as TSESTree.CallExpression, + name: path[path.length - 1], + } + } +} diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts new file mode 100644 index 000000000..bcc6ca5b3 --- /dev/null +++ b/src/rules/valid-context-access.ts @@ -0,0 +1,113 @@ +import { createRule } from "../utils" +import { extractContextReferences } from "./reference-helpers/svelte-context" +import type { TSESTree } from "@typescript-eslint/types" + +export default createRule("valid-context-access", { + meta: { + docs: { + description: + "context functions must be called during component initialization.", + category: "Possible Errors", + // TODO Switch to recommended in the major version. + recommended: false, + }, + schema: [], + messages: { + unexpected: + "Do not call {{function}} except during component initialization.", + }, + type: "problem", + }, + create(context) { + const scopeManager = context.getSourceCode().scopeManager + const toplevelScope = + scopeManager.globalScope?.childScopes.find( + (scope) => scope.type === "module", + ) || scopeManager.globalScope + + /** report ESLint error */ + function report(node: TSESTree.CallExpression) { + context.report({ + loc: node.loc, + messageId: "unexpected", + data: { + function: + node.callee.type === "Identifier" + ? node.callee.name + : "context function", + }, + }) + } + + /** Get nodes where the variable is used */ + function getReferences(id: TSESTree.Identifier | TSESTree.BindingName) { + const variable = toplevelScope?.variables.find((v) => { + if (id.type === "Identifier") { + return v.identifiers.includes(id) + } + return false + }) + if (variable) { + return variable.references.filter((r) => r.identifier !== id) + } + return [] + } + + /** Let's lint! */ + function doLint( + visitedCallExpressions: TSESTree.CallExpression[], + contextCallExpression: TSESTree.CallExpression, + currentNode: TSESTree.CallExpression, + ) { + let { parent } = currentNode + if (parent?.type !== "ExpressionStatement") { + report(contextCallExpression) + return + } + while (parent) { + parent = parent.parent + if ( + parent?.type === "VariableDeclaration" || + parent?.type === "FunctionDeclaration" + ) { + const references = + parent.type === "VariableDeclaration" + ? getReferences(parent.declarations[0].id) + : parent.id + ? getReferences(parent.id) + : [] + + for (const reference of references) { + if (reference.identifier?.parent?.type === "CallExpression") { + if ( + !visitedCallExpressions.includes(reference.identifier.parent) + ) { + visitedCallExpressions.push(reference.identifier.parent) + doLint( + visitedCallExpressions, + contextCallExpression, + reference.identifier?.parent, + ) + } + } + } + } else if (parent?.type === "ExpressionStatement") { + if (parent.expression.type !== "CallExpression") { + report(contextCallExpression) + } else if (parent.expression.callee.type === "Identifier") { + report(contextCallExpression) + } + } + } + } + + return { + Program() { + for (const { node } of extractContextReferences(context)) { + const visitedCallExpressions: TSESTree.CallExpression[] = [] + doLint(visitedCallExpressions, node, node) + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 7dac11750..487616784 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -57,6 +57,7 @@ import sortAttributes from "../rules/sort-attributes" import spacedHtmlComment from "../rules/spaced-html-comment" import system from "../rules/system" import validCompile from "../rules/valid-compile" +import validContextAccess from "../rules/valid-context-access" import validEachKey from "../rules/valid-each-key" import validPropNamesInKitPages from "../rules/valid-prop-names-in-kit-pages" @@ -116,6 +117,7 @@ export const rules = [ spacedHtmlComment, system, validCompile, + validContextAccess, validEachKey, validPropNamesInKitPages, ] as RuleModule[] diff --git a/tests/fixtures/rules/valid-context-access/invalid/case01-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case01-errors.yaml new file mode 100644 index 000000000..22f67eb10 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case01-errors.yaml @@ -0,0 +1,36 @@ +- message: Do not call setContext except during component initialization. + line: 11 + column: 5 +- message: Do not call getContext except during component initialization. + line: 12 + column: 5 +- message: Do not call hasContext except during component initialization. + line: 13 + column: 5 +- message: Do not call getAllContexts except during component initialization. + line: 14 + column: 5 +- message: Do not call setContext except during component initialization. + line: 18 + column: 5 +- message: Do not call getContext except during component initialization. + line: 19 + column: 5 +- message: Do not call hasContext except during component initialization. + line: 20 + column: 5 +- message: Do not call getAllContexts except during component initialization. + line: 21 + column: 5 +- message: Do not call setContext except during component initialization. + line: 25 + column: 5 +- message: Do not call getContext except during component initialization. + line: 26 + column: 5 +- message: Do not call hasContext except during component initialization. + line: 27 + column: 5 +- message: Do not call getAllContexts except during component initialization. + line: 28 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case01-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case01-input.svelte new file mode 100644 index 000000000..859baf1bb --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case01-input.svelte @@ -0,0 +1,36 @@ + diff --git a/tests/fixtures/rules/valid-context-access/invalid/case02-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case02-errors.yaml new file mode 100644 index 000000000..f33732b66 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case02-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 5 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case02-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case02-input.svelte new file mode 100644 index 000000000..b0af74395 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case02-input.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/rules/valid-context-access/invalid/case03-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case03-errors.yaml new file mode 100644 index 000000000..96c6f1b93 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case03-errors.yaml @@ -0,0 +1,4 @@ +- message: Do not call setContext except during component initialization. + line: 4 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/valid-context-access/invalid/case03-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case03-input.svelte new file mode 100644 index 000000000..c88a5a69c --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case03-input.svelte @@ -0,0 +1,10 @@ + + + diff --git a/tests/fixtures/rules/valid-context-access/invalid/case04-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case04-errors.yaml new file mode 100644 index 000000000..96c6f1b93 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case04-errors.yaml @@ -0,0 +1,4 @@ +- message: Do not call setContext except during component initialization. + line: 4 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/valid-context-access/invalid/case04-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case04-input.svelte new file mode 100644 index 000000000..4b62a8d4e --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case04-input.svelte @@ -0,0 +1,13 @@ + + +{#if true} + {@const foo = something()} + +{/if} diff --git a/tests/fixtures/rules/valid-context-access/invalid/case05-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case05-errors.yaml new file mode 100644 index 000000000..96c6f1b93 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case05-errors.yaml @@ -0,0 +1,4 @@ +- message: Do not call setContext except during component initialization. + line: 4 + column: 5 + suggestions: null diff --git a/tests/fixtures/rules/valid-context-access/invalid/case05-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case05-input.svelte new file mode 100644 index 000000000..b21f3b9b8 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case05-input.svelte @@ -0,0 +1,12 @@ + + +{#if something()} + +{/if} diff --git a/tests/fixtures/rules/valid-context-access/valid/case01-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case01-input.svelte new file mode 100644 index 000000000..3ec7fa441 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case02-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case02-input.svelte new file mode 100644 index 000000000..bab6b283a --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case02-input.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte new file mode 100644 index 000000000..9bb8171c7 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte @@ -0,0 +1,8 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case04-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case04-input.svelte new file mode 100644 index 000000000..852518424 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case04-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case05-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case05-input.svelte new file mode 100644 index 000000000..49cce3bc7 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case05-input.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case06-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case06-input.svelte new file mode 100644 index 000000000..17e2ae7e2 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case06-input.svelte @@ -0,0 +1,15 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case07-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case07-input.svelte new file mode 100644 index 000000000..24c5ae0a4 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case07-input.svelte @@ -0,0 +1,12 @@ + + +{#if something} + +{/if} diff --git a/tests/src/rules/valid-context-access.ts b/tests/src/rules/valid-context-access.ts new file mode 100644 index 000000000..815950c1d --- /dev/null +++ b/tests/src/rules/valid-context-access.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/valid-context-access" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "valid-context-access", + rule as any, + loadTestCases("valid-context-access"), +) From 2c57127cfb7d6f31c07da8a98a2a11dc8f3c78ac Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 14 May 2023 20:06:43 +0900 Subject: [PATCH 02/12] add changeset --- .changeset/serious-mayflies-yell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/serious-mayflies-yell.md diff --git a/.changeset/serious-mayflies-yell.md b/.changeset/serious-mayflies-yell.md new file mode 100644 index 000000000..a03e61934 --- /dev/null +++ b/.changeset/serious-mayflies-yell.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: Add `svelte/valid-context-access` rule From 05f8917b1c2dac99ef37d9db7ca2cedfede169fb Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 28 May 2023 15:26:07 +0900 Subject: [PATCH 03/12] add test --- .../invalid/case06-errors.yaml | 4 ++++ .../invalid/case06-input.svelte | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case06-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case06-input.svelte diff --git a/tests/fixtures/rules/valid-context-access/invalid/case06-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case06-errors.yaml new file mode 100644 index 000000000..9d0ec949e --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case06-errors.yaml @@ -0,0 +1,4 @@ +- message: Do not call setContext except during component initialization. + line: 5 + column: 7 + suggestions: null diff --git a/tests/fixtures/rules/valid-context-access/invalid/case06-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case06-input.svelte new file mode 100644 index 000000000..310840bb8 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case06-input.svelte @@ -0,0 +1,15 @@ + + +{#if something()} + +{/if} From a86eac8312032e1008f84c67b2a9b59590d71bc6 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Mon, 29 May 2023 14:02:43 +0900 Subject: [PATCH 04/12] add if statement test --- .../valid-context-access/invalid/case07-errors.yaml | 3 +++ .../valid-context-access/invalid/case07-input.svelte | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case07-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case07-input.svelte diff --git a/tests/fixtures/rules/valid-context-access/invalid/case07-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case07-errors.yaml new file mode 100644 index 000000000..5702a7bed --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case07-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call hasContext except during component initialization. + line: 5 + column: 9 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case07-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case07-input.svelte new file mode 100644 index 000000000..5484f2dc9 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case07-input.svelte @@ -0,0 +1,9 @@ + From 72dffcce52d2b22421b63d471de719c2b69eddb8 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 14:07:20 +0900 Subject: [PATCH 05/12] fix if statement and support context module --- src/rules/valid-context-access.ts | 43 +++++++++++++++++-- .../invalid/case03-input.svelte | 2 - .../invalid/case08-errors.yaml | 3 ++ .../invalid/case08-input.svelte | 4 ++ .../valid/case03-input.svelte | 2 +- .../valid/case08-input.svelte | 22 ++++++++++ .../valid/case09-input.js | 17 ++++++++ 7 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case08-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case08-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case08-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case09-input.js diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index bcc6ca5b3..4b2179478 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -8,7 +8,6 @@ export default createRule("valid-context-access", { description: "context functions must be called during component initialization.", category: "Possible Errors", - // TODO Switch to recommended in the major version. recommended: false, }, schema: [], @@ -19,7 +18,28 @@ export default createRule("valid-context-access", { type: "problem", }, create(context) { - const scopeManager = context.getSourceCode().scopeManager + // // This rule doesn't check other than Svelte files. + if (!context.parserServices.isSvelte) { + return {} + } + + // Extract diff --git a/tests/fixtures/rules/valid-context-access/invalid/case08-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case08-errors.yaml new file mode 100644 index 000000000..8a807194f --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case08-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 3 + column: 3 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case08-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case08-input.svelte new file mode 100644 index 000000000..0017cf865 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case08-input.svelte @@ -0,0 +1,4 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte index 9bb8171c7..461a5f1f3 100644 --- a/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte +++ b/tests/fixtures/rules/valid-context-access/valid/case03-input.svelte @@ -1,5 +1,5 @@ diff --git a/tests/fixtures/rules/valid-context-access/valid/case09-input.js b/tests/fixtures/rules/valid-context-access/valid/case09-input.js new file mode 100644 index 000000000..4d3f8fe4c --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case09-input.js @@ -0,0 +1,17 @@ +import { setContext } from "svelte" + +const something = () => { + setContext("answer", 42) +} + +const something2 = async () => { + await Promise.resolve() + setContext("answer", 42) +} + +const aaa = (fn) => { + fn() +} + +aaa(() => something()) +something2() From d7d04490b0f03a506086b906633b94eb97f18576 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 15:53:12 +0900 Subject: [PATCH 06/12] fix function argument --- .../reference-helpers/svelte-lifecycle.ts | 53 +++++++++++++++++++ src/rules/valid-context-access.ts | 16 ++++-- .../invalid/case08-errors.yaml | 4 +- .../invalid/case08-input.svelte | 9 +++- .../invalid/case09-errors.yaml | 3 ++ .../invalid/case09-input.svelte | 4 ++ .../valid/case10-input.svelte | 6 +++ 7 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/rules/reference-helpers/svelte-lifecycle.ts create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case09-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case09-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/valid/case10-input.svelte diff --git a/src/rules/reference-helpers/svelte-lifecycle.ts b/src/rules/reference-helpers/svelte-lifecycle.ts new file mode 100644 index 000000000..2b05069b7 --- /dev/null +++ b/src/rules/reference-helpers/svelte-lifecycle.ts @@ -0,0 +1,53 @@ +import type { TSESTree } from "@typescript-eslint/types" +import { ReferenceTracker } from "@eslint-community/eslint-utils" +import type { RuleContext } from "../../types" + +type LifeCycleName = + | "onMount" + | "beforeUpdate" + | "afterUpdate" + | "onDestroy" + | "tick" + +/** + * Get usage of Svelte life cycle functions. + */ +export function* extractSvelteLifeCycleReferences( + context: RuleContext, + fuctionName: LifeCycleName[] = [ + "onMount", + "beforeUpdate", + "afterUpdate", + "onDestroy", + "tick", + ], +): Generator<{ node: TSESTree.CallExpression; name: string }, void> { + const referenceTracker = new ReferenceTracker( + context.getSourceCode().scopeManager.globalScope!, + ) + for (const { node, path } of referenceTracker.iterateEsmReferences({ + svelte: { + [ReferenceTracker.ESM]: true, + onMount: { + [ReferenceTracker.CALL]: fuctionName.includes("onMount"), + }, + beforeUpdate: { + [ReferenceTracker.CALL]: fuctionName.includes("beforeUpdate"), + }, + afterUpdate: { + [ReferenceTracker.CALL]: fuctionName.includes("afterUpdate"), + }, + onDestroy: { + [ReferenceTracker.CALL]: fuctionName.includes("onDestroy"), + }, + tick: { + [ReferenceTracker.CALL]: fuctionName.includes("tick"), + }, + }, + })) { + yield { + node: node as TSESTree.CallExpression, + name: path[path.length - 1], + } + } +} diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index 4b2179478..c08a10564 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -1,5 +1,6 @@ import { createRule } from "../utils" import { extractContextReferences } from "./reference-helpers/svelte-context" +import { extractSvelteLifeCycleReferences } from "./reference-helpers/svelte-lifecycle" import type { TSESTree } from "@typescript-eslint/types" export default createRule("valid-context-access", { @@ -23,8 +24,12 @@ export default createRule("valid-context-access", { return {} } - // Extract diff --git a/tests/fixtures/rules/valid-context-access/invalid/case09-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case09-errors.yaml new file mode 100644 index 000000000..8a807194f --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case09-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 3 + column: 3 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case09-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case09-input.svelte new file mode 100644 index 000000000..0017cf865 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case09-input.svelte @@ -0,0 +1,4 @@ + diff --git a/tests/fixtures/rules/valid-context-access/valid/case10-input.svelte b/tests/fixtures/rules/valid-context-access/valid/case10-input.svelte new file mode 100644 index 000000000..fac4aafc3 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/valid/case10-input.svelte @@ -0,0 +1,6 @@ + From 9e01170e9a2fb84715de24f9dcd1e7427a473f89 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 16:09:22 +0900 Subject: [PATCH 07/12] support await --- src/rules/valid-context-access.ts | 75 +++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index c08a10564..c1070f4bf 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -1,6 +1,7 @@ import { createRule } from "../utils" import { extractContextReferences } from "./reference-helpers/svelte-context" import { extractSvelteLifeCycleReferences } from "./reference-helpers/svelte-lifecycle" +import type { AST } from "svelte-eslint-parser" import type { TSESTree } from "@typescript-eslint/types" export default createRule("valid-context-access", { @@ -30,19 +31,20 @@ export default createRule("valid-context-access", { ).map((r) => r.node) // Extract From 736e5a11deeba579f017c4eaf2dec941da62eadb Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 16:54:38 +0900 Subject: [PATCH 10/12] support setTimeout and others --- src/rules/infinite-reactive-loop.ts | 2 +- src/rules/reference-helpers/microtask.ts | 32 +++++++++++++------ src/rules/valid-context-access.ts | 25 ++++++++++++--- .../invalid/case10-errors.yaml | 4 +-- .../invalid/case10-input.svelte | 7 ++-- .../invalid/case11-errors.yaml | 3 ++ .../invalid/case11-input.svelte | 11 +++++++ tests/src/rules/valid-context-access.ts | 4 +++ 8 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case11-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case11-input.svelte diff --git a/src/rules/infinite-reactive-loop.ts b/src/rules/infinite-reactive-loop.ts index 8197ca16d..fe0075e16 100644 --- a/src/rules/infinite-reactive-loop.ts +++ b/src/rules/infinite-reactive-loop.ts @@ -357,7 +357,7 @@ export default createRule("infinite-reactive-loop", { const tickCallExpressions = Array.from( extractSvelteLifeCycleReferences(context, ["tick"]), ) - const taskReferences = extractTaskReferences(context) + const taskReferences = Array.from(extractTaskReferences(context)) const reactiveVariableReferences = getReactiveVariableReferences(context) return { diff --git a/src/rules/reference-helpers/microtask.ts b/src/rules/reference-helpers/microtask.ts index ddaeab350..073b968a2 100644 --- a/src/rules/reference-helpers/microtask.ts +++ b/src/rules/reference-helpers/microtask.ts @@ -2,24 +2,36 @@ import type { TSESTree } from "@typescript-eslint/types" import { ReferenceTracker } from "@eslint-community/eslint-utils" import type { RuleContext } from "../../types" +type FunctionName = "setTimeout" | "setInterval" | "queueMicrotask" + /** * Get usage of `setTimeout`, `setInterval`, `queueMicrotask` */ -export function extractTaskReferences( +export function* extractTaskReferences( context: RuleContext, -): { node: TSESTree.CallExpression; name: string }[] { + functionNames: FunctionName[] = [ + "setTimeout", + "setInterval", + "queueMicrotask", + ], +): Generator<{ node: TSESTree.CallExpression; name: string }, void> { const referenceTracker = new ReferenceTracker( context.getSourceCode().scopeManager.globalScope!, ) - const a = referenceTracker.iterateGlobalReferences({ - setTimeout: { [ReferenceTracker.CALL]: true }, - setInterval: { [ReferenceTracker.CALL]: true }, - queueMicrotask: { [ReferenceTracker.CALL]: true }, - }) - return Array.from(a).map(({ node, path }) => { - return { + for (const { node, path } of referenceTracker.iterateGlobalReferences({ + setTimeout: { + [ReferenceTracker.CALL]: functionNames.includes("setTimeout"), + }, + setInterval: { + [ReferenceTracker.CALL]: functionNames.includes("setInterval"), + }, + queueMicrotask: { + [ReferenceTracker.CALL]: functionNames.includes("queueMicrotask"), + }, + })) { + yield { node: node as TSESTree.CallExpression, name: path[path.length - 1], } - }) + } } diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index c1070f4bf..45d15d2e3 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -1,6 +1,7 @@ import { createRule } from "../utils" import { extractContextReferences } from "./reference-helpers/svelte-context" import { extractSvelteLifeCycleReferences } from "./reference-helpers/svelte-lifecycle" +import { extractTaskReferences } from "./reference-helpers/microtask" import type { AST } from "svelte-eslint-parser" import type { TSESTree } from "@typescript-eslint/types" @@ -29,6 +30,7 @@ export default createRule("valid-context-access", { const lifeCycleReferences = Array.from( extractSvelteLifeCycleReferences(context), ).map((r) => r.node) + const taskReferences = Array.from(extractTaskReferences(context)) // Extract diff --git a/tests/fixtures/rules/valid-context-access/invalid/case11-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case11-errors.yaml new file mode 100644 index 000000000..0897a75a9 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case11-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 6 + column: 7 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case11-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case11-input.svelte new file mode 100644 index 000000000..dc9f3571b --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case11-input.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/src/rules/valid-context-access.ts b/tests/src/rules/valid-context-access.ts index 815950c1d..f40dafd6d 100644 --- a/tests/src/rules/valid-context-access.ts +++ b/tests/src/rules/valid-context-access.ts @@ -7,6 +7,10 @@ const tester = new RuleTester({ ecmaVersion: 2020, sourceType: "module", }, + env: { + browser: true, + es2017: true, + }, }) tester.run( From 6ff09948a711fc984b21e3f2310e3b0a104a9ae9 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 21:58:15 +0900 Subject: [PATCH 11/12] support then / catch --- src/rules/valid-context-access.ts | 6 ++++ src/utils/promise.ts | 31 +++++++++++++++++++ .../invalid/case12-errors.yaml | 12 +++++++ .../invalid/case12-input.svelte | 19 ++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 src/utils/promise.ts create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case12-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case12-input.svelte diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index 45d15d2e3..d4fc4ac8f 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -2,6 +2,7 @@ import { createRule } from "../utils" import { extractContextReferences } from "./reference-helpers/svelte-context" import { extractSvelteLifeCycleReferences } from "./reference-helpers/svelte-lifecycle" import { extractTaskReferences } from "./reference-helpers/microtask" +import { isInsideOfPromiseThenOrCatch } from "../utils/promise" import type { AST } from "svelte-eslint-parser" import type { TSESTree } from "@typescript-eslint/types" @@ -153,6 +154,11 @@ export default createRule("valid-context-access", { return } + if (isInsideOfPromiseThenOrCatch(currentNode)) { + report(contextCallExpression) + return + } + let { parent } = currentNode while (parent) { parent = parent.parent diff --git a/src/utils/promise.ts b/src/utils/promise.ts new file mode 100644 index 000000000..0e97e1ee9 --- /dev/null +++ b/src/utils/promise.ts @@ -0,0 +1,31 @@ +import type { TSESTree } from "@typescript-eslint/types" + +/** + * Return true if `node` is inside of `then` or `catch`. + */ +export function isInsideOfPromiseThenOrCatch(node: TSESTree.Node): boolean { + let parent: TSESTree.Node | undefined = node.parent + while (parent) { + parent = parent.parent + if (parent?.type !== "ExpressionStatement") { + continue + } + const expression = parent?.expression + if (expression == null || expression?.type !== "CallExpression") { + return false + } + + const callee = expression.callee + if (callee.type !== "MemberExpression") { + return false + } + + const property = callee.property + return ( + property.type === "Identifier" && + ["then", "catch"].includes(property.name) + ) + } + + return false +} diff --git a/tests/fixtures/rules/valid-context-access/invalid/case12-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case12-errors.yaml new file mode 100644 index 000000000..28b8f23b8 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case12-errors.yaml @@ -0,0 +1,12 @@ +- message: Do not call setContext except during component initialization. + line: 5 + column: 5 +- message: Do not call setContext except during component initialization. + line: 9 + column: 5 +- message: Do not call setContext except during component initialization. + line: 13 + column: 5 +- message: Do not call setContext except during component initialization. + line: 17 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case12-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case12-input.svelte new file mode 100644 index 000000000..c170ecc49 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case12-input.svelte @@ -0,0 +1,19 @@ + From ffc0bce80160b891b032e6445606f1f46b736b2e Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 3 Jun 2023 22:09:10 +0900 Subject: [PATCH 12/12] add tests and fix bug --- docs/rules/valid-context-access.md | 12 ++++++++++++ src/rules/valid-context-access.ts | 9 ++++++--- .../valid-context-access/invalid/case13-errors.yaml | 3 +++ .../valid-context-access/invalid/case13-input.svelte | 7 +++++++ .../valid-context-access/invalid/case14-errors.yaml | 3 +++ .../valid-context-access/invalid/case14-input.svelte | 10 ++++++++++ .../valid-context-access/invalid/case15-errors.yaml | 3 +++ .../valid-context-access/invalid/case15-input.svelte | 11 +++++++++++ 8 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case13-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case13-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case14-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case14-input.svelte create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case15-errors.yaml create mode 100644 tests/fixtures/rules/valid-context-access/invalid/case15-input.svelte diff --git a/docs/rules/valid-context-access.md b/docs/rules/valid-context-access.md index ff113111f..365b4b23e 100644 --- a/docs/rules/valid-context-access.md +++ b/docs/rules/valid-context-access.md @@ -45,11 +45,23 @@ This rule reports where context API is called except during component initializa update() setContext("answer", 42) }) + + const update2 = async () => { + await Promise.resolve() + setContext("answer", 42) + } + + ;(async () => { + await Promise.resolve() + setContext("answer", 42) + })() ``` +- :warning: This rule only inspects Svelte files, not JS / TS files. + ## :wrench: Options Nothing. diff --git a/src/rules/valid-context-access.ts b/src/rules/valid-context-access.ts index d4fc4ac8f..cd8490f1e 100644 --- a/src/rules/valid-context-access.ts +++ b/src/rules/valid-context-access.ts @@ -107,6 +107,7 @@ export default createRule("valid-context-access", { belongingFunction: | TSESTree.FunctionDeclaration | TSESTree.VariableDeclaration + | TSESTree.ArrowFunctionExpression node: TSESTree.Node }[] = [] @@ -207,9 +208,11 @@ export default createRule("valid-context-access", { AwaitExpression(node) { let parent: TSESTree.Node | undefined = node.parent while (parent) { - if (parent.type === "FunctionDeclaration") { - awaitExpressions.push({ belongingFunction: parent, node }) - } else if (parent.type === "VariableDeclaration") { + if ( + parent.type === "FunctionDeclaration" || + parent.type === "VariableDeclaration" || + parent.type === "ArrowFunctionExpression" + ) { awaitExpressions.push({ belongingFunction: parent, node }) } parent = parent.parent diff --git a/tests/fixtures/rules/valid-context-access/invalid/case13-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case13-errors.yaml new file mode 100644 index 000000000..f33732b66 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case13-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 5 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case13-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case13-input.svelte new file mode 100644 index 000000000..80ddb16c4 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case13-input.svelte @@ -0,0 +1,7 @@ + diff --git a/tests/fixtures/rules/valid-context-access/invalid/case14-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case14-errors.yaml new file mode 100644 index 000000000..4055b4a9a --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case14-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 4 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case14-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case14-input.svelte new file mode 100644 index 000000000..e8a9b151e --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case14-input.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/rules/valid-context-access/invalid/case15-errors.yaml b/tests/fixtures/rules/valid-context-access/invalid/case15-errors.yaml new file mode 100644 index 000000000..4055b4a9a --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case15-errors.yaml @@ -0,0 +1,3 @@ +- message: Do not call setContext except during component initialization. + line: 4 + column: 5 diff --git a/tests/fixtures/rules/valid-context-access/invalid/case15-input.svelte b/tests/fixtures/rules/valid-context-access/invalid/case15-input.svelte new file mode 100644 index 000000000..f0df9c344 --- /dev/null +++ b/tests/fixtures/rules/valid-context-access/invalid/case15-input.svelte @@ -0,0 +1,11 @@ +