diff --git a/docs/rules/no-duplicate-attributes.md b/docs/rules/no-duplicate-attributes.md index 000b67414..6bed7521d 100644 --- a/docs/rules/no-duplicate-attributes.md +++ b/docs/rules/no-duplicate-attributes.md @@ -45,27 +45,37 @@ This rule reports duplicate attributes. { "vue/no-duplicate-attributes": ["error", { "allowCoexistClass": true, - "allowCoexistStyle": true + "allowCoexistStyle": true, + "allowedDOMProps": ["name"] }] } ``` - `allowCoexistClass` (`boolean`) ... Enables [`v-bind:class`] directive can coexist with the plain `class` attribute. Default is `true`. - `allowCoexistStyle` (`boolean`) ... Enables [`v-bind:style`] directive can coexist with the plain `style` attribute. Default is `true`. +- `allowedDOMProps` (`Array`) ... Enables [`v-bind:attribute-name.prop`] directive to coexist with [`v-bind:attribute-name`] or [`attribute-name`] attribute. Default is `[]` [`v-bind:class`]: https://v3.vuejs.org/guide/class-and-style.html [`v-bind:style`]: https://v3.vuejs.org/guide/class-and-style.html +[`v-bind:prop`]: https://vuejs.org/api/built-in-directives.html#v-bind ### `"allowCoexistClass": false, "allowCoexistStyle": false` +### `allowedDOMProps: ['name', 'id']` + ```vue + ``` diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js index 4a3309b0c..7366d5618 100644 --- a/lib/rules/no-duplicate-attributes.js +++ b/lib/rules/no-duplicate-attributes.js @@ -35,6 +35,17 @@ function getName(attribute) { return null } +/** + * Check if the given attribute node has prop modifier. + * @param {VAttribute | VDirective} attribute The attribute node to get. + * @returns {boolean} The name of the attribute. + */ +function isDirectiveWithProp(attribute) { + if (!attribute.directive) return false + + return attribute.key.modifiers.some((m) => m.name === 'prop') +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -58,6 +69,9 @@ module.exports = { }, allowCoexistStyle: { type: 'boolean' + }, + allowedDOMProps: { + type: 'array' } }, additionalProperties: false @@ -69,17 +83,27 @@ module.exports = { const options = context.options[0] || {} const allowCoexistStyle = options.allowCoexistStyle !== false const allowCoexistClass = options.allowCoexistClass !== false + const allowedList = options.allowedDOMProps || [] /** @type {Set} */ const directiveNames = new Set() /** @type {Set} */ const attributeNames = new Set() + const directiveWithModifiers = new Set() + /** * @param {string} name * @param {boolean} isDirective + * @param {boolean} hasModifier */ - function isDuplicate(name, isDirective) { + function isDuplicate(name, isDirective, hasModifier) { + if (allowedList.includes(name) && hasModifier) { + return isDirective + ? directiveWithModifiers.has(name) + : attributeNames.has(name) + } + if ( (allowCoexistStyle && name === 'style') || (allowCoexistClass && name === 'class') @@ -100,7 +124,9 @@ module.exports = { return } - if (isDuplicate(name, node.directive)) { + const hasPropModifer = isDirectiveWithProp(node) + + if (isDuplicate(name, node.directive, hasPropModifer)) { context.report({ node, loc: node.loc, @@ -109,7 +135,9 @@ module.exports = { }) } - if (node.directive) { + if (hasPropModifer) { + directiveWithModifiers.add(name) + } else if (node.directive) { directiveNames.add(name) } else { attributeNames.add(name) diff --git a/tests/lib/rules/no-duplicate-attributes.js b/tests/lib/rules/no-duplicate-attributes.js index baa79dc98..9cb387410 100644 --- a/tests/lib/rules/no-duplicate-attributes.js +++ b/tests/lib/rules/no-duplicate-attributes.js @@ -60,14 +60,19 @@ tester.run('no-duplicate-attributes', rule, { { filename: 'test.vue', code: '' + }, + { + filename: 'test.vue', + code: '', + options: [{ allowedDOMProps: ['name', 'id'] }] } ], invalid: [ - // { - // filename: 'test.vue', - // code: '', - // errors: ["Duplicate attribute 'foo'."] - // }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'foo'."] + }, { filename: 'test.vue', code: '', @@ -101,6 +106,12 @@ tester.run('no-duplicate-attributes', rule, { code: '', errors: ["Duplicate attribute 'class'."], options: [{ allowCoexistClass: false }] + }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'id'."], + options: [{ allowedDOMProps: ['name'] }] } ] })