From b7952e5948af2404d326155d591cd5325874a7fd Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 31 Jul 2022 00:21:40 -0700 Subject: [PATCH 01/10] wip: add prefer-destructured-store-props rule --- README.md | 1 + docs/rules.md | 1 + docs/rules/prefer-destructured-store-props.md | 53 ++++++++++++++ src/rules/prefer-destructured-store-props.ts | 69 +++++++++++++++++++ src/utils/rules.ts | 2 + .../invalid/test01-errors.json | 26 +++++++ .../invalid/test01-input.svelte | 4 ++ .../valid/test01-input.svelte | 3 + .../rules/prefer-destructured-store-props.ts | 16 +++++ 9 files changed, 175 insertions(+) create mode 100644 docs/rules/prefer-destructured-store-props.md create mode 100644 src/rules/prefer-destructured-store-props.ts create mode 100644 tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json create mode 100644 tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte create mode 100644 tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte create mode 100644 tests/src/rules/prefer-destructured-store-props.ts diff --git a/README.md b/README.md index f0a95ec47..31bd2ba07 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-destructured-store-props](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-destructured-store-props/) | (no description) | | | [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..006ad9039 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-destructured-store-props](./rules/prefer-destructured-store-props.md) | (no description) | | | [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-destructured-store-props.md b/docs/rules/prefer-destructured-store-props.md new file mode 100644 index 000000000..7bde2d11d --- /dev/null +++ b/docs/rules/prefer-destructured-store-props.md @@ -0,0 +1,53 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/prefer-destructured-store-props" +description: "Destructure store props for more efficient redraws" +--- + +# svelte/prefer-destructured-store-props + +> Destructure store props for more efficient redraws + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule reports on directly accessing properties of a store containing an object. These usages can instead be written as a reactive statement using destructuring to allow for more granular change-tracking and reduced redraws in the component. + +An example of the improvements can be see in this [REPL](https://svelte.dev/repl/7de86fea94ff40c48abb82da534dfb89) + + + + + +```svelte + + + +{foo} + + +{$store.foo} +``` + + + +## :wrench: Options + +Nothing + +## :heart: Compatibility + +This rule was taken from [@tivac/eslint-plugin-svelte]. +This rule is compatible with `@tivac/svelte/store-prop-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-destructured-store-props.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/prefer-destructured-store-props.ts) diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts new file mode 100644 index 000000000..3cd56d57f --- /dev/null +++ b/src/rules/prefer-destructured-store-props.ts @@ -0,0 +1,69 @@ +import type { TSESTree } from "@typescript-eslint/types" +import { createRule } from "../utils" + +export default createRule("prefer-destructured-store-props", { + meta: { + docs: { + description: "", + category: "Best Practices", + recommended: false, + }, + hasSuggestions: true, + schema: [], + messages: { + useDestructuring: `Destructure {{prop}} from store {{store}} for better change tracking & fewer redraws`, + fixUseDestructuring: `Using destructuring like $: ({ {{prop}} } = {{store}}); will run faster`, + }, + type: "suggestion", + }, + create(context) { + return { + // {$foo.bar + baz} + // should be + // $: ({ bar } = $foo); + // {bar + baz} + [`MemberExpression[object.name=/$/][property.type="Identifier"]`]( + node: TSESTree.MemberExpression, + ) { + const store = (node.object as TSESTree.Identifier).name + + console.log(node) + + // Since the regex can't specify positioning of the "$", we need to check again + if (!store.startsWith("$")) { + return false + } + + const prop = (node.property as TSESTree.Identifier).name + + return context.report({ + node, + messageId: "useDestructuring", + data: { + store, + prop, + }, + suggest: [ + { + messageId: "fixUseDestructuring", + data: { + store, + prop, + }, + + fix(fixer) { + return [ + fixer.insertTextBefore( + node, + `$: ({ ${prop} } = ${store});\n`, + ), + fixer.replaceText(node, prop), + ] + }, + }, + ], + }) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 4874d321f..9390e4edd 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 preferDestructuredStoreProps from "../rules/prefer-destructured-store-props" 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, + preferDestructuredStoreProps, preferStyleDirective, requireOptimizedStyleAttribute, shorthandAttribute, diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json new file mode 100644 index 000000000..7a0f424a1 --- /dev/null +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json @@ -0,0 +1,26 @@ +[ + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 2, + "column": 2, + "suggestions": [ + { + "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", + "messageId": "fixUseDestructuring", + "output": "\n{$: ({ bar } = $foo);\nbar}\n{$foo[bar]}\n{$foo[\"bar\"]}\n" + } + ] + }, + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 3, + "column": 2, + "suggestions": [ + { + "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", + "messageId": "fixUseDestructuring", + "output": "\n{$foo.bar}\n{$: ({ bar } = $foo);\nbar}\n{$foo[\"bar\"]}\n" + } + ] + } +] 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..6d1ef35fe --- /dev/null +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte @@ -0,0 +1,4 @@ + +{$foo.bar} +{$foo[bar]} +{$foo["bar"]} 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..8e32775f8 --- /dev/null +++ b/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte @@ -0,0 +1,3 @@ + +{$foo[`bar${baz}`]} +{foo$.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"), +) From 061a4af238fd2f59591d92baf2371d7fc9526977 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 31 Jul 2022 00:33:13 -0700 Subject: [PATCH 02/10] chore: remove log --- src/rules/prefer-destructured-store-props.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts index 3cd56d57f..477b63640 100644 --- a/src/rules/prefer-destructured-store-props.ts +++ b/src/rules/prefer-destructured-store-props.ts @@ -27,8 +27,6 @@ export default createRule("prefer-destructured-store-props", { ) { const store = (node.object as TSESTree.Identifier).name - console.log(node) - // Since the regex can't specify positioning of the "$", we need to check again if (!store.startsWith("$")) { return false From e5ff3d8f07728177530bf5e3bac8865399aa2af0 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Sun, 31 Jul 2022 00:42:07 -0700 Subject: [PATCH 03/10] chore: trying to figure out lint invocations Editor doesn't like this but maybe it's ok? --- .../invalid/test01-errors.json | 16 ++++++++++++++-- .../invalid/test01-input.svelte | 4 +++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json index 7a0f424a1..0f07fdc5e 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-errors.json @@ -7,7 +7,7 @@ { "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", "messageId": "fixUseDestructuring", - "output": "\n{$: ({ bar } = $foo);\nbar}\n{$foo[bar]}\n{$foo[\"bar\"]}\n" + "output": "\n{$: ({ bar } = $foo);\nbar}\n{$foo[bar]}\n\n\n{$foo.bar}\n" } ] }, @@ -19,7 +19,19 @@ { "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", "messageId": "fixUseDestructuring", - "output": "\n{$foo.bar}\n{$: ({ bar } = $foo);\nbar}\n{$foo[\"bar\"]}\n" + "output": "\n{$foo.bar}\n{$: ({ bar } = $foo);\nbar}\n\n\n{$foo.bar}\n" + } + ] + }, + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 6, + "column": 2, + "suggestions": [ + { + "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", + "messageId": "fixUseDestructuring", + "output": "\n{$foo.bar}\n{$foo[bar]}\n\n\n{$: ({ bar } = $foo);\nbar}\n" } ] } 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 index 6d1ef35fe..1b0ed176d 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte @@ -1,4 +1,6 @@ {$foo.bar} {$foo[bar]} -{$foo["bar"]} + + +{$foo.bar} From 94271e849649dc08e29d288f954ea4ca430c4599 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 1 Aug 2022 00:20:58 -0700 Subject: [PATCH 04/10] wip: better query --- src/rules/prefer-destructured-store-props.ts | 8 +------- .../valid/test01-input.svelte | 1 + 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts index 477b63640..59eafe662 100644 --- a/src/rules/prefer-destructured-store-props.ts +++ b/src/rules/prefer-destructured-store-props.ts @@ -22,16 +22,10 @@ export default createRule("prefer-destructured-store-props", { // should be // $: ({ bar } = $foo); // {bar + baz} - [`MemberExpression[object.name=/$/][property.type="Identifier"]`]( + [`MemberExpression[object.name=/^\\$/][property.type="Identifier"]`]( node: TSESTree.MemberExpression, ) { const store = (node.object as TSESTree.Identifier).name - - // Since the regex can't specify positioning of the "$", we need to check again - if (!store.startsWith("$")) { - return false - } - const prop = (node.property as TSESTree.Identifier).name return context.report({ 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 index 8e32775f8..ccbf18077 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/valid/test01-input.svelte @@ -1,3 +1,4 @@ {$foo[`bar${baz}`]} {foo$.bar} +{f$oo.bar} From 09f02494bdb00f09af324ffbc5d4c513b3023a4a Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 1 Aug 2022 23:50:06 -0700 Subject: [PATCH 05/10] wip: suggest reactive statement in \n\n{bar}\n{$foo[bar]}\n\n\n{$foo.bar}\n" } ] }, { "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 3, + "line": 7, "column": 2, "suggestions": [ { "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", "messageId": "fixUseDestructuring", - "output": "\n{$foo.bar}\n{$: ({ bar } = $foo);\nbar}\n\n\n{$foo.bar}\n" + "output": "\n\n\n{$foo.bar}\n{bar}\n\n\n{$foo.bar}\n" } ] }, { "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 6, + "line": 10, "column": 2, "suggestions": [ { "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", "messageId": "fixUseDestructuring", - "output": "\n{$foo.bar}\n{$foo[bar]}\n\n\n{$: ({ bar } = $foo);\nbar}\n" + "output": "\n\n\n{$foo.bar}\n{$foo[bar]}\n\n\n{bar}\n" } ] } 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 index 1b0ed176d..fb79b00ac 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte @@ -1,4 +1,8 @@ + + {$foo.bar} {$foo[bar]} 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..090274bd4 --- /dev/null +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json @@ -0,0 +1,20 @@ +[ + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 2, + "column": 2, + "suggestions": null + }, + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 3, + "column": 2, + "suggestions": null + }, + { + "message": "Destructure bar from store $foo for better change tracking & fewer redraws", + "line": 6, + "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..1b0ed176d --- /dev/null +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte @@ -0,0 +1,6 @@ + +{$foo.bar} +{$foo[bar]} + + +{$foo.bar} From 4deb69d0a4ca4a4576847e6d0a049a9ae72bd514 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Wed, 3 Aug 2022 00:08:55 -0700 Subject: [PATCH 06/10] wip: address feedbakc --- src/rules/prefer-destructured-store-props.ts | 53 ++++++++++++++----- .../invalid/test01-errors.json | 26 +++------ .../invalid/test01-input.svelte | 5 +- .../invalid/test02-errors.json | 4 +- .../invalid/test02-input.svelte | 3 +- 5 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts index b38ed2643..c72f0b189 100644 --- a/src/rules/prefer-destructured-store-props.ts +++ b/src/rules/prefer-destructured-store-props.ts @@ -1,6 +1,10 @@ +import type * as ESTree from "estree" import type { TSESTree } from "@typescript-eslint/types" import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" +import { getStringIfConstant } from "../utils/ast-utils" + +type NodeRecord = { property: string; node: TSESTree.MemberExpression } export default createRule("prefer-destructured-store-props", { meta: { @@ -13,61 +17,82 @@ export default createRule("prefer-destructured-store-props", { hasSuggestions: true, schema: [], messages: { - useDestructuring: `Destructure {{prop}} from store {{store}} for better change tracking & fewer redraws`, - fixUseDestructuring: `Using destructuring like $: ({ {{prop}} } = {{store}}); will run faster`, + useDestructuring: `Destructure {{property}} from store {{store}} for better change tracking & fewer redraws`, + fixUseDestructuring: `Using destructuring like $: ({ {{property}} } = {{store}}); will run faster`, }, type: "suggestion", }, create(context) { let script: AST.SvelteScriptElement - const reports: TSESTree.MemberExpression[] = [] + const reports: NodeRecord[] = [] return { [`SvelteScriptElement`](node: AST.SvelteScriptElement) { script = node }, - // {$foo.bar + baz} + // {$foo.bar} // should be // $: ({ bar } = $foo); - // {bar + baz} - [`MemberExpression[object.name=/^\\$/][property.type="Identifier"]`]( + // {bar} + // Same with {$foo["bar"]} + [`MemberExpression[object.name=/^\\$/]`]( node: TSESTree.MemberExpression, ) { - reports.push(node) + 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((node) => { + reports.forEach(({ property, node }) => { const store = (node.object as TSESTree.Identifier).name - const prop = (node.property as TSESTree.Identifier).name + // let prop: string | null = null + + // if (node.property.type === "Literal") { + // prop = node.property.value as string + // } else if (node.property.type === "Identifier") { + // prop = node.property.name + // } context.report({ node, messageId: "useDestructuring", data: { store, - prop, + property, }, + suggest: [ { messageId: "fixUseDestructuring", data: { store, - prop, + property, }, fix(fixer) { - if (!script || !script.endTag) { + // Avoid autofix suggestions for: + // dynamic accesses like {$foo[bar]} + // no \n\n{bar}\n{$foo[bar]}\n\n\n{$foo.bar}\n" + "output": "\n\n\n{bar}\n\n{$foo[baz]}\n\n\n{$foo[\"qux\"]}\n" } ] }, { - "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 7, + "message": "Destructure baz from store $foo for better change tracking & fewer redraws", + "line": 8, "column": 2, - "suggestions": [ - { - "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", - "messageId": "fixUseDestructuring", - "output": "\n\n\n{$foo.bar}\n{bar}\n\n\n{$foo.bar}\n" - } - ] + "suggestions": null }, { - "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 10, + "message": "Destructure qux from store $foo for better change tracking & fewer redraws", + "line": 11, "column": 2, - "suggestions": [ - { - "desc": "Using destructuring like $: ({ bar } = $foo); will run faster", - "messageId": "fixUseDestructuring", - "output": "\n\n\n{$foo.bar}\n{$foo[bar]}\n\n\n{bar}\n" - } - ] + "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 index fb79b00ac..aba95036a 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test01-input.svelte @@ -4,7 +4,8 @@ {$foo.bar} -{$foo[bar]} + +{$foo[baz]} -{$foo.bar} +{$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 index 090274bd4..456e42f7d 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-errors.json @@ -7,13 +7,13 @@ }, { "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 3, + "line": 4, "column": 2, "suggestions": null }, { "message": "Destructure bar from store $foo for better change tracking & fewer redraws", - "line": 6, + "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 index 1b0ed176d..73b5fe2e6 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test02-input.svelte @@ -1,6 +1,7 @@ {$foo.bar} + {$foo[bar]} -{$foo.bar} +{$foo["bar"]} From 48187ddf8e041ab0db7be2c948726586d92b14b4 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 3 Aug 2022 16:55:30 +0900 Subject: [PATCH 07/10] Update .eslintrc.js --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 57826c457..04e5d321d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,6 +43,7 @@ module.exports = { }, { files: ["*.svelte"], + extends: ["plugin:svelte/base"], parser: "svelte-eslint-parser", parserOptions: { parser: { From 2fd72eedc39f7166957cbef5da3a3f2d725dcd10 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 9 Aug 2022 23:34:13 -0700 Subject: [PATCH 08/10] wip: feedback --- src/rules/prefer-destructured-store-props.ts | 82 +++++++++++-------- .../invalid/test01-errors.json | 6 +- .../invalid/test02-errors.json | 6 +- .../invalid/test03-errors.json | 14 ++++ .../invalid/test03-input.svelte | 17 ++++ 5 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json create mode 100644 tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts index c72f0b189..09407c06b 100644 --- a/src/rules/prefer-destructured-store-props.ts +++ b/src/rules/prefer-destructured-store-props.ts @@ -3,6 +3,7 @@ import type { TSESTree } from "@typescript-eslint/types" import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" import { getStringIfConstant } from "../utils/ast-utils" +import { returnStatement } from "@babel/types" type NodeRecord = { property: string; node: TSESTree.MemberExpression } @@ -17,20 +18,33 @@ export default createRule("prefer-destructured-store-props", { hasSuggestions: true, schema: [], messages: { - useDestructuring: `Destructure {{property}} from store {{store}} for better change tracking & fewer redraws`, + useDestructuring: `Destructure {{property}} from {{store}} for better change tracking & fewer redraws`, fixUseDestructuring: `Using destructuring like $: ({ {{property}} } = {{store}}); will run faster`, }, type: "suggestion", }, create(context) { let script: AST.SvelteScriptElement + + // Store off instances of probably-destructurable statements const reports: NodeRecord[] = [] + // Store off defined variable names so we can avoid offering a suggestion in those cases + const defined: Set = new Set() + return { [`SvelteScriptElement`](node: AST.SvelteScriptElement) { script = node }, + [`VariableDeclarator[id.type="Identifier"]`]( + node: TSESTree.VariableDeclarator, + ) { + const { name } = node.id as TSESTree.Identifier + + defined.add(name) + }, + // {$foo.bar} // should be // $: ({ bar } = $foo); @@ -54,13 +68,6 @@ export default createRule("prefer-destructured-store-props", { [`Program:exit`]() { reports.forEach(({ property, node }) => { const store = (node.object as TSESTree.Identifier).name - // let prop: string | null = null - - // if (node.property.type === "Literal") { - // prop = node.property.value as string - // } else if (node.property.type === "Identifier") { - // prop = node.property.name - // } context.report({ node, @@ -70,33 +77,42 @@ export default createRule("prefer-destructured-store-props", { property, }, - suggest: [ - { - messageId: "fixUseDestructuring", - data: { - store, - property, - }, + // Avoid suggestions for: + // dynamic accesses like {$foo[bar]} + // no + +
+ foo: {foo + " " + Date.now()} +
+
+ bar: {bar + " " + Date.now()} +
From 65fd0f33f06f78c0a5b54b8748292e8641bde455 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 9 Aug 2022 23:39:58 -0700 Subject: [PATCH 09/10] wip: also track imported names --- src/rules/prefer-destructured-store-props.ts | 11 ++++++++++- .../invalid/test03-errors.json | 10 ++++++++-- .../invalid/test03-input.svelte | 4 ++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/rules/prefer-destructured-store-props.ts b/src/rules/prefer-destructured-store-props.ts index 09407c06b..8db050067 100644 --- a/src/rules/prefer-destructured-store-props.ts +++ b/src/rules/prefer-destructured-store-props.ts @@ -3,7 +3,6 @@ import type { TSESTree } from "@typescript-eslint/types" import type { AST } from "svelte-eslint-parser" import { createRule } from "../utils" import { getStringIfConstant } from "../utils/ast-utils" -import { returnStatement } from "@babel/types" type NodeRecord = { property: string; node: TSESTree.MemberExpression } @@ -37,6 +36,16 @@ export default createRule("prefer-destructured-store-props", { 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, ) { 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 index a53c6abc3..1f1376572 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-errors.json @@ -1,14 +1,20 @@ [ { "message": "Destructure foo from $store for better change tracking & fewer redraws", - "line": 7, + "line": 8, "column": 11, "suggestions": null }, { "message": "Destructure bar from $store for better change tracking & fewer redraws", - "line": 8, + "line": 9, "column": 11, "suggestions": null + }, + { + "message": "Destructure baz from $store for better change tracking & fewer redraws", + "line": 20, + "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 index aa7d41a57..1bdd097bf 100644 --- a/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte +++ b/tests/fixtures/rules/prefer-destructured-store-props/invalid/test03-input.svelte @@ -1,6 +1,7 @@
+ foo: {foo + " " + Date.now()}
+ bar: {bar + " " + Date.now()}