66
77const utils = require ( '../utils' )
88
9+ /**
10+ * @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
11+ */
12+
913/**
1014 * Get all `v-slot` directives on a given element.
1115 * @param {VElement } node The VElement node to check.
@@ -93,27 +97,128 @@ function getNormalizedName(node, sourceCode) {
9397 * Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node.
9498 * @param {VDirective[][] } vSlotGroups The result of `getAllNamedSlotElements()`.
9599 * @param {VDirective } currentVSlot The current `v-slot` directive node.
100+ * @param {VSlotVForVariables | null } currentVSlotVForVars The current `v-for` variables.
96101 * @param {SourceCode } sourceCode The source code.
102+ * @param {ParserServices.TokenStore } tokenStore The token store.
97103 * @returns {VDirective[][] } The array of the group of `v-slot` directives.
98104 */
99- function filterSameSlot ( vSlotGroups , currentVSlot , sourceCode ) {
105+ function filterSameSlot (
106+ vSlotGroups ,
107+ currentVSlot ,
108+ currentVSlotVForVars ,
109+ sourceCode ,
110+ tokenStore
111+ ) {
100112 const currentName = getNormalizedName ( currentVSlot , sourceCode )
101113 return vSlotGroups
102114 . 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+ } )
106134 )
107135 . filter ( ( slots ) => slots . length >= 1 )
108136}
109137
110138/**
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.
112216 * @param {VExpressionContainer|VIdentifier|null } argument The argument node to check.
113217 * @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 .
115219 */
116- function isUsingIterationVar ( argument , element ) {
220+ function getUsingIterationVars ( argument , element ) {
221+ const vars = [ ]
117222 if ( argument && argument . type === 'VExpressionContainer' ) {
118223 for ( const { variable } of argument . references ) {
119224 if (
@@ -122,11 +227,11 @@ function isUsingIterationVar(argument, element) {
122227 variable . id . range [ 0 ] > element . startTag . range [ 0 ] &&
123228 variable . id . range [ 1 ] < element . startTag . range [ 1 ]
124229 ) {
125- return true
230+ vars . push ( variable )
126231 }
127232 }
128233 }
129- return false
234+ return vars
130235}
131236
132237/**
@@ -206,6 +311,9 @@ module.exports = {
206311 /** @param {RuleContext } context */
207312 create ( context ) {
208313 const sourceCode = context . getSourceCode ( )
314+ const tokenStore =
315+ context . parserServices . getTemplateBodyTokenStore &&
316+ context . parserServices . getTemplateBodyTokenStore ( )
209317 const options = context . options [ 0 ] || { }
210318 const allowModifiers = options . allowModifiers === true
211319
@@ -256,12 +364,18 @@ module.exports = {
256364 } )
257365 }
258366 if ( ownerElement === parentElement ) {
367+ const vFor = utils . getDirective ( element , 'for' )
368+ const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars (
369+ node ,
370+ vFor
371+ )
259372 const vSlotGroupsOfSameSlot = filterSameSlot (
260373 vSlotGroupsOnChildren ,
261374 node ,
262- sourceCode
375+ vSlotVForVar ,
376+ sourceCode ,
377+ tokenStore
263378 )
264- const vFor = utils . getDirective ( element , 'for' )
265379 if (
266380 vSlotGroupsOfSameSlot . length >= 2 &&
267381 ! vSlotGroupsOfSameSlot [ 0 ] . includes ( node )
@@ -273,7 +387,7 @@ module.exports = {
273387 messageId : 'disallowDuplicateSlotsOnChildren'
274388 } )
275389 }
276- if ( vFor && ! isUsingIterationVar ( node . key . argument , element ) ) {
390+ if ( vFor && ! vSlotVForVar ) {
277391 // E.g., <template v-for="x of xs" #one></template>
278392 context . report ( {
279393 node,
0 commit comments