From c47f4e3a7500e4d06c797fe0bd455be9bf2a642f Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Wed, 24 May 2023 17:11:31 +0900 Subject: [PATCH 1/3] Fix false positives for toRef props in `vue/no-dupe-keys` rule --- lib/rules/no-dupe-keys.js | 80 +++++++++++++++++++++++++++++++++ tests/lib/rules/no-dupe-keys.js | 64 +++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index f959f4b66..a2fd37b10 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -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', @@ -63,12 +105,23 @@ module.exports = { }), utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(node, props) { + const propsNode = getPropsPattern(node) + const propReferences = [ + ...(propsNode ? extractReferences(propsNode, context) : []), + node + ] + for (const prop of props) { if (!prop.propName) continue const variable = findVariable(context.getScope(), prop.propName) if (!variable || variable.defs.length === 0) continue + for (const def of variable.defs) { + if (def.type !== 'Variable') continue + if (isInsideInitializer(def.node, propReferences)) return + } + context.report({ node: variable.defs[0].node, message: "Duplicated key '{{name}}'.", @@ -80,5 +133,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 [] + } } } diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js index e1294032f..4c775f055 100644 --- a/tests/lib/rules/no-dupe-keys.js +++ b/tests/lib/rules/no-dupe-keys.js @@ -416,6 +416,68 @@ ruleTester.run('no-dupe-keys', rule, { `, parser: require.resolve('vue-eslint-parser'), parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + const {foo,bar} = defineProps(['foo', 'bar']) + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + const {foo=42,bar='abc'} = defineProps(['foo', 'bar']) + + `, + parser: require.resolve('vue-eslint-parser') } ], @@ -912,7 +974,7 @@ ruleTester.run('no-dupe-keys', rule, { `, parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } } ], From 56c897d258917809b7d0b5f2f5dbb065d6fb8664 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 26 May 2023 16:57:35 +0900 Subject: [PATCH 3/3] fix and add test case --- lib/rules/no-dupe-keys.js | 12 ++++++++---- tests/lib/rules/no-dupe-keys.js | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index a2fd37b10..b007631f4 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -107,7 +107,7 @@ module.exports = { onDefinePropsEnter(node, props) { const propsNode = getPropsPattern(node) const propReferences = [ - ...(propsNode ? extractReferences(propsNode, context) : []), + ...(propsNode ? extractReferences(propsNode) : []), node ] @@ -117,9 +117,13 @@ module.exports = { const variable = findVariable(context.getScope(), prop.propName) if (!variable || variable.defs.length === 0) continue - for (const def of variable.defs) { - if (def.type !== 'Variable') continue - if (isInsideInitializer(def.node, propReferences)) return + if ( + variable.defs.some((def) => { + if (def.type !== 'Variable') return false + return isInsideInitializer(def.node, propReferences) + }) + ) { + continue } context.report({ diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js index 1cfd0c9d8..29384f567 100644 --- a/tests/lib/rules/no-dupe-keys.js +++ b/tests/lib/rules/no-dupe-keys.js @@ -1046,6 +1046,23 @@ ruleTester.run('no-dupe-keys', rule, { line: 9 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + errors: [ + { + message: "Duplicated key 'bar'.", + line: 5 + } + ] } ] })