diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
index 07025f5e7..f5ff3a0e7 100644
--- a/lib/rules/valid-v-slot.js
+++ b/lib/rules/valid-v-slot.js
@@ -6,6 +6,10 @@
const utils = require('../utils')
+/**
+ * @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
+ */
+
/**
* Get all `v-slot` directives on a given element.
* @param {VElement} node The VElement node to check.
@@ -93,27 +97,128 @@ function getNormalizedName(node, sourceCode) {
* Get all `v-slot` directives which are distributed to the same slot as a given `v-slot` directive node.
* @param {VDirective[][]} vSlotGroups The result of `getAllNamedSlotElements()`.
* @param {VDirective} currentVSlot The current `v-slot` directive node.
+ * @param {VSlotVForVariables | null} currentVSlotVForVars The current `v-for` variables.
* @param {SourceCode} sourceCode The source code.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
* @returns {VDirective[][]} The array of the group of `v-slot` directives.
*/
-function filterSameSlot(vSlotGroups, currentVSlot, sourceCode) {
+function filterSameSlot(
+ vSlotGroups,
+ currentVSlot,
+ currentVSlotVForVars,
+ sourceCode,
+ tokenStore
+) {
const currentName = getNormalizedName(currentVSlot, sourceCode)
return vSlotGroups
.map((vSlots) =>
- vSlots.filter(
- (vSlot) => getNormalizedName(vSlot, sourceCode) === currentName
- )
+ vSlots.filter((vSlot) => {
+ if (getNormalizedName(vSlot, sourceCode) !== currentName) {
+ return false
+ }
+ const vForExpr = getVSlotVForVariableIfUsingIterationVars(
+ vSlot,
+ utils.getDirective(vSlot.parent.parent, 'for')
+ )
+ if (!currentVSlotVForVars || !vForExpr) {
+ return !currentVSlotVForVars && !vForExpr
+ }
+ if (
+ !equalVSlotVForVariables(currentVSlotVForVars, vForExpr, tokenStore)
+ ) {
+ return false
+ }
+ //
+ return true
+ })
)
.filter((slots) => slots.length >= 1)
}
/**
- * Check whether a given argument node is using an iteration variable that the element defined.
+ * Determines whether the two given `v-slot` variables are considered to be equal.
+ * @param {VSlotVForVariables} a First element.
+ * @param {VSlotVForVariables} b Second element.
+ * @param {ParserServices.TokenStore} tokenStore The token store.
+ * @returns {boolean} `true` if the elements are considered to be equal.
+ */
+function equalVSlotVForVariables(a, b, tokenStore) {
+ if (a.variables.length !== b.variables.length) {
+ return false
+ }
+ if (!equal(a.expr.right, b.expr.right)) {
+ return false
+ }
+
+ const checkedVarNames = new Set()
+ const len = Math.min(a.expr.left.length, b.expr.left.length)
+ for (let index = 0; index < len; index++) {
+ const aPtn = a.expr.left[index]
+ const bPtn = b.expr.left[index]
+
+ const aVar = a.variables.find(
+ (v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
+ )
+ const bVar = b.variables.find(
+ (v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
+ )
+ if (aVar && bVar) {
+ if (aVar.id.name !== bVar.id.name) {
+ return false
+ }
+ if (!equal(aPtn, bPtn)) {
+ return false
+ }
+ checkedVarNames.add(aVar.id.name)
+ } else if (aVar || bVar) {
+ return false
+ }
+ }
+ for (const v of a.variables) {
+ if (!checkedVarNames.has(v.id.name)) {
+ if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
+ return false
+ }
+ }
+ }
+ return true
+
+ /**
+ * Determines whether the two given nodes are considered to be equal.
+ * @param {ASTNode} a First node.
+ * @param {ASTNode} b Second node.
+ * @returns {boolean} `true` if the nodes are considered to be equal.
+ */
+ function equal(a, b) {
+ if (a.type !== b.type) {
+ return false
+ }
+ return utils.equalTokens(a, b, tokenStore)
+ }
+}
+
+/**
+ * Gets the `v-for` directive and variable that provide the variables used by the given` v-slot` directive.
+ * @param {VDirective} vSlot The current `v-slot` directive node.
+ * @param {VDirective | null} [vFor] The current `v-for` directive node.
+ * @returns { VSlotVForVariables | null } The VSlotVForVariable.
+ */
+function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) {
+ const expr =
+ vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
+ const variables =
+ expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent)
+ return expr && variables && variables.length ? { expr, variables } : null
+}
+
+/**
+ * Gets iterative variables if a given argument node is using iterative variables that the element defined.
* @param {VExpressionContainer|VIdentifier|null} argument The argument node to check.
* @param {VElement} element The element node which has the argument.
- * @returns {boolean} `true` if the argument node is using the iteration variable.
+ * @returns {VVariable[]} The argument node is using iteration variables.
*/
-function isUsingIterationVar(argument, element) {
+function getUsingIterationVars(argument, element) {
+ const vars = []
if (argument && argument.type === 'VExpressionContainer') {
for (const { variable } of argument.references) {
if (
@@ -122,11 +227,11 @@ function isUsingIterationVar(argument, element) {
variable.id.range[0] > element.startTag.range[0] &&
variable.id.range[1] < element.startTag.range[1]
) {
- return true
+ vars.push(variable)
}
}
}
- return false
+ return vars
}
/**
@@ -206,6 +311,9 @@ module.exports = {
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.getSourceCode()
+ const tokenStore =
+ context.parserServices.getTemplateBodyTokenStore &&
+ context.parserServices.getTemplateBodyTokenStore()
const options = context.options[0] || {}
const allowModifiers = options.allowModifiers === true
@@ -256,12 +364,18 @@ module.exports = {
})
}
if (ownerElement === parentElement) {
+ const vFor = utils.getDirective(element, 'for')
+ const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars(
+ node,
+ vFor
+ )
const vSlotGroupsOfSameSlot = filterSameSlot(
vSlotGroupsOnChildren,
node,
- sourceCode
+ vSlotVForVar,
+ sourceCode,
+ tokenStore
)
- const vFor = utils.getDirective(element, 'for')
if (
vSlotGroupsOfSameSlot.length >= 2 &&
!vSlotGroupsOfSameSlot[0].includes(node)
@@ -273,7 +387,7 @@ module.exports = {
messageId: 'disallowDuplicateSlotsOnChildren'
})
}
- if (vFor && !isUsingIterationVar(node.key.argument, element)) {
+ if (vFor && !vSlotVForVar) {
// E.g.,
context.report({
node,
diff --git a/tests/lib/rules/valid-v-slot.js b/tests/lib/rules/valid-v-slot.js
index 08b55224d..dfcb0eb75 100644
--- a/tests/lib/rules/valid-v-slot.js
+++ b/tests/lib/rules/valid-v-slot.js
@@ -84,6 +84,30 @@ tester.run('valid-v-slot', rule, {
{{value}}
`,
+ `
+
+ {{value}}
+ {{value}}
+
+ `,
+ `
+
+ {{value}}
+ {{value}}
+
+ `,
+ `
+
+ {{value}}
+ {{value}}
+
+ `,
+ `
+
+ {{value}}
+ {{value}}
+
+ `,
{
code: `
@@ -282,6 +306,43 @@ tester.run('valid-v-slot', rule, {
`,
errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
},
+ {
+ code: `
+
+
+ {{value}}
+ {{value}}
+
+
+ `,
+ errors: [
+ { messageId: 'disallowDuplicateSlotsOnChildren' },
+ { messageId: 'disallowDuplicateSlotsOnChildren' },
+ { messageId: 'disallowDuplicateSlotsOnChildren' }
+ ]
+ },
+ {
+ code: `
+
+
+ {{value}}
+ {{value}}
+
+
+ `,
+ errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
+ },
+ {
+ code: `
+
+
+ {{value}}
+ {{value}}
+
+
+ `,
+ errors: [{ messageId: 'disallowDuplicateSlotsOnChildren' }]
+ },
{
code: `