= new Set()
+
+ return {
+ [`SvelteScriptElement`](node: AST.SvelteScriptElement) {
+ script = node
+ },
+
+ // Capture import names
+ [`ImportSpecifier, ImportDefaultSpecifier, ImportNamespaceSpecifier`](
+ node: TSESTree.ImportSpecifier,
+ ) {
+ const { name } = node.local
+
+ defined.add(name)
+ },
+
+ // Capture variable names
+ [`VariableDeclarator[id.type="Identifier"]`](
+ node: TSESTree.VariableDeclarator,
+ ) {
+ const { name } = node.id as TSESTree.Identifier
+
+ defined.add(name)
+ },
+
+ // {$foo.bar}
+ // should be
+ // $: ({ bar } = $foo);
+ // {bar}
+ // Same with {$foo["bar"]}
+ [`MemberExpression[object.name=/^\\$/]`](
+ node: TSESTree.MemberExpression,
+ ) {
+ const property =
+ node.property.type === "Identifier"
+ ? node.property.name
+ : getStringIfConstant(node.property as ESTree.Expression)
+
+ if (!property) {
+ return
+ }
+
+ reports.push({ property, node })
+ },
+
+ [`Program:exit`]() {
+ reports.forEach(({ property, node }) => {
+ const store = (node.object as TSESTree.Identifier).name
+
+ context.report({
+ node,
+ messageId: "useDestructuring",
+ data: {
+ store,
+ property,
+ },
+
+ // Avoid suggestions for:
+ // dynamic accesses like {$foo[bar]}
+ // no \n\n{bar}\n\n{$foo[baz]}\n\n\n{$foo[\"qux\"]}\n"
+ }
+ ]
+ },
+ {
+ "message": "Destructure baz from $foo for better change tracking & fewer redraws",
+ "line": 8,
+ "column": 2,
+ "suggestions": null
+ },
+ {
+ "message": "Destructure qux from $foo for better change tracking & fewer redraws",
+ "line": 11,
+ "column": 2,
+ "suggestions": null
+ }
+]
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte
new file mode 100644
index 000000000..aba95036a
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte
@@ -0,0 +1,11 @@
+
+
+
+{$foo.bar}
+
+{$foo[baz]}
+
+
+{$foo["qux"]}
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json
new file mode 100644
index 000000000..332ca84b4
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json
@@ -0,0 +1,20 @@
+[
+ {
+ "message": "Destructure bar from $foo for better change tracking & fewer redraws",
+ "line": 2,
+ "column": 2,
+ "suggestions": null
+ },
+ {
+ "message": "Destructure bar from $foo for better change tracking & fewer redraws",
+ "line": 4,
+ "column": 2,
+ "suggestions": null
+ },
+ {
+ "message": "Destructure bar from $foo for better change tracking & fewer redraws",
+ "line": 7,
+ "column": 2,
+ "suggestions": null
+ }
+]
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte
new file mode 100644
index 000000000..73b5fe2e6
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte
@@ -0,0 +1,7 @@
+
+{$foo.bar}
+
+{$foo[bar]}
+
+
+{$foo["bar"]}
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json
new file mode 100644
index 000000000..96010a682
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json
@@ -0,0 +1,20 @@
+[
+ {
+ "message": "Destructure foo from $store for better change tracking & fewer redraws",
+ "line": 8,
+ "column": 11,
+ "suggestions": null
+ },
+ {
+ "message": "Destructure bar from $store for better change tracking & fewer redraws",
+ "line": 9,
+ "column": 11,
+ "suggestions": null
+ },
+ {
+ "message": "Destructure baz from $store for better change tracking & fewer redraws",
+ "line": 22,
+ "column": 9,
+ "suggestions": null
+ }
+]
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte
new file mode 100644
index 000000000..9df5ed9e9
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
+ foo: {foo + " " + Date.now()}
+
+
+
+ bar: {bar + " " + Date.now()}
+
+
+ baz: {$store.baz}
+
diff --git a/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte b/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte
new file mode 100644
index 000000000..ccbf18077
--- /dev/null
+++ b/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte
@@ -0,0 +1,4 @@
+
+{$foo[`bar${baz}`]}
+{foo$.bar}
+{f$oo.bar}
diff --git a/tests/src/rules/prefer-destructured-store-props.ts b/tests/src/rules/prefer-destructured-store-props.ts
new file mode 100644
index 000000000..7cdbf8e3c
--- /dev/null
+++ b/tests/src/rules/prefer-destructured-store-props.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/prefer-destructured-store-props"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "prefer-destructured-store-props",
+ rule as any,
+ loadTestCases("prefer-destructured-store-props"),
+)