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"), +)