diff --git a/README.md b/README.md
index f0a95ec47..6c7e8add9 100644
--- a/README.md
+++ b/README.md
@@ -285,6 +285,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-reactive-literals](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | Don't assign literal values in reactive statements | :bulb: |
| [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
| [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
+| [svelte/prefer-reactive-destructuring](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-reactive-destructuring/) | Prefer destructuring from objects in reactive statements | |
| [svelte/require-optimized-style-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
## Stylistic Issues
diff --git a/docs/rules.md b/docs/rules.md
index 407aa65d5..61534782f 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -45,6 +45,7 @@ These rules relate to better ways of doing things to help you avoid problems:
| [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | Don't assign literal values in reactive statements | :bulb: |
| [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-reactive-destructuring](./rules/prefer-reactive-destructuring.md) | Prefer destructuring from objects in reactive statements | |
| [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | |
## Stylistic Issues
diff --git a/docs/rules/prefer-reactive-destructuring.md b/docs/rules/prefer-reactive-destructuring.md
new file mode 100644
index 000000000..6481dd366
--- /dev/null
+++ b/docs/rules/prefer-reactive-destructuring.md
@@ -0,0 +1,55 @@
+---
+pageClass: "rule-details"
+sidebarDepth: 0
+title: "svelte/prefer-reactive-destructuring"
+description: "Prefer destructuring from objects in reactive statements"
+---
+
+# svelte/prefer-reactive-destructuring
+
+> Prefer destructuring from objects in reactive statements
+
+- :exclamation: **_This rule has not been released yet._**
+
+## :book: Rule Details
+
+This rule triggers whenever a reactive statement contains an assignment that could be rewritten using destructuring. This is beneficial because it allows svelte's change-tracking to prevent wasteful redraws whenever the object is changed.
+
+This [svelte REPL](https://svelte.dev/repl/96759f4772314d0a840e11370ef76711) example shows just how effective this can be at preventing bogus redraws.
+
+
+
+
+
+```svelte
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing
+
+## :heart: Compatibility
+
+This rule was taken from [@tivac/eslint-plugin-svelte].
+This rule is compatible with `@tivac/svelte/reactive-destructuring` rule.
+
+[@tivac/eslint-plugin-svelte]: https://github.com/tivac/eslint-plugin-svelte/
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/prefer-reactive-destructuring.ts)
+- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/prefer-reactive-destructuring.ts)
diff --git a/src/rules/prefer-reactive-destructuring.ts b/src/rules/prefer-reactive-destructuring.ts
new file mode 100644
index 000000000..1bf64316e
--- /dev/null
+++ b/src/rules/prefer-reactive-destructuring.ts
@@ -0,0 +1,67 @@
+import type { TSESTree } from "@typescript-eslint/types"
+import { createRule } from "../utils"
+
+export default createRule("prefer-reactive-destructuring", {
+ meta: {
+ docs: {
+ description: "Prefer destructuring from objects in reactive statements",
+ category: "Best Practices",
+ recommended: false,
+ },
+ hasSuggestions: true,
+ schema: [],
+ messages: {
+ useDestructuring: `Prefer destructuring in reactive statements`,
+ suggestDestructuring: `Use destructuring to get finer-grained redraws`,
+ },
+ type: "suggestion",
+ },
+ create(context) {
+ return {
+ // Finds: $: info = foo.info
+ // Suggests: $: ({ info } = foo);
+ [`SvelteReactiveStatement > ExpressionStatement > AssignmentExpression[left.type="Identifier"][right.type="MemberExpression"]`](
+ node: TSESTree.AssignmentExpression,
+ ) {
+ const left = node.left as TSESTree.Identifier
+ const right = node.right as TSESTree.MemberExpression
+
+ const prop = (right.property as TSESTree.Identifier).name
+
+ const source = context.getSourceCode()
+ const lToken = source.getFirstToken(left)
+ const rTokens = source.getLastTokens(right, {
+ includeComments: true,
+ count: 2,
+ })
+ const matched = prop === left.name
+
+ return context.report({
+ node,
+ loc: node.loc,
+ messageId: "useDestructuring",
+ suggest:
+ // Don't show suggestions for complex right-hand values, too tricky to get it right
+ right.object.type !== "Identifier" || right.computed
+ ? []
+ : [
+ {
+ messageId: "suggestDestructuring",
+ fix: (fixer) => [
+ fixer.insertTextBefore(
+ lToken,
+ matched ? `({ ` : `({ ${prop}: `,
+ ),
+ fixer.insertTextAfter(lToken, ` }`),
+ fixer.replaceTextRange(
+ [rTokens[0].range[0], rTokens[1].range[1]],
+ ")",
+ ),
+ ],
+ },
+ ],
+ })
+ },
+ }
+ },
+})
diff --git a/src/utils/rules.ts b/src/utils/rules.ts
index 4874d321f..bc3b599e5 100644
--- a/src/utils/rules.ts
+++ b/src/utils/rules.ts
@@ -24,6 +24,7 @@ import noUnknownStyleDirectiveProperty from "../rules/no-unknown-style-directive
import noUnusedSvelteIgnore from "../rules/no-unused-svelte-ignore"
import noUselessMustaches from "../rules/no-useless-mustaches"
import preferClassDirective from "../rules/prefer-class-directive"
+import preferReactiveDestructuring from "../rules/prefer-reactive-destructuring"
import preferStyleDirective from "../rules/prefer-style-directive"
import requireOptimizedStyleAttribute from "../rules/require-optimized-style-attribute"
import shorthandAttribute from "../rules/shorthand-attribute"
@@ -59,6 +60,7 @@ export const rules = [
noUnusedSvelteIgnore,
noUselessMustaches,
preferClassDirective,
+ preferReactiveDestructuring,
preferStyleDirective,
requireOptimizedStyleAttribute,
shorthandAttribute,
diff --git a/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-errors.json b/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-errors.json
new file mode 100644
index 000000000..4236393d4
--- /dev/null
+++ b/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-errors.json
@@ -0,0 +1,44 @@
+[
+ {
+ "message": "Prefer destructuring in reactive statements",
+ "line": 3,
+ "column": 8,
+ "suggestions": [
+ {
+ "desc": "Use destructuring to get finer-grained redraws",
+ "messageId": "suggestDestructuring",
+ "output": "\n\n"
+ }
+ ]
+ },
+ {
+ "message": "Prefer destructuring in reactive statements",
+ "line": 4,
+ "column": 8,
+ "suggestions": [
+ {
+ "desc": "Use destructuring to get finer-grained redraws",
+ "messageId": "suggestDestructuring",
+ "output": "\n\n"
+ }
+ ]
+ },
+ {
+ "message": "Prefer destructuring in reactive statements",
+ "line": 5,
+ "column": 8,
+ "suggestions": null
+ },
+ {
+ "message": "Prefer destructuring in reactive statements",
+ "line": 6,
+ "column": 8,
+ "suggestions": null
+ },
+ {
+ "message": "Prefer destructuring in reactive statements",
+ "line": 7,
+ "column": 8,
+ "suggestions": null
+ }
+]
diff --git a/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-input.svelte b/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-input.svelte
new file mode 100644
index 000000000..207dbfe4b
--- /dev/null
+++ b/tests/fixtures/rules/prefer-reactive-destructuring/invalid/test01-input.svelte
@@ -0,0 +1,8 @@
+
+
diff --git a/tests/fixtures/rules/prefer-reactive-destructuring/valid/test01-input.svelte b/tests/fixtures/rules/prefer-reactive-destructuring/valid/test01-input.svelte
new file mode 100644
index 000000000..7e544d5a4
--- /dev/null
+++ b/tests/fixtures/rules/prefer-reactive-destructuring/valid/test01-input.svelte
@@ -0,0 +1,6 @@
+
+
diff --git a/tests/src/rules/prefer-reactive-destructuring.ts b/tests/src/rules/prefer-reactive-destructuring.ts
new file mode 100644
index 000000000..ce3ee66a0
--- /dev/null
+++ b/tests/src/rules/prefer-reactive-destructuring.ts
@@ -0,0 +1,16 @@
+import { RuleTester } from "eslint"
+import rule from "../../../src/rules/prefer-reactive-destructuring"
+import { loadTestCases } from "../../utils/utils"
+
+const tester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: "module",
+ },
+})
+
+tester.run(
+ "prefer-reactive-destructuring",
+ rule as any,
+ loadTestCases("prefer-reactive-destructuring"),
+)