Repository: vuejs/eslint-plugin-vue (js)

Issue:
Rule proposal: `v-if-else-key`
**Style guide:**
https://vuejs.org/v2/style-guide/#v-if-v-if-else-v-else-without-key-use-with-caution

**Description:**
This rule would enforce to add `key` property on elements with `v-if` and `v-else` directives if they're both of the same type.

Pull request description:
Require key for conditionally rendered repeated components
This PR resolves #255 

When Vue updates the DOM, it employs a diffing algorithm to enhance efficiency. If elements of the same type are added or removed, Vue attempts to optimize the process by recycling the previous element for the new one. This recycling strategy is an integral part of Vue's optimization efforts to minimize rendering overhead.

However, this clever recycling strategy can sometimes lead to unexpected issues, primarily because it may not properly update the components. By assigning unique keys to each repeated component within the scope of the component, Vue can quickly identify which items have changed, been added, or been removed.

This lint rule is designed to guard against such recycling issues by requiring a unique key attribute on repeated components that are part of a conditional family (v-if, v-else-if, v-else) within the same scope level. The rule not only checks for the presence of a key attribute but also ensures that repeated components within these conditional families have unique keys.

If a component is found to violate this rule, the lint tool will suggest a fix by proposing a unique key based on the component's name and count (e.g., "some-component-2").

