1
+ import type * as ESTree from "estree"
1
2
import type { TSESTree } from "@typescript-eslint/types"
2
3
import type { AST } from "svelte-eslint-parser"
3
4
import { createRule } from "../utils"
5
+ import { getStringIfConstant } from "../utils/ast-utils"
6
+
7
+ type NodeRecord = { property : string ; node : TSESTree . MemberExpression }
4
8
5
9
export default createRule ( "prefer-destructured-store-props" , {
6
10
meta : {
@@ -13,61 +17,82 @@ export default createRule("prefer-destructured-store-props", {
13
17
hasSuggestions : true ,
14
18
schema : [ ] ,
15
19
messages : {
16
- useDestructuring : `Destructure {{prop }} from store {{store}} for better change tracking & fewer redraws` ,
17
- fixUseDestructuring : `Using destructuring like $: ({ {{prop }} } = {{store}}); will run faster` ,
20
+ useDestructuring : `Destructure {{property }} from store {{store}} for better change tracking & fewer redraws` ,
21
+ fixUseDestructuring : `Using destructuring like $: ({ {{property }} } = {{store}}); will run faster` ,
18
22
} ,
19
23
type : "suggestion" ,
20
24
} ,
21
25
create ( context ) {
22
26
let script : AST . SvelteScriptElement
23
- const reports : TSESTree . MemberExpression [ ] = [ ]
27
+ const reports : NodeRecord [ ] = [ ]
24
28
25
29
return {
26
30
[ `SvelteScriptElement` ] ( node : AST . SvelteScriptElement ) {
27
31
script = node
28
32
} ,
29
33
30
- // {$foo.bar + baz }
34
+ // {$foo.bar}
31
35
// should be
32
36
// $: ({ bar } = $foo);
33
- // {bar + baz}
34
- [ `MemberExpression[object.name=/^\\$/][property.type="Identifier"]` ] (
37
+ // {bar}
38
+ // Same with {$foo["bar"]}
39
+ [ `MemberExpression[object.name=/^\\$/]` ] (
35
40
node : TSESTree . MemberExpression ,
36
41
) {
37
- reports . push ( node )
42
+ const property =
43
+ node . property . type === "Identifier"
44
+ ? node . property . name
45
+ : getStringIfConstant ( node . property as ESTree . Expression )
46
+
47
+ if ( ! property ) {
48
+ return
49
+ }
50
+
51
+ reports . push ( { property, node } )
38
52
} ,
39
53
40
54
[ `Program:exit` ] ( ) {
41
- reports . forEach ( ( node ) => {
55
+ reports . forEach ( ( { property , node } ) => {
42
56
const store = ( node . object as TSESTree . Identifier ) . name
43
- const prop = ( node . property as TSESTree . Identifier ) . name
57
+ // let prop: string | null = null
58
+
59
+ // if (node.property.type === "Literal") {
60
+ // prop = node.property.value as string
61
+ // } else if (node.property.type === "Identifier") {
62
+ // prop = node.property.name
63
+ // }
44
64
45
65
context . report ( {
46
66
node,
47
67
messageId : "useDestructuring" ,
48
68
data : {
49
69
store,
50
- prop ,
70
+ property ,
51
71
} ,
72
+
52
73
suggest : [
53
74
{
54
75
messageId : "fixUseDestructuring" ,
55
76
data : {
56
77
store,
57
- prop ,
78
+ property ,
58
79
} ,
59
80
60
81
fix ( fixer ) {
61
- if ( ! script || ! script . endTag ) {
82
+ // Avoid autofix suggestions for:
83
+ // dynamic accesses like {$foo[bar]}
84
+ // no <script> tag
85
+ // no <script> ending
86
+ if ( node . computed || ! script || ! script . endTag ) {
62
87
return [ ]
63
88
}
64
89
65
90
return [
66
91
fixer . insertTextAfterRange (
67
92
[ script . endTag . range [ 0 ] , script . endTag . range [ 0 ] ] ,
68
- `$: ({ ${ prop } } = ${ store } );\n` ,
93
+ `$: ({ ${ property } } = ${ store } );\n` ,
69
94
) ,
70
- fixer . replaceText ( node , prop ) ,
95
+ fixer . replaceText ( node , property ) ,
71
96
]
72
97
} ,
73
98
} ,
0 commit comments