6
6
7
7
const utils = require ( '../utils' )
8
8
9
+ /**
10
+ * @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
11
+ */
12
+
9
13
/**
10
14
* Get all `v-slot` directives on a given element.
11
15
* @param {VElement } node The VElement node to check.
@@ -93,27 +97,128 @@ function getNormalizedName(node, sourceCode) {
93
97
* Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node.
94
98
* @param {VDirective[][] } vSlotGroups The result of `getAllNamedSlotElements()`.
95
99
* @param {VDirective } currentVSlot The current `v-slot` directive node.
100
+ * @param {VSlotVForVariables | null } currentVSlotVForVars The current `v-for` variables.
96
101
* @param {SourceCode } sourceCode The source code.
102
+ * @param {ParserServices.TokenStore } tokenStore The token store.
97
103
* @returns {VDirective[][] } The array of the group of `v-slot` directives.
98
104
*/
99
- function filterSameSlot ( vSlotGroups , currentVSlot , sourceCode ) {
105
+ function filterSameSlot (
106
+ vSlotGroups ,
107
+ currentVSlot ,
108
+ currentVSlotVForVars ,
109
+ sourceCode ,
110
+ tokenStore
111
+ ) {
100
112
const currentName = getNormalizedName ( currentVSlot , sourceCode )
101
113
return vSlotGroups
102
114
. map ( ( vSlots ) =>
103
- vSlots . filter (
104
- ( vSlot ) => getNormalizedName ( vSlot , sourceCode ) === currentName
105
- )
115
+ vSlots . filter ( ( vSlot ) => {
116
+ if ( getNormalizedName ( vSlot , sourceCode ) !== currentName ) {
117
+ return false
118
+ }
119
+ const vForExpr = getVSlotVForVariableIfUsingIterationVars (
120
+ vSlot ,
121
+ utils . getDirective ( vSlot . parent . parent , 'for' )
122
+ )
123
+ if ( ! currentVSlotVForVars || ! vForExpr ) {
124
+ return ! currentVSlotVForVars && ! vForExpr
125
+ }
126
+ if (
127
+ ! equalVSlotVForVariables ( currentVSlotVForVars , vForExpr , tokenStore )
128
+ ) {
129
+ return false
130
+ }
131
+ //
132
+ return true
133
+ } )
106
134
)
107
135
. filter ( ( slots ) => slots . length >= 1 )
108
136
}
109
137
110
138
/**
111
- * Check whether a given argument node is using an iteration variable that the element defined.
139
+ * Determines whether the two given `v-slot` variables are considered to be equal.
140
+ * @param {VSlotVForVariables } a First element.
141
+ * @param {VSlotVForVariables } b Second element.
142
+ * @param {ParserServices.TokenStore } tokenStore The token store.
143
+ * @returns {boolean } `true` if the elements are considered to be equal.
144
+ */
145
+ function equalVSlotVForVariables ( a , b , tokenStore ) {
146
+ if ( a . variables . length !== b . variables . length ) {
147
+ return false
148
+ }
149
+ if ( ! equal ( a . expr . right , b . expr . right ) ) {
150
+ return false
151
+ }
152
+
153
+ const checkedVarNames = new Set ( )
154
+ const len = Math . min ( a . expr . left . length , b . expr . left . length )
155
+ for ( let index = 0 ; index < len ; index ++ ) {
156
+ const aPtn = a . expr . left [ index ]
157
+ const bPtn = b . expr . left [ index ]
158
+
159
+ const aVar = a . variables . find (
160
+ ( v ) => aPtn . range [ 0 ] <= v . id . range [ 0 ] && v . id . range [ 1 ] <= aPtn . range [ 1 ]
161
+ )
162
+ const bVar = b . variables . find (
163
+ ( v ) => bPtn . range [ 0 ] <= v . id . range [ 0 ] && v . id . range [ 1 ] <= bPtn . range [ 1 ]
164
+ )
165
+ if ( aVar && bVar ) {
166
+ if ( aVar . id . name !== bVar . id . name ) {
167
+ return false
168
+ }
169
+ if ( ! equal ( aPtn , bPtn ) ) {
170
+ return false
171
+ }
172
+ checkedVarNames . add ( aVar . id . name )
173
+ } else if ( aVar || bVar ) {
174
+ return false
175
+ }
176
+ }
177
+ for ( const v of a . variables ) {
178
+ if ( ! checkedVarNames . has ( v . id . name ) ) {
179
+ if ( b . variables . every ( ( bv ) => v . id . name !== bv . id . name ) ) {
180
+ return false
181
+ }
182
+ }
183
+ }
184
+ return true
185
+
186
+ /**
187
+ * Determines whether the two given nodes are considered to be equal.
188
+ * @param {ASTNode } a First node.
189
+ * @param {ASTNode } b Second node.
190
+ * @returns {boolean } `true` if the nodes are considered to be equal.
191
+ */
192
+ function equal ( a , b ) {
193
+ if ( a . type !== b . type ) {
194
+ return false
195
+ }
196
+ return utils . equalTokens ( a , b , tokenStore )
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Gets the `v-for` directive and variable that provide the variables used by the given` v-slot` directive.
202
+ * @param {VDirective } vSlot The current `v-slot` directive node.
203
+ * @param {VDirective | null } [vFor] The current `v-for` directive node.
204
+ * @returns { VSlotVForVariables | null } The VSlotVForVariable.
205
+ */
206
+ function getVSlotVForVariableIfUsingIterationVars ( vSlot , vFor ) {
207
+ const expr =
208
+ vFor && vFor . value && /** @type {VForExpression } */ ( vFor . value . expression )
209
+ const variables =
210
+ expr && getUsingIterationVars ( vSlot . key . argument , vSlot . parent . parent )
211
+ return expr && variables && variables . length ? { expr, variables } : null
212
+ }
213
+
214
+ /**
215
+ * Gets iterative variables if a given argument node is using iterative variables that the element defined.
112
216
* @param {VExpressionContainer|VIdentifier|null } argument The argument node to check.
113
217
* @param {VElement } element The element node which has the argument.
114
- * @returns {boolean } `true` if the argument node is using the iteration variable .
218
+ * @returns {VVariable[] } The argument node is using iteration variables .
115
219
*/
116
- function isUsingIterationVar ( argument , element ) {
220
+ function getUsingIterationVars ( argument , element ) {
221
+ const vars = [ ]
117
222
if ( argument && argument . type === 'VExpressionContainer' ) {
118
223
for ( const { variable } of argument . references ) {
119
224
if (
@@ -122,11 +227,11 @@ function isUsingIterationVar(argument, element) {
122
227
variable . id . range [ 0 ] > element . startTag . range [ 0 ] &&
123
228
variable . id . range [ 1 ] < element . startTag . range [ 1 ]
124
229
) {
125
- return true
230
+ vars . push ( variable )
126
231
}
127
232
}
128
233
}
129
- return false
234
+ return vars
130
235
}
131
236
132
237
/**
@@ -206,6 +311,9 @@ module.exports = {
206
311
/** @param {RuleContext } context */
207
312
create ( context ) {
208
313
const sourceCode = context . getSourceCode ( )
314
+ const tokenStore =
315
+ context . parserServices . getTemplateBodyTokenStore &&
316
+ context . parserServices . getTemplateBodyTokenStore ( )
209
317
const options = context . options [ 0 ] || { }
210
318
const allowModifiers = options . allowModifiers === true
211
319
@@ -256,12 +364,18 @@ module.exports = {
256
364
} )
257
365
}
258
366
if ( ownerElement === parentElement ) {
367
+ const vFor = utils . getDirective ( element , 'for' )
368
+ const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars (
369
+ node ,
370
+ vFor
371
+ )
259
372
const vSlotGroupsOfSameSlot = filterSameSlot (
260
373
vSlotGroupsOnChildren ,
261
374
node ,
262
- sourceCode
375
+ vSlotVForVar ,
376
+ sourceCode ,
377
+ tokenStore
263
378
)
264
- const vFor = utils . getDirective ( element , 'for' )
265
379
if (
266
380
vSlotGroupsOfSameSlot . length >= 2 &&
267
381
! vSlotGroupsOfSameSlot [ 0 ] . includes ( node )
@@ -273,7 +387,7 @@ module.exports = {
273
387
messageId : 'disallowDuplicateSlotsOnChildren'
274
388
} )
275
389
}
276
- if ( vFor && ! isUsingIterationVar ( node . key . argument , element ) ) {
390
+ if ( vFor && ! vSlotVForVar ) {
277
391
// E.g., <template v-for="x of xs" #one></template>
278
392
context . report ( {
279
393
node,
0 commit comments