From e9d97e1a23009b5c7140602b7609ed876e7b3a24 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 10 May 2023 13:06:26 +0900 Subject: [PATCH 1/2] feat: add `svelte/valid-each-key` rule --- README.md | 1 + docs/rules.md | 1 + docs/rules/require-each-key.md | 4 ++ docs/rules/valid-each-key.md | 64 +++++++++++++++++++ src/rules/valid-each-key.ts | 59 +++++++++++++++++ src/utils/rules.ts | 2 + tests/fixtures/rules/.eslintrc.js | 1 + .../invalid/const-key01-errors.yaml | 4 ++ .../invalid/const-key01-input.svelte | 14 ++++ .../invalid/out-vars-key01-errors.yaml | 4 ++ .../invalid/out-vars-key01-input.svelte | 14 ++++ .../valid/call-key01-input.svelte | 16 +++++ .../valid/destructure-key01-input.svelte | 13 ++++ .../valid/expression-key01-input.svelte | 13 ++++ .../valid/expression-key02-input.svelte | 14 ++++ .../valid/index-key01-input.svelte | 13 ++++ .../valid/member-key01-input.svelte | 13 ++++ tests/src/rules/valid-each-key.ts | 12 ++++ 18 files changed, 262 insertions(+) create mode 100644 docs/rules/valid-each-key.md create mode 100644 src/rules/valid-each-key.ts create mode 100644 tests/fixtures/rules/valid-each-key/invalid/const-key01-errors.yaml create mode 100644 tests/fixtures/rules/valid-each-key/invalid/const-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-errors.yaml create mode 100644 tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/call-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/destructure-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/expression-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/expression-key02-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/index-key01-input.svelte create mode 100644 tests/fixtures/rules/valid-each-key/valid/member-key01-input.svelte create mode 100644 tests/src/rules/valid-each-key.ts diff --git a/README.md b/README.md index 3b78b7407..d7ba245c5 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/require-event-dispatcher-types](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for `createEventDispatcher` | | | [svelte/require-optimized-style-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | | | [svelte/require-stores-init](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | | +| [svelte/valid-each-key](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-each-key/) | enforce keys to use variables defined in the `{#each}` block | | ## Stylistic Issues diff --git a/docs/rules.md b/docs/rules.md index 8dd077cc1..45391260c 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -63,6 +63,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/require-event-dispatcher-types](./rules/require-event-dispatcher-types.md) | require type parameters for `createEventDispatcher` | | | [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | | | [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | | +| [svelte/valid-each-key](./rules/valid-each-key.md) | enforce keys to use variables defined in the `{#each}` block | | ## Stylistic Issues diff --git a/docs/rules/require-each-key.md b/docs/rules/require-each-key.md index 0e15f8e40..830703c26 100644 --- a/docs/rules/require-each-key.md +++ b/docs/rules/require-each-key.md @@ -41,6 +41,10 @@ This rule reports `{#each}` block without key Nothing. +## :couple: Related Rules + +- [svelte/valid-each-key](./valid-each-key.md) + ## :books: Further Reading - [Svelte - Tutorial > 4. Logic / Keyed each blocks](https://svelte.dev/tutorial/keyed-each-blocks) diff --git a/docs/rules/valid-each-key.md b/docs/rules/valid-each-key.md new file mode 100644 index 000000000..ec003bb1c --- /dev/null +++ b/docs/rules/valid-each-key.md @@ -0,0 +1,64 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/valid-each-key" +description: "enforce keys to use variables defined in the `{#each}` block" +--- + +# svelte/valid-each-key + +> enforce keys to use variables defined in the `{#each}` block + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule reports that `{#each}` block keys does not use the variables which are defined by the `{#each}` block. + + + + + +```svelte + + + +{#each things as thing (thing.id)} + +{/each} + + +{#each things as thing (foo)} + +{/each} +``` + + + +## :wrench: Options + +Nothing. + +## :couple: Related Rules + +- [svelte/require-each-key](./require-each-key.md) + +## :books: Further Reading + +- [Svelte - Tutorial > 4. Logic / Keyed each blocks](https://svelte.dev/tutorial/keyed-each-blocks) + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/valid-each-key.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/valid-each-key.ts) diff --git a/src/rules/valid-each-key.ts b/src/rules/valid-each-key.ts new file mode 100644 index 000000000..09bb89acc --- /dev/null +++ b/src/rules/valid-each-key.ts @@ -0,0 +1,59 @@ +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" +import { getScope } from "../utils/ast-utils" + +export default createRule("valid-each-key", { + meta: { + docs: { + description: + "enforce keys to use variables defined in the `{#each}` block", + category: "Best Practices", + // TODO Switch to recommended in the major version. + recommended: false, + }, + schema: [], + messages: { + keyUseEachVars: + "Expected key to use the variables which are defined by the `{#each}` block.", + }, + type: "suggestion", + }, + create(context) { + return { + SvelteEachBlock(node: AST.SvelteEachBlock) { + if (node.key == null) { + return + } + const scope = getScope(context, node.key) + for (const variable of scope.variables) { + if ( + !variable.defs.some( + (def) => + (node.context.range[0] <= def.name.range[0] && + def.name.range[1] <= node.context.range[1]) || + (node.index && + node.index.range[0] <= def.name.range[0] && + def.name.range[1] <= node.index.range[1]), + ) + ) { + // It's not an iteration variable. + continue + } + for (const reference of variable.references) { + if ( + node.key.range[0] <= reference.identifier.range[0] && + reference.identifier.range[1] <= node.key.range[1] + ) { + // A variable is used in the key. + return + } + } + } + context.report({ + node: node.key, + messageId: "keyUseEachVars", + }) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 88965c3ca..c5bd38fa2 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 validEachKey from "../rules/valid-each-key" import validPropNamesInKitPages from "../rules/valid-prop-names-in-kit-pages" export const rules = [ @@ -115,5 +116,6 @@ export const rules = [ spacedHtmlComment, system, validCompile, + validEachKey, validPropNamesInKitPages, ] as RuleModule[] diff --git a/tests/fixtures/rules/.eslintrc.js b/tests/fixtures/rules/.eslintrc.js index c5afba262..27774f3a5 100644 --- a/tests/fixtures/rules/.eslintrc.js +++ b/tests/fixtures/rules/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { "one-var": "off", "func-style": "off", "no-console": "off", + "no-use-before-define": "off", "node/no-unsupported-features/es-syntax": "off", "@typescript-eslint/await-thenable": "off", diff --git a/tests/fixtures/rules/valid-each-key/invalid/const-key01-errors.yaml b/tests/fixtures/rules/valid-each-key/invalid/const-key01-errors.yaml new file mode 100644 index 000000000..090db68fc --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/invalid/const-key01-errors.yaml @@ -0,0 +1,4 @@ +- message: Expected key to use the variables which are defined by the `{#each}` block. + line: 11 + column: 25 + suggestions: null diff --git a/tests/fixtures/rules/valid-each-key/invalid/const-key01-input.svelte b/tests/fixtures/rules/valid-each-key/invalid/const-key01-input.svelte new file mode 100644 index 000000000..6d338621b --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/invalid/const-key01-input.svelte @@ -0,0 +1,14 @@ + + +{#each things as thing (key)} + {@const key = thing.id} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-errors.yaml b/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-errors.yaml new file mode 100644 index 000000000..09c08d5ea --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-errors.yaml @@ -0,0 +1,4 @@ +- message: Expected key to use the variables which are defined by the `{#each}` block. + line: 12 + column: 25 + suggestions: null diff --git a/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-input.svelte b/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-input.svelte new file mode 100644 index 000000000..e62dc745f --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/invalid/out-vars-key01-input.svelte @@ -0,0 +1,14 @@ + + +{#each things as thing (foo)} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/call-key01-input.svelte b/tests/fixtures/rules/valid-each-key/valid/call-key01-input.svelte new file mode 100644 index 000000000..61873e1dc --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/call-key01-input.svelte @@ -0,0 +1,16 @@ + + +{#each things as thing (fn(thing))} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/destructure-key01-input.svelte b/tests/fixtures/rules/valid-each-key/valid/destructure-key01-input.svelte new file mode 100644 index 000000000..a060ac008 --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/destructure-key01-input.svelte @@ -0,0 +1,13 @@ + + +{#each things as { id, name } (id)} + {name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/expression-key01-input.svelte b/tests/fixtures/rules/valid-each-key/valid/expression-key01-input.svelte new file mode 100644 index 000000000..be4855c52 --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/expression-key01-input.svelte @@ -0,0 +1,13 @@ + + +{#each things as thing (`thing_id=${thing.id}`)} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/expression-key02-input.svelte b/tests/fixtures/rules/valid-each-key/valid/expression-key02-input.svelte new file mode 100644 index 000000000..b15c95da0 --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/expression-key02-input.svelte @@ -0,0 +1,14 @@ + + +{#each things as thing (foo + thing.id)} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/index-key01-input.svelte b/tests/fixtures/rules/valid-each-key/valid/index-key01-input.svelte new file mode 100644 index 000000000..8da30f439 --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/index-key01-input.svelte @@ -0,0 +1,13 @@ + + +{#each things as thing, index (index)} + {thing.name} +{/each} diff --git a/tests/fixtures/rules/valid-each-key/valid/member-key01-input.svelte b/tests/fixtures/rules/valid-each-key/valid/member-key01-input.svelte new file mode 100644 index 000000000..a812f5075 --- /dev/null +++ b/tests/fixtures/rules/valid-each-key/valid/member-key01-input.svelte @@ -0,0 +1,13 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} diff --git a/tests/src/rules/valid-each-key.ts b/tests/src/rules/valid-each-key.ts new file mode 100644 index 000000000..94488c312 --- /dev/null +++ b/tests/src/rules/valid-each-key.ts @@ -0,0 +1,12 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/valid-each-key" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run("valid-each-key", rule as any, loadTestCases("valid-each-key")) From aaa3c784d77e02a22f15675f993d30959c1e4aee Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 10 May 2023 13:07:48 +0900 Subject: [PATCH 2/2] Create shy-moles-deliver.md --- .changeset/shy-moles-deliver.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shy-moles-deliver.md diff --git a/.changeset/shy-moles-deliver.md b/.changeset/shy-moles-deliver.md new file mode 100644 index 000000000..5f2213599 --- /dev/null +++ b/.changeset/shy-moles-deliver.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `svelte/valid-each-key` rule