From 86c7cba977f7ff92b2cc38d44f9e5e85e1b8cd6b Mon Sep 17 00:00:00 2001 From: baseballyama Date: Thu, 4 Dec 2025 17:49:40 +0900 Subject: [PATCH 1/2] fix(prefer-destructured-store-props): handle runes properly --- .changeset/wet-kiwis-cover.md | 5 +++++ .../rules/prefer-destructured-store-props.ts | 18 ++++++++++++++++++ .../invalid/runes-with-store01-errors.yaml | 18 ++++++++++++++++++ .../invalid/runes-with-store01-input.svelte | 10 ++++++++++ .../runes-with-store01-requirements.json | 3 +++ .../valid/runes01-input.svelte.js | 8 ++++++++ .../valid/runes01-requirements.json | 3 +++ 7 files changed, 65 insertions(+) create mode 100644 .changeset/wet-kiwis-cover.md create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-requirements.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-input.svelte.js create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-requirements.json diff --git a/.changeset/wet-kiwis-cover.md b/.changeset/wet-kiwis-cover.md new file mode 100644 index 000000000..a4bff6325 --- /dev/null +++ b/.changeset/wet-kiwis-cover.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': patch +--- + +fix(prefer-destructured-store-props): handle runes properly diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts b/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts index 82864254f..5c256d8b4 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts @@ -5,11 +5,26 @@ import { keyword } from 'esutils'; import type { SuggestionReportDescriptor } from '../types.js'; import { createRule } from '../utils/index.js'; import { findAttribute, isExpressionIdentifier, findVariable } from '../utils/ast-utils.js'; +import { getSvelteContext } from '../utils/svelte-context.js'; type StoreMemberExpression = TSESTree.MemberExpression & { object: TSESTree.Identifier & { name: string }; }; +/** + * Svelte 5 runes that start with `$` but are not stores. + * These should be excluded from the prefer-destructured-store-props rule. + */ +const SVELTE_RUNES = new Set([ + '$state', + '$derived', + '$effect', + '$props', + '$bindable', + '$inspect', + '$host' +]); + export default createRule('prefer-destructured-store-props', { meta: { docs: { @@ -29,6 +44,7 @@ export default createRule('prefer-destructured-store-props', { }, create(context) { let mainScript: AST.SvelteScriptElement | null = null; + const svelteContext = getSvelteContext(context); // Store off instances of probably-destructurable statements const reports: StoreMemberExpression[] = []; @@ -150,6 +166,8 @@ export default createRule('prefer-destructured-store-props', { node: StoreMemberExpression ) { if (inScriptElement) return; // Within a script tag + // Skip Svelte 5 runes (e.g., $derived.by, $state.raw, $effect.pre) + if (svelteContext?.runes === true && SVELTE_RUNES.has(node.object.name)) return; storeMemberAccessStack.unshift({ node, identifiers: [] }); }, Identifier(node: TSESTree.Identifier) { diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-errors.yaml new file mode 100644 index 000000000..838e5e203 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-errors.yaml @@ -0,0 +1,18 @@ +- message: Destructure foo from $store for better change tracking & fewer redraws + line: 10 + column: 18 + suggestions: + - desc: 'Using destructuring like $: ({ foo } = $store); will run faster' + messageId: fixUseDestructuring + output: | + + + +

Count: {count}

+

Doubled: {doubled}

+

Store value: {foo}

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-input.svelte new file mode 100644 index 000000000..abd8fbccf --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-input.svelte @@ -0,0 +1,10 @@ + + + +

Count: {count}

+

Doubled: {doubled}

+

Store value: {$store.foo}

diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/invalid/runes-with-store01-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-input.svelte.js b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-input.svelte.js new file mode 100644 index 000000000..84972dafa --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-input.svelte.js @@ -0,0 +1,8 @@ +export class Test { + a = $state(0); + b = $derived.by(() => this.a * 2); + + output() { + console.log(this.a, this.b); + } +} \ No newline at end of file diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-requirements.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-requirements.json new file mode 100644 index 000000000..0192b1098 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-destructured-store-props/valid/runes01-requirements.json @@ -0,0 +1,3 @@ +{ + "svelte": ">=5.0.0-0" +} From c3643d5db886bbf340dda1ae2a13137f2aa73a07 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Thu, 4 Dec 2025 17:55:27 +0900 Subject: [PATCH 2/2] refactor --- .../src/rules/prefer-destructured-store-props.ts | 15 +-------------- packages/eslint-plugin-svelte/src/shared/runes.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 14 deletions(-) create mode 100644 packages/eslint-plugin-svelte/src/shared/runes.ts diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts b/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts index 5c256d8b4..5f0bec74f 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-destructured-store-props.ts @@ -6,25 +6,12 @@ import type { SuggestionReportDescriptor } from '../types.js'; import { createRule } from '../utils/index.js'; import { findAttribute, isExpressionIdentifier, findVariable } from '../utils/ast-utils.js'; import { getSvelteContext } from '../utils/svelte-context.js'; +import { SVELTE_RUNES } from '../shared/runes.js'; type StoreMemberExpression = TSESTree.MemberExpression & { object: TSESTree.Identifier & { name: string }; }; -/** - * Svelte 5 runes that start with `$` but are not stores. - * These should be excluded from the prefer-destructured-store-props rule. - */ -const SVELTE_RUNES = new Set([ - '$state', - '$derived', - '$effect', - '$props', - '$bindable', - '$inspect', - '$host' -]); - export default createRule('prefer-destructured-store-props', { meta: { docs: { diff --git a/packages/eslint-plugin-svelte/src/shared/runes.ts b/packages/eslint-plugin-svelte/src/shared/runes.ts new file mode 100644 index 000000000..9fd53a679 --- /dev/null +++ b/packages/eslint-plugin-svelte/src/shared/runes.ts @@ -0,0 +1,9 @@ +export const SVELTE_RUNES = new Set([ + '$state', + '$derived', + '$effect', + '$props', + '$bindable', + '$inspect', + '$host' +]);