From dfcc5f178d09bebb9e9c9c10e1697b9b1689ed0e Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 10 May 2023 00:01:13 +0900 Subject: [PATCH 1/2] feat: add `svelte/require-each-key` rule --- README.md | 1 + docs/rules.md | 1 + docs/rules/require-each-key.md | 51 +++++++++++++++++++ src/rules/require-each-key.ts | 27 ++++++++++ src/utils/rules.ts | 2 + .../each-block-without-key01-errors.yaml | 4 ++ .../each-block-without-key01-input.svelte | 21 ++++++++ .../valid/keyed-each-block01-input.svelte | 21 ++++++++ tests/src/rules/require-each-key.ts | 12 +++++ 9 files changed, 140 insertions(+) create mode 100644 docs/rules/require-each-key.md create mode 100644 src/rules/require-each-key.ts create mode 100644 tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-errors.yaml create mode 100644 tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-input.svelte create mode 100644 tests/fixtures/rules/require-each-key/valid/keyed-each-block01-input.svelte create mode 100644 tests/src/rules/require-each-key.ts diff --git a/README.md b/README.md index 0e6c8f6cf..3b78b7407 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-unused-svelte-ignore](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: | | [svelte/prefer-destructured-store-props](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | destructure values from object stores for better change tracking & fewer redraws | :bulb: | +| [svelte/require-each-key](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-each-key/) | require keyed `{#each}` block | | | [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 | | diff --git a/docs/rules.md b/docs/rules.md index b8858ff59..8dd077cc1 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -59,6 +59,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | | [svelte/prefer-destructured-store-props](./rules/prefer-destructured-store-props.md) | destructure values from object stores for better change tracking & fewer redraws | :bulb: | +| [svelte/require-each-key](./rules/require-each-key.md) | require keyed `{#each}` block | | | [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 | | diff --git a/docs/rules/require-each-key.md b/docs/rules/require-each-key.md new file mode 100644 index 000000000..0e15f8e40 --- /dev/null +++ b/docs/rules/require-each-key.md @@ -0,0 +1,51 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/require-each-key" +description: "require keyed `{#each}` block" +--- + +# svelte/require-each-key + +> require keyed `{#each}` block + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule reports `{#each}` block without key + + + + + +```svelte + + + +{#each things as thing (thing.id)} + +{/each} + + +{#each things as thing} + +{/each} +``` + + + +## :wrench: Options + +Nothing. + +## :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/require-each-key.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/require-each-key.ts) diff --git a/src/rules/require-each-key.ts b/src/rules/require-each-key.ts new file mode 100644 index 000000000..e721a0a26 --- /dev/null +++ b/src/rules/require-each-key.ts @@ -0,0 +1,27 @@ +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" + +export default createRule("require-each-key", { + meta: { + docs: { + description: "require keyed `{#each}` block", + category: "Best Practices", + recommended: false, + }, + schema: [], + messages: { expectedKey: "Each block should have a key" }, + type: "suggestion", + }, + create(context) { + return { + SvelteEachBlock(node: AST.SvelteEachBlock) { + if (node.key == null) { + context.report({ + node, + messageId: "expectedKey", + }) + } + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 7f02e373c..88965c3ca 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -45,6 +45,7 @@ import noUselessMustaches from "../rules/no-useless-mustaches" import preferClassDirective from "../rules/prefer-class-directive" import preferDestructuredStoreProps from "../rules/prefer-destructured-store-props" import preferStyleDirective from "../rules/prefer-style-directive" +import requireEachKey from "../rules/require-each-key" import requireEventDispatcherTypes from "../rules/require-event-dispatcher-types" import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute" import requireStoreCallbacksUseSetParam from "../rules/require-store-callbacks-use-set-param" @@ -102,6 +103,7 @@ export const rules = [ preferClassDirective, preferDestructuredStoreProps, preferStyleDirective, + requireEachKey, requireEventDispatcherTypes, requireOptimizedStyleAttribute, requireStoreCallbacksUseSetParam, diff --git a/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-errors.yaml b/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-errors.yaml new file mode 100644 index 000000000..10fc22cd9 --- /dev/null +++ b/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-errors.yaml @@ -0,0 +1,4 @@ +- message: Each block should have a key + line: 19 + column: 1 + suggestions: null diff --git a/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-input.svelte b/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-input.svelte new file mode 100644 index 000000000..359b1937a --- /dev/null +++ b/tests/fixtures/rules/require-each-key/invalid/each-block-without-key01-input.svelte @@ -0,0 +1,21 @@ + + + + +{#each things as thing} + +{/each} diff --git a/tests/fixtures/rules/require-each-key/valid/keyed-each-block01-input.svelte b/tests/fixtures/rules/require-each-key/valid/keyed-each-block01-input.svelte new file mode 100644 index 000000000..840b949bc --- /dev/null +++ b/tests/fixtures/rules/require-each-key/valid/keyed-each-block01-input.svelte @@ -0,0 +1,21 @@ + + + + +{#each things as thing (thing.id)} + +{/each} diff --git a/tests/src/rules/require-each-key.ts b/tests/src/rules/require-each-key.ts new file mode 100644 index 000000000..e49b22c36 --- /dev/null +++ b/tests/src/rules/require-each-key.ts @@ -0,0 +1,12 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/require-each-key" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run("require-each-key", rule as any, loadTestCases("require-each-key")) From b098c063095b6213eecf5e97591d2d58137c350e Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 10 May 2023 00:03:25 +0900 Subject: [PATCH 2/2] Create gentle-doors-impress.md --- .changeset/gentle-doors-impress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gentle-doors-impress.md diff --git a/.changeset/gentle-doors-impress.md b/.changeset/gentle-doors-impress.md new file mode 100644 index 000000000..d5b52236e --- /dev/null +++ b/.changeset/gentle-doors-impress.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `svelte/require-each-key` rule