From 79e13d111b9d1a196448fccc693da023cd99d16a Mon Sep 17 00:00:00 2001
From: Felipe <86751220+felipemelendez@users.noreply.github.com>
Date: Fri, 12 Jul 2024 21:17:37 -0400
Subject: [PATCH] Fix siblings and interleaving issues reported in #2342
(#2348)
---
lib/rules/v-if-else-key.js | 62 ++++++++++---
tests/lib/rules/v-if-else-key.js | 152 +++++++++++++++++++++++++++++++
2 files changed, 199 insertions(+), 15 deletions(-)
diff --git a/lib/rules/v-if-else-key.js b/lib/rules/v-if-else-key.js
index 75add9ca0..a83d436b8 100644
--- a/lib/rules/v-if-else-key.js
+++ b/lib/rules/v-if-else-key.js
@@ -25,6 +25,28 @@ const casing = require('../utils/casing')
* @property {VElement | null} else - The node associated with the 'v-else' directive, or null if there isn't one.
*/
+/**
+ * Checks if a given node has sibling nodes of the same type that are also conditionally rendered.
+ * This is used to determine if multiple instances of the same component are being conditionally
+ * rendered within the same parent scope.
+ *
+ * @param {VElement} node - The Vue component node to check for conditional rendering siblings.
+ * @param {string} componentName - The name of the component to check for sibling instances.
+ * @returns {boolean} True if there are sibling nodes of the same type and conditionally rendered, false otherwise.
+ */
+const hasConditionalRenderedSiblings = (node, componentName) => {
+ if (!node.parent || node.parent.type !== 'VElement') {
+ return false
+ }
+ return node.parent.children.some(
+ (sibling) =>
+ sibling !== node &&
+ sibling.type === 'VElement' &&
+ sibling.rawName === componentName &&
+ hasConditionalDirective(sibling)
+ )
+}
+
/**
* Checks for the presence of a 'key' attribute in the given node. If the 'key' attribute is missing
* and the node is part of a conditional family a report is generated.
@@ -44,26 +66,31 @@ const checkForKey = (
uniqueKey,
conditionalFamilies
) => {
- if (node.parent && node.parent.type === 'VElement') {
- const conditionalFamily = conditionalFamilies.get(node.parent)
+ if (
+ !node.parent ||
+ node.parent.type !== 'VElement' ||
+ !hasConditionalRenderedSiblings(node, componentName)
+ ) {
+ return
+ }
- if (
- conditionalFamily &&
- (utils.hasDirective(node, 'bind', 'key') ||
- utils.hasAttribute(node, 'key') ||
- !hasConditionalDirective(node) ||
- !(conditionalFamily.else || conditionalFamily.elseIf.length > 0))
- ) {
- return
- }
+ const conditionalFamily = conditionalFamilies.get(node.parent)
+
+ if (!conditionalFamily || utils.hasAttribute(node, 'key')) {
+ return
+ }
+
+ const needsKey =
+ conditionalFamily.if === node ||
+ conditionalFamily.else === node ||
+ conditionalFamily.elseIf.includes(node)
+ if (needsKey) {
context.report({
node: node.startTag,
loc: node.startTag.loc,
messageId: 'requireKey',
- data: {
- componentName
- },
+ data: { componentName },
fix(fixer) {
const afterComponentNamePosition =
node.startTag.range[0] + componentName.length + 1
@@ -190,13 +217,18 @@ module.exports = {
if (node.parent && node.parent.type === 'VElement') {
let conditionalFamily = conditionalFamilies.get(node.parent)
- if (conditionType === 'if' && !conditionalFamily) {
+ if (!conditionalFamily) {
conditionalFamily = createConditionalFamily(node)
conditionalFamilies.set(node.parent, conditionalFamily)
}
if (conditionalFamily) {
switch (conditionType) {
+ case 'if': {
+ conditionalFamily = createConditionalFamily(node)
+ conditionalFamilies.set(node.parent, conditionalFamily)
+ break
+ }
case 'else-if': {
conditionalFamily.elseIf.push(node)
break
diff --git a/tests/lib/rules/v-if-else-key.js b/tests/lib/rules/v-if-else-key.js
index 16d975a0a..46c42f52f 100644
--- a/tests/lib/rules/v-if-else-key.js
+++ b/tests/lib/rules/v-if-else-key.js
@@ -127,6 +127,90 @@ tester.run('v-if-else-key', rule, {
}
`
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ …
+ …
+
+ …
+ …
+
+
+
+ `
}
],
invalid: [
@@ -424,6 +508,74 @@ tester.run('v-if-else-key', rule, {
line: 6
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ output: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ errors: [
+ {
+ message:
+ "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+ line: 4
+ },
+ {
+ message:
+ "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+ line: 5
+ },
+ {
+ message:
+ "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+ line: 7
+ },
+ {
+ message:
+ "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+ line: 8
+ },
+ {
+ message:
+ "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+ line: 9
+ }
+ ]
}
]
})