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 + } + ] } ] })