diff --git a/README.md b/README.md index 303f0b341..b01a5b728 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ The `--fix` option on the command line automatically fixes problems reported by | :wrench: | [no-multi-spaces](./docs/rules/no-multi-spaces.md) | disallow multiple spaces | | :wrench: | [v-bind-style](./docs/rules/v-bind-style.md) | enforce `v-bind` directive style | | :wrench: | [v-on-style](./docs/rules/v-on-style.md) | enforce `v-on` directive style | - +| :wrench: | [attribute-order](./docs/rules/attribute-order.md) | enforce alphabetical ordering of properties and prioritizing vue-specific attributes | ### Variables diff --git a/docs/rules/attribute-order.md b/docs/rules/attribute-order.md new file mode 100644 index 000000000..47ec90965 --- /dev/null +++ b/docs/rules/attribute-order.md @@ -0,0 +1,77 @@ +# enforce alphabetical ordering of properties and prioritizing vue-specific attributes (attribute-order) + +## Rule Details + +:+1: Examples of **correct** code`: + +```html + +
+
+ +``` + +```html + +
+
+ +``` + +```html + +
+
+ +``` + +:-1: Examples of **incorrect** code`: + +```html + +
+
+ +``` + +```html + +
+
+ +``` + +```html + +
+
+ +``` diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js index c8f6abf1c..5aaa0d7f4 100644 --- a/lib/recommended-rules.js +++ b/lib/recommended-rules.js @@ -5,6 +5,7 @@ */ module.exports = { "vue/attribute-hyphenation": "off", + "vue/attribute-order": "off", "vue/html-end-tags": "off", "vue/html-no-self-closing": "off", "vue/html-quotes": "off", diff --git a/lib/rules/attribute-order.js b/lib/rules/attribute-order.js new file mode 100644 index 000000000..d7b589cc9 --- /dev/null +++ b/lib/rules/attribute-order.js @@ -0,0 +1,76 @@ +/** + * @fileoverview enforce alphabetical ordering of properties and prioritizing vue-specific attributes + * @author Erin Depew + */ +'use strict' +const utils = require('../utils') + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function getName (attribute) { + if (!attribute.directive) { + return attribute.key.name + } + if (attribute.key.name === 'bind') { + return attribute.key.argument || null + } + if (attribute.directive) { + return '@' + attribute.key.argument + } + return null +} + +function create (context) { + const sourceCode = context.getSourceCode() + let attributeList + let previousNode + + function reportIssue (node, previousNode, name) { + const currentNode = sourceCode.getText(node.key) + const prevNode = sourceCode.getText(previousNode.key) + + context.report({ + node: node.key, + loc: node.loc, + message: `Attribute ${currentNode} must go before ${prevNode}.`, + data: { + currentNode + } + }) + } + + return utils.defineTemplateBodyVisitor(context, { + 'VStartTag' () { + attributeList = [] + }, + 'VAttribute' (node) { + const name = getName(node) + + if (attributeList.length && attributeList[attributeList.length - 1] < name && name) { + attributeList.push(name) + previousNode = node + } else if (attributeList.length === 0 && name) { + attributeList.push(name) + previousNode = node + } else { + reportIssue(node, previousNode, name) + } + } + }) +} + +module.exports = { + meta: { + docs: { + description: 'enforce alphabetical ordering of properties and prioritizing vue-specific attributes', + category: 'Fill me in', + recommended: false + }, + fixable: null, + schema: [] + }, + + create +} diff --git a/tests/lib/rules/attribute-order.js b/tests/lib/rules/attribute-order.js new file mode 100644 index 000000000..e7884fcc3 --- /dev/null +++ b/tests/lib/rules/attribute-order.js @@ -0,0 +1,118 @@ +/** + * @fileoverview enforce alphabetical ordering of properties and prioritizing vue-specific attributes + * @author Erin Depew + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/attribute-order') + +var RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var tester = new RuleTester({ + parser: 'vue-eslint-parser', + parserOptions: { ecmaVersion: 2015 } +}) + +tester.run('attribute-order', rule, { + valid: [ + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + } + ], + + invalid: [ + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute aria-test must go before data-id.', + type: 'VIdentifier' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute v-if must go before myProp.', + type: 'VDirectiveKey' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute v-if must go before :myProp.', + type: 'VDirectiveKey' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute aria-test must go before :class.', + type: 'VIdentifier' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute :class must go before myProp.', + type: 'VDirectiveKey' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute @click must go before :class.', + type: 'VDirectiveKey' + }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ + message: 'Attribute @click must go before v-for.', + type: 'VDirectiveKey' + }] + } + ] +})