Skip to content

Commit 25fcb9b

Browse files
authored
Fix false positives for toRef props in vue/no-dupe-keys rule (#2189)
1 parent 15f7032 commit 25fcb9b

File tree

2 files changed

+182
-1
lines changed

2 files changed

+182
-1
lines changed

lib/rules/no-dupe-keys.js

+84
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,48 @@ const utils = require('../utils')
1616
/** @type {GroupName[]} */
1717
const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup']
1818

19+
/**
20+
* Gets the props pattern node from given `defineProps()` node
21+
* @param {CallExpression} node
22+
* @returns {Pattern|null}
23+
*/
24+
function getPropsPattern(node) {
25+
let target = node
26+
if (
27+
target.parent &&
28+
target.parent.type === 'CallExpression' &&
29+
target.parent.arguments[0] === target &&
30+
target.parent.callee.type === 'Identifier' &&
31+
target.parent.callee.name === 'withDefaults'
32+
) {
33+
target = target.parent
34+
}
35+
36+
if (
37+
!target.parent ||
38+
target.parent.type !== 'VariableDeclarator' ||
39+
target.parent.init !== target
40+
) {
41+
return null
42+
}
43+
return target.parent.id
44+
}
45+
46+
/**
47+
* Checks whether the initialization of the given variable declarator node contains one of the references.
48+
* @param {VariableDeclarator} node
49+
* @param {ESNode[]} references
50+
*/
51+
function isInsideInitializer(node, references) {
52+
const init = node.init
53+
if (!init) {
54+
return false
55+
}
56+
return references.some(
57+
(id) => init.range[0] <= id.range[0] && id.range[1] <= init.range[1]
58+
)
59+
}
60+
1961
module.exports = {
2062
meta: {
2163
type: 'problem',
@@ -63,12 +105,27 @@ module.exports = {
63105
}),
64106
utils.defineScriptSetupVisitor(context, {
65107
onDefinePropsEnter(node, props) {
108+
const propsNode = getPropsPattern(node)
109+
const propReferences = [
110+
...(propsNode ? extractReferences(propsNode) : []),
111+
node
112+
]
113+
66114
for (const prop of props) {
67115
if (!prop.propName) continue
68116

69117
const variable = findVariable(context.getScope(), prop.propName)
70118
if (!variable || variable.defs.length === 0) continue
71119

120+
if (
121+
variable.defs.some((def) => {
122+
if (def.type !== 'Variable') return false
123+
return isInsideInitializer(def.node, propReferences)
124+
})
125+
) {
126+
continue
127+
}
128+
72129
context.report({
73130
node: variable.defs[0].node,
74131
message: "Duplicated key '{{name}}'.",
@@ -80,5 +137,32 @@ module.exports = {
80137
}
81138
})
82139
)
140+
141+
/**
142+
* Extracts references from the given node.
143+
* @param {Pattern} node
144+
* @returns {Identifier[]} References
145+
*/
146+
function extractReferences(node) {
147+
if (node.type === 'Identifier') {
148+
const variable = findVariable(context.getScope(), node)
149+
if (!variable) {
150+
return []
151+
}
152+
return variable.references.map((ref) => ref.identifier)
153+
}
154+
if (node.type === 'ObjectPattern') {
155+
return node.properties.flatMap((prop) =>
156+
extractReferences(prop.type === 'Property' ? prop.value : prop)
157+
)
158+
}
159+
if (node.type === 'AssignmentPattern') {
160+
return extractReferences(node.left)
161+
}
162+
if (node.type === 'RestElement') {
163+
return extractReferences(node.argument)
164+
}
165+
return []
166+
}
83167
}
84168
}

tests/lib/rules/no-dupe-keys.js

+98-1
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,86 @@ ruleTester.run('no-dupe-keys', rule, {
416416
`,
417417
parser: require.resolve('vue-eslint-parser'),
418418
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
419+
},
420+
{
421+
filename: 'test.vue',
422+
code: `
423+
<script setup>
424+
const props = defineProps(['foo', 'bar'])
425+
const { foo, bar } = props
426+
</script>
427+
`,
428+
parser: require.resolve('vue-eslint-parser')
429+
},
430+
{
431+
filename: 'test.vue',
432+
code: `
433+
<script setup>
434+
const props = defineProps(['foo', 'bar'])
435+
const foo = props.foo
436+
const bar = props.bar
437+
</script>
438+
`,
439+
parser: require.resolve('vue-eslint-parser')
440+
},
441+
{
442+
filename: 'test.vue',
443+
code: `
444+
<script setup>
445+
import {toRefs} from 'vue'
446+
const props = defineProps(['foo', 'bar'])
447+
const { foo, bar } = toRefs(props)
448+
</script>
449+
`,
450+
parser: require.resolve('vue-eslint-parser')
451+
},
452+
{
453+
filename: 'test.vue',
454+
code: `
455+
<script setup>
456+
import {toRef} from 'vue'
457+
const props = defineProps(['foo', 'bar'])
458+
const foo = toRef(props, 'foo')
459+
const bar = toRef(props, 'bar')
460+
</script>
461+
`,
462+
parser: require.resolve('vue-eslint-parser')
463+
},
464+
{
465+
filename: 'test.vue',
466+
code: `
467+
<script setup></script>
468+
const {foo,bar} = defineProps(['foo', 'bar'])
469+
</script>
470+
`,
471+
parser: require.resolve('vue-eslint-parser')
472+
},
473+
{
474+
filename: 'test.vue',
475+
code: `
476+
<script setup></script>
477+
const {foo=42,bar='abc'} = defineProps(['foo', 'bar'])
478+
</script>
479+
`,
480+
parser: require.resolve('vue-eslint-parser')
481+
},
482+
{
483+
filename: 'test.vue',
484+
code: `
485+
<script setup lang="ts">
486+
const props = withDefaults(
487+
defineProps<{
488+
foo?: string | number
489+
}>(),
490+
{
491+
foo: "Foo",
492+
}
493+
);
494+
const foo = props.foo
495+
</script>
496+
`,
497+
parser: require.resolve('vue-eslint-parser'),
498+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
419499
}
420500
],
421501

@@ -912,7 +992,7 @@ ruleTester.run('no-dupe-keys', rule, {
912992
<script setup>
913993
import { Foo } from './Foo.vue';
914994
import baz from './baz';
915-
995+
916996
defineProps({
917997
foo: String,
918998
bar: String,
@@ -966,6 +1046,23 @@ ruleTester.run('no-dupe-keys', rule, {
9661046
line: 9
9671047
}
9681048
]
1049+
},
1050+
{
1051+
filename: 'test.vue',
1052+
code: `
1053+
<script setup>
1054+
const props = defineProps(['foo', 'bar'])
1055+
const { foo } = props
1056+
const bar = 42
1057+
</script>
1058+
`,
1059+
parser: require.resolve('vue-eslint-parser'),
1060+
errors: [
1061+
{
1062+
message: "Duplicated key 'bar'.",
1063+
line: 5
1064+
}
1065+
]
9691066
}
9701067
]
9711068
})

0 commit comments

Comments
 (0)