diff --git a/lib/utils/index.js b/lib/utils/index.js index b61b52926..6b7df723f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -18,6 +18,8 @@ * @typedef {import('vue-eslint-parser').AST.ESLintFunctionExpression} FunctionExpression * @typedef {import('vue-eslint-parser').AST.ESLintBlockStatement} BlockStatement * @typedef {import('vue-eslint-parser').AST.ESLintNode} ESLintNode + * + * @typedef {import('vue-eslint-parser').AST.ESLintArrowFunctionExpression | { type: 'ArrowFunctionExpression', body: BlockStatement | Expression } } ArrowFunctionExpression */ /** @@ -37,6 +39,11 @@ /** * @typedef { {key: string, value: BlockStatement} } ComponentComputedProperty */ +/** + * @typedef { { name: string, groupName: string, node: Literal | TemplateLiteral } } ComponentArrayPropertyData + * @typedef { { name: string, groupName: string, node: Identifier | Literal | TemplateLiteral } } ComponentObjectPropertyData + * @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData + */ // ------------------------------------------------------------------------------ // Helpers @@ -799,14 +806,17 @@ module.exports = { }, /** * Return generator with all properties - * @param {ASTNode} node Node to check - * @param {Set} groups Name of parent group + * @param {ObjectExpression} node Node to check + * @param {Set} groups Name of parent group + * @returns {IterableIterator} */ * iterateProperties (node, groups) { - const nodes = node.properties.filter(p => p.type === 'Property' && groups.has(getStaticPropertyName(p.key))) - for (const item of nodes) { - const name = getStaticPropertyName(item.key) - if (!name) continue + for (const item of node.properties) { + if (item.type !== 'Property') { + continue + } + const name = getStaticPropertyName(item) + if (!name || !groups.has(name)) continue if (item.value.type === 'ArrayExpression') { yield * this.iterateArrayExpression(item.value, name) @@ -822,40 +832,66 @@ module.exports = { /** * Return generator with all elements inside ArrayExpression - * @param {ASTNode} node Node to check + * @param {ArrayExpression} node Node to check * @param {string} groupName Name of parent group + * @returns {IterableIterator} */ * iterateArrayExpression (node, groupName) { assert(node.type === 'ArrayExpression') for (const item of node.elements) { - const name = getStaticPropertyName(item) - if (name) { - const obj = { name, groupName, node: item } - yield obj + if (item.type === 'Literal' || item.type === 'TemplateLiteral') { + const name = getStaticPropertyName(item) + if (name) { + yield { name, groupName, node: item } + } } } }, /** * Return generator with all elements inside ObjectExpression - * @param {ASTNode} node Node to check + * @param {ObjectExpression} node Node to check * @param {string} groupName Name of parent group + * @returns {IterableIterator} */ * iterateObjectExpression (node, groupName) { assert(node.type === 'ObjectExpression') + let usedGetter for (const item of node.properties) { - const name = getStaticPropertyName(item) - if (name) { - const obj = { name, groupName, node: item.key } - yield obj + if (item.type === 'Property') { + const key = item.key + if (key.type === 'Identifier' || key.type === 'Literal' || key.type === 'TemplateLiteral') { + const name = getStaticPropertyName(item) + if (name) { + if (item.kind === 'set') { + // find getter pair + if (!usedGetter) { usedGetter = new Set() } + if (node.properties.some(item2 => { + if (item2.type === 'Property' && item2.kind === 'get' && !usedGetter.has(item2)) { + const getterName = getStaticPropertyName(item2) + if (getterName === name) { + usedGetter.add(item2) + return true + } + } + return false + })) { + // has getter pair + continue + } + } + yield { name, groupName, node: key } + } + } } } }, /** * Return generator with all elements inside FunctionExpression - * @param {ASTNode} node Node to check + * @param {FunctionExpression} node Node to check * @param {string} groupName Name of parent group + * @returns {IterableIterator} */ * iterateFunctionExpression (node, groupName) { assert(node.type === 'FunctionExpression') @@ -870,19 +906,21 @@ module.exports = { /** * Return generator with all elements inside ArrowFunctionExpression - * @param {ASTNode} node Node to check + * @param {ArrowFunctionExpression} node Node to check * @param {string} groupName Name of parent group + * @returns {IterableIterator} */ * iterateArrowFunctionExpression (node, groupName) { assert(node.type === 'ArrowFunctionExpression') - if (node.body.type === 'BlockStatement') { - for (const item of node.body.body) { + const body = node.body + if (body.type === 'BlockStatement') { + for (const item of body.body) { if (item.type === 'ReturnStatement' && item.argument && item.argument.type === 'ObjectExpression') { yield * this.iterateObjectExpression(item.argument, groupName) } } - } else if (node.body.type === 'ObjectExpression') { - yield * this.iterateObjectExpression(node.body, groupName) + } else if (body.type === 'ObjectExpression') { + yield * this.iterateObjectExpression(body, groupName) } }, diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js index 94c1eaf98..d85655669 100644 --- a/tests/lib/rules/no-dupe-keys.js +++ b/tests/lib/rules/no-dupe-keys.js @@ -15,7 +15,12 @@ const RuleTester = require('eslint').RuleTester // Tests // ------------------------------------------------------------------------------ -const ruleTester = new RuleTester() +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } +}) ruleTester.run('no-dupe-keys', rule, { valid: [ { @@ -41,8 +46,7 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` }, { filename: 'test.vue', @@ -72,8 +76,7 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` }, { @@ -99,8 +102,7 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` }, { @@ -124,8 +126,7 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` }, { @@ -161,8 +162,7 @@ ruleTester.run('no-dupe-keys', rule, { } }, } - `, - parserOptions: { ecmaVersion: 2018, sourceType: 'module' } + ` }, { @@ -206,8 +206,7 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 2018, sourceType: 'module' } + ` }, { @@ -243,8 +242,7 @@ ruleTester.run('no-dupe-keys', rule, { } }, } - `, - parserOptions: { ecmaVersion: 2018, sourceType: 'module' } + ` }, { @@ -278,8 +276,7 @@ ruleTester.run('no-dupe-keys', rule, { ...dat }), } - `, - parserOptions: { ecmaVersion: 2018, sourceType: 'module' } + ` }, { @@ -298,8 +295,7 @@ ruleTester.run('no-dupe-keys', rule, { propA: String } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` }, { filename: 'test.vue', @@ -331,8 +327,59 @@ ruleTester.run('no-dupe-keys', rule, { } } } - `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + data () { + return { + get foo() { + return foo + }, + set foo(v) { + foo = v + } + } + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + data () { + return { + set foo(v) { + foo = v + }, + get foo() { + return foo + } + } + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + data () { + return { + get foo() { + return foo + }, + bar, + set foo(v) { + foo = v + } + } + } + } + ` } ], @@ -364,7 +411,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -411,7 +457,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -449,7 +494,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -494,7 +538,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -527,7 +570,6 @@ ruleTester.run('no-dupe-keys', rule, { }) `, options: [{ groups: ['foo'] }], - parserOptions: { ecmaVersion: 6 }, errors: [ { message: "Duplicated key 'bar'.", @@ -553,7 +595,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -577,7 +618,6 @@ ruleTester.run('no-dupe-keys', rule, { } } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -599,7 +639,6 @@ ruleTester.run('no-dupe-keys', rule, { }) } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -621,7 +660,6 @@ ruleTester.run('no-dupe-keys', rule, { }) } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -643,7 +681,6 @@ ruleTester.run('no-dupe-keys', rule, { }) } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -665,7 +702,6 @@ ruleTester.run('no-dupe-keys', rule, { }) } `, - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'foo'.", @@ -686,7 +722,6 @@ ruleTester.run('no-dupe-keys', rule, { }) `, options: [{ groups: ['foo'] }], - parserOptions: { ecmaVersion: 6 }, errors: [ { message: "Duplicated key 'bar'.", @@ -707,13 +742,115 @@ ruleTester.run('no-dupe-keys', rule, { }) `, options: [{ groups: ['foo'] }], - parserOptions: { ecmaVersion: 6, sourceType: 'module' }, errors: [ { message: "Duplicated key 'bar'.", line: 7 } ] + }, + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + data () { + return { + get foo() { + return foo + }, + set foo(v) { + foo = v + } + } + } + } + `, + errors: [ + { + message: "Duplicated key 'foo'.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + data () { + return { + set foo(v) {}, + get foo() {} + } + } + } + `, + errors: [ + { + message: "Duplicated key 'foo'.", + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + data () { + return { + set foo(v) {} + } + } + } + `, + errors: [ + { + message: "Duplicated key 'foo'.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + export default { + data () { + return { + get foo() {}, + set foo(v) {}, + get foo() {}, + } + } + } + `, + errors: [ + { + message: "Duplicated key 'foo'.", + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + export default { + data () { + return { + get foo() {}, + set foo(v) {}, + set foo(v) {}, + } + } + } + `, + errors: [ + { + message: "Duplicated key 'foo'.", + line: 7 + } + ] } ] })