1
1
'use strict'
2
2
3
+ const { classify, getAttributes } = require ( '../util/helpers' )
4
+
5
+ const groups = [
6
+ {
7
+ components : [ 'VDialog' , 'VMenu' , 'VTooltip' ] ,
8
+ slots : [ 'activator' ] ,
9
+ handler ( context , node , directive , param ) {
10
+ if ( param . type === 'Identifier' ) {
11
+ // #activator="data"
12
+ const boundVariables = { }
13
+ node . variables . find ( variable => variable . id . name === param . name ) ?. references . forEach ( ref => {
14
+ if ( ref . id . parent . type !== 'MemberExpression' ) return
15
+ if (
16
+ // v-bind="data.props"
17
+ ref . id . parent . property . name === 'props' &&
18
+ ref . id . parent . parent . parent . directive &&
19
+ ref . id . parent . parent . parent . key . name . name === 'bind' &&
20
+ ! ref . id . parent . parent . parent . key . argument
21
+ ) return
22
+ if ( ref . id . parent . property . name === 'on' ) {
23
+ boundVariables . on = ref . id
24
+ } else if ( ref . id . parent . property . name === 'attrs' ) {
25
+ boundVariables . attrs = ref . id
26
+ }
27
+ } )
28
+ if ( boundVariables . on ) {
29
+ const ref = boundVariables . on
30
+ context . report ( {
31
+ node : ref ,
32
+ messageId : 'changedProps' ,
33
+ data : {
34
+ component : node . parent . name ,
35
+ slot : directive . key . argument . name ,
36
+ } ,
37
+ fix ( fixer ) {
38
+ return fixer . replaceText ( ref . parent . parent . parent , `v-bind="${ param . name } .props"` )
39
+ } ,
40
+ } )
41
+ }
42
+ if ( boundVariables . attrs ) {
43
+ const ref = boundVariables . attrs
44
+ if ( ! boundVariables . on ) {
45
+ context . report ( {
46
+ node : boundVariables . attrs ,
47
+ messageId : 'invalidProps' ,
48
+ } )
49
+ } else {
50
+ context . report ( {
51
+ node : ref ,
52
+ messageId : 'changedProps' ,
53
+ data : {
54
+ component : node . parent . name ,
55
+ slot : directive . key . argument . name ,
56
+ } ,
57
+ fix ( fixer ) {
58
+ return fixer . removeRange ( [ ref . parent . parent . parent . range [ 0 ] - 1 , ref . parent . parent . parent . range [ 1 ] ] )
59
+ } ,
60
+ } )
61
+ }
62
+ }
63
+ } else if ( param . type === 'ObjectPattern' ) {
64
+ // #activator="{ on, attrs }"
65
+ const boundVariables = { }
66
+ param . properties . forEach ( prop => {
67
+ node . variables . find ( variable => variable . id . name === prop . value . name ) ?. references . forEach ( ref => {
68
+ if ( prop . key . name === 'on' ) {
69
+ boundVariables . on = { prop, id : ref . id }
70
+ } else if ( prop . key . name === 'attrs' ) {
71
+ boundVariables . attrs = { prop, id : ref . id }
72
+ }
73
+ } )
74
+ } )
75
+ if ( boundVariables . on || boundVariables . attrs ) {
76
+ if ( boundVariables . attrs && ! boundVariables . on ) {
77
+ context . report ( {
78
+ node : boundVariables . attrs . prop . key ,
79
+ messageId : 'invalidProps' ,
80
+ } )
81
+ } else {
82
+ context . report ( {
83
+ node : param ,
84
+ messageId : 'changedProps' ,
85
+ data : {
86
+ component : node . parent . name ,
87
+ slot : directive . key . argument . name ,
88
+ } ,
89
+ * fix ( fixer ) {
90
+ if ( boundVariables . on ) {
91
+ const ref = boundVariables . on
92
+ yield fixer . replaceText ( ref . prop , 'props' )
93
+ yield fixer . replaceText ( ref . id . parent . parent , `v-bind="props"` )
94
+ }
95
+ if ( boundVariables . attrs ) {
96
+ const template = context . parserServices . getTemplateBodyTokenStore ( )
97
+ const ref = boundVariables . attrs
98
+ const isLast = ref . prop === param . properties . at ( - 1 )
99
+ if ( isLast ) {
100
+ const comma = template . getTokenBefore ( ref . prop , { filter : token => token . value === ',' } )
101
+ if ( comma ) {
102
+ yield fixer . removeRange ( [ comma . start , ref . prop . end ] )
103
+ } else {
104
+ yield fixer . removeRange ( [ ref . prop . start - 1 , ref . prop . end ] )
105
+ }
106
+ } else {
107
+ const comma = template . getTokenAfter ( ref . prop , { filter : token => token . value === ',' } )
108
+ if ( comma ) {
109
+ yield fixer . removeRange ( [ ref . prop . start - 1 , comma . end ] )
110
+ } else {
111
+ yield fixer . removeRange ( [ ref . prop . start - 1 , ref . prop . end ] )
112
+ }
113
+ }
114
+ yield fixer . removeRange ( [ ref . id . parent . parent . range [ 0 ] - 1 , ref . id . parent . parent . range [ 1 ] ] )
115
+ }
116
+ } ,
117
+ } )
118
+ }
119
+ }
120
+ } else {
121
+ context . report ( {
122
+ node : directive ,
123
+ messageId : 'invalidProps' ,
124
+ } )
125
+ }
126
+ } ,
127
+ } ,
128
+ {
129
+ components : [ 'VSelect' , 'VAutocomplete' , 'VCombobox' ] ,
130
+ slots : [ 'selection' ] ,
131
+ handler ( context , node , directive , param ) {
132
+ if ( ! getAttributes ( node . parent ) . some ( attr => [ 'chips' , 'closable-chips' ] . includes ( attr . name ) ) ) return
133
+
134
+ context . report ( {
135
+ node : directive ,
136
+ messageId : 'renamed' ,
137
+ data : {
138
+ component : node . parent . name ,
139
+ slot : directive . key . argument . name ,
140
+ newSlot : 'chip' ,
141
+ } ,
142
+ fix ( fixer ) {
143
+ return fixer . replaceText ( directive . key . argument , 'chip' )
144
+ } ,
145
+ } )
146
+ } ,
147
+ } ,
148
+ ]
149
+
3
150
// ------------------------------------------------------------------------------
4
151
// Rule Definition
5
152
// ------------------------------------------------------------------------------
@@ -13,13 +160,13 @@ module.exports = {
13
160
fixable : 'code' ,
14
161
schema : [ ] ,
15
162
messages : {
163
+ renamed : `{{ component }}'s '{{ slot }}' slot has been renamed to '{{ newSlot }}'` ,
16
164
changedProps : `{{ component }}'s '{{ slot }}' slot has changed props` ,
17
165
invalidProps : `Slot has invalid props` ,
18
166
} ,
19
167
} ,
20
168
21
169
create ( context ) {
22
- const template = context . parserServices . getTemplateBodyTokenStore ( )
23
170
let scopeStack
24
171
25
172
return context . parserServices . defineTemplateBodyVisitor ( {
@@ -32,140 +179,29 @@ module.exports = {
32
179
scopeStack . nodes . push ( variable . id )
33
180
}
34
181
35
- if ( node . name !== 'template' ) return
36
- if ( ! [ 'v-dialog' , 'v-menu' , 'v-tooltip' ] . includes ( node . parent . name ) ) return
182
+ if ( node . name !== 'template' || node . parent . type !== 'VElement' ) return
37
183
38
- const directive = node . startTag . attributes . find ( attr => {
39
- return (
40
- attr . directive &&
41
- attr . key . name . name === 'slot' &&
42
- attr . key . argument ?. name === 'activator'
43
- )
44
- } )
45
-
46
- if (
47
- ! directive ||
48
- ! directive . value ||
49
- directive . value . type !== 'VExpressionContainer' ||
50
- ! directive . value . expression ||
51
- directive . value . expression . params . length !== 1
52
- ) return
53
-
54
- const param = directive . value . expression . params [ 0 ]
55
- if ( param . type === 'Identifier' ) {
56
- // #activator="data"
57
- const boundVariables = { }
58
- node . variables . find ( variable => variable . id . name === param . name ) ?. references . forEach ( ref => {
59
- if ( ref . id . parent . type !== 'MemberExpression' ) return
60
- if (
61
- // v-bind="data.props"
62
- ref . id . parent . property . name === 'props' &&
63
- ref . id . parent . parent . parent . directive &&
64
- ref . id . parent . parent . parent . key . name . name === 'bind' &&
65
- ! ref . id . parent . parent . parent . key . argument
66
- ) return
67
- if ( ref . id . parent . property . name === 'on' ) {
68
- boundVariables . on = ref . id
69
- } else if ( ref . id . parent . property . name === 'attrs' ) {
70
- boundVariables . attrs = ref . id
71
- }
72
- } )
73
- if ( boundVariables . on ) {
74
- const ref = boundVariables . on
75
- context . report ( {
76
- node : ref ,
77
- messageId : 'changedProps' ,
78
- data : {
79
- component : node . parent . name ,
80
- slot : directive . key . argument . name ,
81
- } ,
82
- fix ( fixer ) {
83
- return fixer . replaceText ( ref . parent . parent . parent , `v-bind="${ param . name } .props"` )
84
- } ,
85
- } )
86
- }
87
- if ( boundVariables . attrs ) {
88
- const ref = boundVariables . attrs
89
- if ( ! boundVariables . on ) {
90
- context . report ( {
91
- node : boundVariables . attrs ,
92
- messageId : 'invalidProps' ,
93
- } )
94
- } else {
95
- context . report ( {
96
- node : ref ,
97
- messageId : 'changedProps' ,
98
- data : {
99
- component : node . parent . name ,
100
- slot : directive . key . argument . name ,
101
- } ,
102
- fix ( fixer ) {
103
- return fixer . removeRange ( [ ref . parent . parent . parent . range [ 0 ] - 1 , ref . parent . parent . parent . range [ 1 ] ] )
104
- } ,
105
- } )
106
- }
107
- }
108
- } else if ( param . type === 'ObjectPattern' ) {
109
- // #activator="{ on, attrs }"
110
- const boundVariables = { }
111
- param . properties . forEach ( prop => {
112
- node . variables . find ( variable => variable . id . name === prop . value . name ) ?. references . forEach ( ref => {
113
- if ( prop . key . name === 'on' ) {
114
- boundVariables . on = { prop, id : ref . id }
115
- } else if ( prop . key . name === 'attrs' ) {
116
- boundVariables . attrs = { prop, id : ref . id }
117
- }
118
- } )
119
- } )
120
- if ( boundVariables . on || boundVariables . attrs ) {
121
- if ( boundVariables . attrs && ! boundVariables . on ) {
122
- context . report ( {
123
- node : boundVariables . attrs . prop . key ,
124
- messageId : 'invalidProps' ,
125
- } )
126
- } else {
127
- context . report ( {
128
- node : param ,
129
- messageId : 'changedProps' ,
130
- data : {
131
- component : node . parent . name ,
132
- slot : directive . key . argument . name ,
133
- } ,
134
- * fix ( fixer ) {
135
- if ( boundVariables . on ) {
136
- const ref = boundVariables . on
137
- yield fixer . replaceText ( ref . prop , 'props' )
138
- yield fixer . replaceText ( ref . id . parent . parent , `v-bind="props"` )
139
- }
140
- if ( boundVariables . attrs ) {
141
- const ref = boundVariables . attrs
142
- const isLast = ref . prop === param . properties . at ( - 1 )
143
- if ( isLast ) {
144
- const comma = template . getTokenBefore ( ref . prop , { filter : token => token . value === ',' } )
145
- if ( comma ) {
146
- yield fixer . removeRange ( [ comma . start , ref . prop . end ] )
147
- } else {
148
- yield fixer . removeRange ( [ ref . prop . start - 1 , ref . prop . end ] )
149
- }
150
- } else {
151
- const comma = template . getTokenAfter ( ref . prop , { filter : token => token . value === ',' } )
152
- if ( comma ) {
153
- yield fixer . removeRange ( [ ref . prop . start - 1 , comma . end ] )
154
- } else {
155
- yield fixer . removeRange ( [ ref . prop . start - 1 , ref . prop . end ] )
156
- }
157
- }
158
- yield fixer . removeRange ( [ ref . id . parent . parent . range [ 0 ] - 1 , ref . id . parent . parent . range [ 1 ] ] )
159
- }
160
- } ,
161
- } )
162
- }
163
- }
164
- } else {
165
- context . report ( {
166
- node : directive ,
167
- messageId : 'invalidProps' ,
184
+ for ( const group of groups ) {
185
+ if (
186
+ ! group . components . includes ( classify ( node . parent . name ) ) &&
187
+ ! group . components . includes ( node . parent . name )
188
+ ) continue
189
+ const directive = node . startTag . attributes . find ( attr => {
190
+ return (
191
+ attr . directive &&
192
+ attr . key . name . name === 'slot' &&
193
+ group . slots . includes ( attr . key . argument ?. name )
194
+ )
168
195
} )
196
+ if (
197
+ ! directive ||
198
+ ! directive . value ||
199
+ directive . value . type !== 'VExpressionContainer' ||
200
+ ! directive . value . expression ||
201
+ directive . value . expression . params . length !== 1
202
+ ) continue
203
+ const param = directive . value . expression . params [ 0 ]
204
+ group . handler ( context , node , directive , param )
169
205
}
170
206
} ,
171
207
'VElement:exit' ( ) {
0 commit comments