diff --git a/docs/rules/no-duplicate-attributes.md b/docs/rules/no-duplicate-attributes.md index 12c8d0335..3cf820de6 100644 --- a/docs/rules/no-duplicate-attributes.md +++ b/docs/rules/no-duplicate-attributes.md @@ -27,9 +27,20 @@ This rule reports duplicate attributes. ## :wrench: Options -Nothing. +`allowCoexistClass` - Enables [`v-bind:class`] directive can coexist with the plain `class` attribute. +`allowCoexistStyle` - Enables [`v-bind:style`] directive can coexist with the plain `style` attribute. + +``` +'vue/name-property-casing': [2, { + allowCoexistClass: Boolean // default: true + allowCoexistStyle: Boolean, // default: true +}] +``` ## TODO: `
` `parse5` remove duplicate attributes on the tokenization phase. Needs investigation to check. + +[`v-bind:class`]: https://vuejs.org/v2/guide/class-and-style.html +[`v-bind:style`]: https://vuejs.org/v2/guide/class-and-style.html diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js index 65a566fb1..53b918ed1 100644 --- a/lib/rules/no-duplicate-attributes.js +++ b/lib/rules/no-duplicate-attributes.js @@ -37,11 +37,24 @@ function getName (attribute) { * @returns {Object} AST event handlers. */ function create (context) { - const names = new Set() + const options = context.options[0] || {} + const allowCoexistStyle = options.allowCoexistStyle !== false + const allowCoexistClass = options.allowCoexistClass !== false + + const directiveNames = new Set() + const attributeNames = new Set() + + function isDuplicate (name, isDirective) { + if ((allowCoexistStyle && name === 'style') || (allowCoexistClass && name === 'class')) { + return isDirective ? directiveNames.has(name) : attributeNames.has(name) + } + return directiveNames.has(name) || attributeNames.has(name) + } utils.registerTemplateBodyVisitor(context, { 'VStartTag' () { - names.clear() + directiveNames.clear() + attributeNames.clear() }, 'VAttribute' (node) { const name = getName(node) @@ -49,7 +62,7 @@ function create (context) { return } - if (names.has(name)) { + if (isDuplicate(name, node.directive)) { context.report({ node, loc: node.loc, @@ -57,7 +70,12 @@ function create (context) { data: { name } }) } - names.add(name) + + if (node.directive) { + directiveNames.add(name) + } else { + attributeNames.add(name) + } } }) @@ -77,6 +95,19 @@ module.exports = { recommended: false }, fixable: false, - schema: [] + + schema: [ + { + type: 'object', + properties: { + allowCoexistClass: { + type: 'boolean' + }, + allowCoexistStyle: { + type: 'boolean' + } + } + } + ] } } diff --git a/tests/lib/rules/no-duplicate-attributes.js b/tests/lib/rules/no-duplicate-attributes.js index ba47a7eec..03cd71c74 100644 --- a/tests/lib/rules/no-duplicate-attributes.js +++ b/tests/lib/rules/no-duplicate-attributes.js @@ -34,14 +34,32 @@ tester.run('no-duplicate-attributes', rule, { { filename: 'test.vue', code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '', + options: [{ allowCoexistStyle: true }] + }, + { + filename: 'test.vue', + code: '', + options: [{ allowCoexistStyle: true }] } ], invalid: [ - // { - // filename: "test.vue", - // code: "", - // errors: ["Duplicate attribute 'foo'."], - // }, + // { + // filename: 'test.vue', + // code: '', + // errors: ["Duplicate attribute 'foo'."] + // }, { filename: 'test.vue', code: '', @@ -51,6 +69,30 @@ tester.run('no-duplicate-attributes', rule, { filename: 'test.vue', code: '', errors: ["Duplicate attribute 'foo'."] + }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'style'."], + options: [{ allowCoexistStyle: false }] + }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'class'."], + options: [{ allowCoexistClass: false }] + }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'style'."], + options: [{ allowCoexistStyle: false }] + }, + { + filename: 'test.vue', + code: '', + errors: ["Duplicate attribute 'class'."], + options: [{ allowCoexistClass: false }] } ] })