By recognizing the unique key attribute, Vue will abandon its recycling strategy and perform a full removal and addition of the new element, thereby ensuring that the component is rendered correctly.

Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index 4e2c26de..71a05d42 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -277,6 +277,7 @@ For example:
 | [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components |  | :hammer: |
 | [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | :hammer: |
 | [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
+| [vue/v-if-else-key](./v-if-else-key.md) | require key attribute for conditionally rendered repeated components | :wrench: | :warning: |
 | [vue/v-on-handler-style](./v-on-handler-style.md) | enforce writing style for handlers in `v-on` directives | :wrench: | :hammer: |
 | [vue/valid-define-options](./valid-define-options.md) | enforce valid `defineOptions` compiler macro |  | :warning: |
 
diff --git a/docs/rules/require-v-for-key.md b/docs/rules/require-v-for-key.md
index c8f894ff..7699e1b3 100644
--- a/docs/rules/require-v-for-key.md
+++ b/docs/rules/require-v-for-key.md
@@ -5,6 +5,7 @@ title: vue/require-v-for-key
 description: require `v-bind:key` with `v-for` directives
 since: v3.0.0
 ---
+
 # vue/require-v-for-key
 
 > require `v-bind:key` with `v-for` directives
@@ -20,12 +21,9 @@ This rule reports the elements which have `v-for` and do not have `v-bind:key` w
 ```vue
 <template>
   <!-- ✓ GOOD -->
-  <div
-    v-for="todo in todos"
-    :key="todo.id"
-  />
+  <div v-for="todo in todos" :key="todo.id" />
   <!-- ✗ BAD -->
-  <div v-for="todo in todos"/>
+  <div v-for="todo in todos" />
 </template>
 ```
 
@@ -43,8 +41,10 @@ Nothing.
 ## :couple: Related Rules
 
 - [vue/valid-v-for]
+- [vue/v-if-else-key]
 
 [vue/valid-v-for]: ./valid-v-for.md
+[vue/v-if-else-key]: ./v-if-else-key.md
 
 ## :books: Further Reading
 
diff --git a/docs/rules/v-if-else-key.md b/docs/rules/v-if-else-key.md
new file mode 100644
index 00000000..11c8c642
--- /dev/null
+++ b/docs/rules/v-if-else-key.md
@@ -0,0 +1,56 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/v-if-else-key
+description: require key attribute for conditionally rendered repeated components
+---
+
+# vue/v-if-else-key
+
+> require key attribute for conditionally rendered repeated components
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule checks for components that are both repeated and conditionally rendered within the same scope. If such a component is found, the rule then checks for the presence of a 'key' directive. If the 'key' directive is missing, the rule issues a warning and offers a fix.
+
+This rule is not required in Vue 3, as the key is automatically assigned to the elements.
+
+<eslint-code-block fix :rules="{'vue/v-if-else-key': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <my-component v-if="condition1" :key="one" />
+  <my-component v-else-if="condition2" :key="two" />
+  <my-component v-else :key="three" />
+
+  <!-- ✗ BAD -->
+  <my-component v-if="condition1" />
+  <my-component v-else-if="condition2" />
+  <my-component v-else />
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/require-v-for-key]
+
+[vue/require-v-for-key]: ./require-v-for-key.md
+
+## :books: Further Reading
+
+- [Guide (for v2) - v-if without key](https://v2.vuejs.org/v2/style-guide/#v-if-v-else-if-v-else-without-key-use-with-caution)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-if-else-key.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-if-else-key.js)
diff --git a/lib/index.js b/lib/index.js
index 0f3dcba2..3497a7b4 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -216,6 +216,7 @@ module.exports = {
     'use-v-on-exact': require('./rules/use-v-on-exact'),
     'v-bind-style': require('./rules/v-bind-style'),
     'v-for-delimiter-style': require('./rules/v-for-delimiter-style'),
+    'v-if-else-key': require('./rules/v-if-else-key'),
     'v-on-event-hyphenation': require('./rules/v-on-event-hyphenation'),
     'v-on-function-call': require('./rules/v-on-function-call'),
     'v-on-handler-style': require('./rules/v-on-handler-style'),
diff --git a/lib/rules/v-if-else-key.js b/lib/rules/v-if-else-key.js
new file mode 100644
index 00000000..0a6ec9cf
--- /dev/null
+++ b/lib/rules/v-if-else-key.js
@@ -0,0 +1,285 @@
+/**
+ * @author Felipe Melendez
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// =============================================================================
+// Requirements
+// =============================================================================
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+// =============================================================================
+// Rule Helpers
+// =============================================================================
+
+/**
+ * A conditional family is made up of a group of repeated components that are conditionally rendered
+ * using v-if, v-else-if, and v-else.
+ *
+ * @typedef {Object} ConditionalFamily
+ * @property {VElement} if - The node associated with the 'v-if' directive.
+ * @property {VElement[]} elseIf - An array of nodes associated with 'v-else-if' directives.
+ * @property {VElement | null} else - The node associated with the 'v-else' directive, or null if there isn't one.
+ */
+
+/**
+ * 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.
+ * The fix proposed adds a unique key based on the component's name and count,
+ * following the format '${kebabCase(componentName)}-${componentCount}', e.g., 'some-component-2'.
+ *
+ * @param {VElement} node - The Vue component node to check for a 'key' attribute.
+ * @param {RuleContext} context - The rule's context object, used for reporting.
+ * @param {string} componentName - Name of the component.
+ * @param {string} uniqueKey - A unique key for the repeated component, used for the fix.
+ * @param {Map<VElement, ConditionalFamily>} conditionalFamilies - Map of conditionally rendered components and their respective conditional directives.
+ */
+const checkForKey = (
+  node,
+  context,
+  componentName,
+  uniqueKey,
+  conditionalFamilies
+) => {
+  if (node.parent && node.parent.type === 'VElement') {
+    const conditionalFamily = conditionalFamilies.get(node.parent)
+
+    if (
+      conditionalFamily &&
+      (utils.hasDirective(node, 'bind', 'key') ||
+        utils.hasAttribute(node, 'key') ||
+        !hasConditionalDirective(node) ||
+        !(conditionalFamily.else || conditionalFamily.elseIf.length > 0))
+    ) {
+      return
+    }
+
+    context.report({
+      node: node.startTag,
+      loc: node.startTag.loc,
+      messageId: 'requireKey',
+      data: {
+        componentName
+      },
+      fix(fixer) {
+        const afterComponentNamePosition =
+          node.startTag.range[0] + componentName.length + 1
+        return fixer.insertTextBeforeRange(
+          [afterComponentNamePosition, afterComponentNamePosition],
+          ` key="${uniqueKey}"`
+        )
+      }
+    })
+  }
+}
+
+/**
+ * Checks for the presence of conditional directives in the given node.
+ *
+ * @param {VElement} node - The node to check for conditional directives.
+ * @returns {boolean} Returns true if a conditional directive is found in the node or its parents,
+ *   false otherwise.
+ */
+const hasConditionalDirective = (node) =>
+  utils.hasDirective(node, 'if') ||
+  utils.hasDirective(node, 'else-if') ||
+  utils.hasDirective(node, 'else')
+
+// =============================================================================
+// Rule Definition
+// =============================================================================
+
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description:
+        'require key attribute for conditionally rendered repeated components',
+      categories: null,
+      recommended: false,
+      url: 'https://eslint.vuejs.org/rules/v-if-else-key.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      requireKey:
+        "Conditionally rendered repeated component '{{componentName}}' expected to have a 'key' attribute."
+    }
+  },
+  /**
+   * Creates and returns a rule object which checks usage of repeated components. If a component
+   * is used more than once, it checks for the presence of a key.
+   *
+   * @param {RuleContext} context - The context object.
+   * @returns {Object} A dictionary of functions to be called on traversal of the template body by
+   *   the eslint parser.
+   */
+  create(context) {
+    /**
+     * Map to store conditionally rendered components and their respective conditional directives.
+     *
+     * @type {Map<VElement, ConditionalFamily>}
+     */
+    const conditionalFamilies = new Map()
+
+    /**
+     * Array of Maps to keep track of components and their usage counts along with the first
+     * node instance. Each Map represents a different scope level, and maps a component name to
+     * an object containing the count and a reference to the first node.
+     */
+    /** @type {Map<string, { count: number; firstNode: any }>[]} */
+    const componentUsageStack = [new Map()]
+
+    /**
+     * Checks if a given node represents a custom component without any conditional directives.
+     *
+     * @param {VElement} node - The AST node to check.
+     * @returns {boolean} True if the node represents a custom component without any conditional directives, false otherwise.
+     */
+    const isCustomComponentWithoutCondition = (node) =>
+      node.type === 'VElement' &&
+      utils.isCustomComponent(node) &&
+      !hasConditionalDirective(node)
+
+    /** Set of built-in Vue components that are exempt from the rule. */
+    /** @type {Set<string>} */
+    const exemptTags = new Set(['component', 'slot', 'template'])
+
+    /** Set to keep track of nodes we've pushed to the stack. */
+    /** @type {Set<any>} */
+    const pushedNodes = new Set()
+
+    /**
+     * Creates and returns an object representing a conditional family.
+     *
+     * @param {VElement} ifNode - The VElement associated with the 'v-if' directive.
+     * @returns {ConditionalFamily}
+     */
+    const createConditionalFamily = (ifNode) => ({
+      if: ifNode,
+      elseIf: [],
+      else: null
+    })
+
+    return utils.defineTemplateBodyVisitor(context, {
+      /**
+       * Callback to be executed when a Vue element is traversed. This function checks if the
+       * element is a component, increments the usage count of the component in the
+       * current scope, and checks for the key directive if the component is repeated.
+       *
+       * @param {VElement} node - The traversed Vue element.
+       */
+      VElement(node) {
+        if (exemptTags.has(node.rawName)) {
+          return
+        }
+
+        const condition =
+          utils.getDirective(node, 'if') ||
+          utils.getDirective(node, 'else-if') ||
+          utils.getDirective(node, 'else')
+
+        if (condition) {
+          const conditionType = condition.key.name.name
+
+          if (node.parent && node.parent.type === 'VElement') {
+            let conditionalFamily = conditionalFamilies.get(node.parent)
+
+            if (conditionType === 'if' && !conditionalFamily) {
+              conditionalFamily = createConditionalFamily(node)
+              conditionalFamilies.set(node.parent, conditionalFamily)
+            }
+
+            if (conditionalFamily) {
+              switch (conditionType) {
+                case 'else-if': {
+                  conditionalFamily.elseIf.push(node)
+                  break
+                }
+                case 'else': {
+                  conditionalFamily.else = node
+                  break
+                }
+              }
+            }
+          }
+        }
+
+        if (isCustomComponentWithoutCondition(node)) {
+          componentUsageStack.push(new Map())
+          return
+        }
+
+        if (!utils.isCustomComponent(node)) {
+          return
+        }
+
+        const componentName = node.rawName
+        const currentScope = componentUsageStack[componentUsageStack.length - 1]
+        const usageInfo = currentScope.get(componentName) || {
+          count: 0,
+          firstNode: null
+        }
+
+        if (hasConditionalDirective(node)) {
+          // Store the first node if this is the first occurrence
+          if (usageInfo.count === 0) {
+            usageInfo.firstNode = node
+          }
+
+          if (usageInfo.count > 0) {
+            const uniqueKey = `${casing.kebabCase(componentName)}-${
+              usageInfo.count + 1
+            }`
+            checkForKey(
+              node,
+              context,
+              componentName,
+              uniqueKey,
+              conditionalFamilies
+            )
+
+            // If this is the second occurrence, also apply a fix to the first occurrence
+            if (usageInfo.count === 1) {
+              const uniqueKeyForFirstInstance = `${casing.kebabCase(
+                componentName
+              )}-1`
+              checkForKey(
+                usageInfo.firstNode,
+                context,
+                componentName,
+                uniqueKeyForFirstInstance,
+                conditionalFamilies
+              )
+            }
+          }
+          usageInfo.count += 1
+          currentScope.set(componentName, usageInfo)
+        }
+        componentUsageStack.push(new Map())
+        pushedNodes.add(node)
+      },
+
+      'VElement:exit'(node) {
+        if (exemptTags.has(node.rawName)) {
+          return
+        }
+        if (isCustomComponentWithoutCondition(node)) {
+          componentUsageStack.pop()
+          return
+        }
+        if (!utils.isCustomComponent(node)) {
+          return
+        }
+        if (pushedNodes.has(node)) {
+          componentUsageStack.pop()
+          pushedNodes.delete(node)
+        }
+      }
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/v-if-else-key.js b/tests/lib/rules/v-if-else-key.js
new file mode 100644
index 00000000..4aab257d
--- /dev/null
+++ b/tests/lib/rules/v-if-else-key.js
@@ -0,0 +1,429 @@
+/**
+ * @author Felipe Melendez
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/v-if-else-key')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('v-if-else-key', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <CustomComponent v-if="some-condition" key="key1" />
+            <CustomComponent v-else key="key2" />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+    <div>
+      <CustomComponent v-if="some-condition" />
+      <CustomComponent />
+    </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <div v-if="some-condition" >
+              <CustomComponent />
+            </div>
+            <CustomComponent v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <slot v-if="some-condition" />
+            <slot v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <div v-if="someCondition" />
+            <div v-else-if="otherCondition" />
+            <div v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <div v-if="foo" />
+            <span v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <OuterComponent v-if="showOuterComponent">
+              <InnerComponent v-if="showInnerComponent1" />
+              <InnerComponent v-else />
+            </OuterComponent>
+            <OuterComponent v-else>
+              <InnerComponent :key="key1" />
+              <InnerComponent :key="key2" />
+            </OuterComponent>
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <OuterComponent key="outer-component-1" v-if="showOuterComponent">
+              <InnerComponent key="inner-component-1" v-if="showInnerComponent1" />
+              <InnerComponent key="inner-component-2" v-else />
+            </OuterComponent>
+            <OuterComponent key="outer-component-2" v-else>
+              <InnerComponent :key="key1" />
+              <InnerComponent :key="key2" />
+            </OuterComponent>
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      errors: [
+        {
+          message:
+            "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.",
+          line: 4
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 5
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 6
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.",
+          line: 8
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <OuterComponent v-if="showOuterComponent">
+              <InnerComponent v-if="showInnerComponent1">
+                <DifferentScopeComponent />
+              </InnerComponent>
+              <InnerComponent v-else />
+            </OuterComponent>
+            <OuterComponent v-else>
+              <InnerComponent />
+              <InnerComponent />
+            </OuterComponent>
+            <DifferentScopeComponent />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <OuterComponent key="outer-component-1" v-if="showOuterComponent">
+              <InnerComponent key="inner-component-1" v-if="showInnerComponent1">
+                <DifferentScopeComponent />
+              </InnerComponent>
+              <InnerComponent key="inner-component-2" v-else />
+            </OuterComponent>
+            <OuterComponent key="outer-component-2" v-else>
+              <InnerComponent />
+              <InnerComponent />
+            </OuterComponent>
+            <DifferentScopeComponent />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      errors: [
+        {
+          message:
+            "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.",
+          line: 4
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 5
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 8
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.",
+          line: 10
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <OuterComponent v-if="showOuterComponent">
+              <InnerComponent v-if="showInnerComponent1" />
+              <InnerComponent v-else />
+            </OuterComponent>
+            <InnerComponent v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <OuterComponent v-if="showOuterComponent">
+              <InnerComponent key="inner-component-1" v-if="showInnerComponent1" />
+              <InnerComponent key="inner-component-2" v-else />
+            </OuterComponent>
+            <InnerComponent v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            OuterComponent,
+            InnerComponent
+          }
+        }
+        </script>
+        `,
+      errors: [
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 5
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.",
+          line: 6
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <ComponentA v-if="foo" />
+            <ComponentA v-else-if="bar" />
+            <ComponentB v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA,
+            ComponentB
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <ComponentA key="component-a-1" v-if="foo" />
+            <ComponentA key="component-a-2" v-else-if="bar" />
+            <ComponentB v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA,
+            ComponentB
+          }
+        }
+        </script>
+        `,
+      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
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <ComponentA v-if="foo" />
+            <ComponentB v-else-if="bar" />
+            <ComponentA v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA,
+            ComponentB
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <ComponentA key="component-a-1" v-if="foo" />
+            <ComponentB v-else-if="bar" />
+            <ComponentA key="component-a-2" v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA,
+            ComponentB
+          }
+        }
+        </script>
+        `,
+      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: 6
+        }
+      ]
+    }
+  ]
+})