Skip to content

Commit

Permalink
Fix false positives for toRef props in vue/no-dupe-keys rule (#2189)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored May 26, 2023
1 parent 15f7032 commit 25fcb9b
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 1 deletion.
84 changes: 84 additions & 0 deletions lib/rules/no-dupe-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,48 @@ const utils = require('../utils')
/** @type {GroupName[]} */
const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup']

/**
* Gets the props pattern node from given `defineProps()` node
* @param {CallExpression} node
* @returns {Pattern|null}
*/
function getPropsPattern(node) {
let target = node
if (
target.parent &&
target.parent.type === 'CallExpression' &&
target.parent.arguments[0] === target &&
target.parent.callee.type === 'Identifier' &&
target.parent.callee.name === 'withDefaults'
) {
target = target.parent
}

if (
!target.parent ||
target.parent.type !== 'VariableDeclarator' ||
target.parent.init !== target
) {
return null
}
return target.parent.id
}

/**
* Checks whether the initialization of the given variable declarator node contains one of the references.
* @param {VariableDeclarator} node
* @param {ESNode[]} references
*/
function isInsideInitializer(node, references) {
const init = node.init
if (!init) {
return false
}
return references.some(
(id) => init.range[0] <= id.range[0] && id.range[1] <= init.range[1]
)
}

module.exports = {
meta: {
type: 'problem',
Expand Down Expand Up @@ -63,12 +105,27 @@ module.exports = {
}),
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
const propsNode = getPropsPattern(node)
const propReferences = [
...(propsNode ? extractReferences(propsNode) : []),
node
]

for (const prop of props) {
if (!prop.propName) continue

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

if (
variable.defs.some((def) => {
if (def.type !== 'Variable') return false
return isInsideInitializer(def.node, propReferences)
})
) {
continue
}

context.report({
node: variable.defs[0].node,
message: "Duplicated key '{{name}}'.",
Expand All @@ -80,5 +137,32 @@ module.exports = {
}
})
)

/**
* Extracts references from the given node.
* @param {Pattern} node
* @returns {Identifier[]} References
*/
function extractReferences(node) {
if (node.type === 'Identifier') {
const variable = findVariable(context.getScope(), node)
if (!variable) {
return []
}
return variable.references.map((ref) => ref.identifier)
}
if (node.type === 'ObjectPattern') {
return node.properties.flatMap((prop) =>
extractReferences(prop.type === 'Property' ? prop.value : prop)
)
}
if (node.type === 'AssignmentPattern') {
return extractReferences(node.left)
}
if (node.type === 'RestElement') {
return extractReferences(node.argument)
}
return []
}
}
}
99 changes: 98 additions & 1 deletion tests/lib/rules/no-dupe-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,86 @@ ruleTester.run('no-dupe-keys', rule, {
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps(['foo', 'bar'])
const { foo, bar } = props
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps(['foo', 'bar'])
const foo = props.foo
const bar = props.bar
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup>
import {toRefs} from 'vue'
const props = defineProps(['foo', 'bar'])
const { foo, bar } = toRefs(props)
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup>
import {toRef} from 'vue'
const props = defineProps(['foo', 'bar'])
const foo = toRef(props, 'foo')
const bar = toRef(props, 'bar')
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup></script>
const {foo,bar} = defineProps(['foo', 'bar'])
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup></script>
const {foo=42,bar='abc'} = defineProps(['foo', 'bar'])
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = withDefaults(
defineProps<{
foo?: string | number
}>(),
{
foo: "Foo",
}
);
const foo = props.foo
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
}
],

Expand Down Expand Up @@ -912,7 +992,7 @@ ruleTester.run('no-dupe-keys', rule, {
<script setup>
import { Foo } from './Foo.vue';
import baz from './baz';
defineProps({
foo: String,
bar: String,
Expand Down Expand Up @@ -966,6 +1046,23 @@ ruleTester.run('no-dupe-keys', rule, {
line: 9
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const props = defineProps(['foo', 'bar'])
const { foo } = props
const bar = 42
</script>
`,
parser: require.resolve('vue-eslint-parser'),
errors: [
{
message: "Duplicated key 'bar'.",
line: 5
}
]
}
]
})

0 comments on commit 25fcb9b

Please sign in to comment.