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

Issue:
Detect props destructure rename in `vue/no-dupe-keys`
**What rule do you want to change?**

`vue/no-dupe-keys`

**Does this change cause the rule to produce more or fewer warnings?**

Fewer

**How will the change be implemented? (New option, new default behavior, etc.)?**

I think it should be the default behavior.

**Please provide some example code that this change will affect:**

Here is a simple example (BTW, removing the intermediate `withPrefix` computed makes the error to disappear on `const messages = ...`).

```vue
<template>
  <div v-for="message of messages" :key="message">{{ message }}</div>
</template>

<script lang="ts" setup>
import { computed } from 'vue';

const { messages: _messages } = defineProps<{ messages: string[] }>()

const withPrefix = computed(() => _messages.map(message => `→ ${message}`))

const messages = computed(() => withPrefix.value.map(message => `${message} ←`))
</script>

```

**What does the rule currently do for this code?**

> ESLint: Duplicate key 'messages'. May cause name collision in script or template tag. (vue/no-dupe-keys)

**What will the rule do after it's changed?**

No warning nor error since the destructured prop is intentionally renamed.




Discussion:

Patch:
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index 01b85d9f5..ecfa787cf 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -58,6 +58,33 @@ function isInsideInitializer(node, references) {
   )
 }
 
+/**
+ * Collects all renamed props from a pattern
+ * @param {Pattern | null} pattern - The destructuring pattern
+ * @returns {Set<string>} - Set of prop names that have been renamed
+ */
+function collectRenamedProps(pattern) {
+  const renamedProps = new Set()
+
+  if (!pattern || pattern.type !== 'ObjectPattern') {
+    return renamedProps
+  }
+
+  for (const prop of pattern.properties) {
+    if (prop.type !== 'Property') continue
+
+    if (
+      prop.key.type === 'Identifier' &&
+      prop.value.type === 'Identifier' &&
+      prop.key.name !== prop.value.name
+    ) {
+      renamedProps.add(prop.key.name)
+    }
+  }
+
+  return renamedProps
+}
+
 module.exports = {
   meta: {
     type: 'problem',
@@ -115,9 +142,15 @@ module.exports = {
             node
           ]
 
+          const renamedProps = collectRenamedProps(propsNode)
+
           for (const prop of props) {
             if (!prop.propName) continue
 
+            if (renamedProps.has(prop.propName)) {
+              continue
+            }
+
             const variable = findVariable(
               utils.getScope(context, node),
               prop.propName


Test patch:
diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js
index 124442ec2..2df95908c 100644
--- a/tests/lib/rules/no-dupe-keys.js
+++ b/tests/lib/rules/no-dupe-keys.js
@@ -466,7 +466,7 @@ ruleTester.run('no-dupe-keys', rule, {
     {
       filename: 'test.vue',
       code: `
-      <script setup></script>
+      <script setup>
       const {foo,bar} = defineProps(['foo', 'bar'])
       </script>
       `,
@@ -475,7 +475,7 @@ ruleTester.run('no-dupe-keys', rule, {
     {
       filename: 'test.vue',
       code: `
-      <script setup></script>
+      <script setup>
       const {foo=42,bar='abc'} = defineProps(['foo', 'bar'])
       </script>
       `,
@@ -500,6 +500,17 @@ ruleTester.run('no-dupe-keys', rule, {
         parser: require('vue-eslint-parser'),
         parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
       }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      const { foo: renamedFoo, bar: renamedBar } = defineProps(['foo', 'bar'])
+      const foo = 42
+      const bar = 'hello'
+      </script>
+      `,
+      languageOptions: { parser: require('vue-eslint-parser') }
     }
   ],
 
@@ -1105,6 +1116,24 @@ ruleTester.run('no-dupe-keys', rule, {
           line: 5
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      const { foo: renamedFoo } = defineProps(['foo', 'bar'])
+      const foo = 'foo'
+      const bar = 'bar'
+      </script>
+      `,
+      languageOptions: { parser: require('vue-eslint-parser') },
+      errors: [
+        {
+          message:
+            "Duplicate key 'bar'. May cause name collision in script or template tag.",
+          line: 5
+        }
+      ]
     }
   ]
 })