Skip to content

Commit dfb2b58

Browse files
author
erindepew
committed
attribute order linting
1 parent 9f83c5a commit dfb2b58

File tree

7 files changed

+449
-1
lines changed

7 files changed

+449
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
202202

203203
| | Rule ID | Description |
204204
|:---|:--------|:------------|
205+
| | [vue/attributes-order](./docs/rules/attributes-order.md) | enforce order of attributes |
205206
| :wrench: | [vue/html-closing-bracket-newline](./docs/rules/html-closing-bracket-newline.md) | require or disallow a line break before tag's closing brackets |
206207
| :wrench: | [vue/html-closing-bracket-spacing](./docs/rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets |
207208
| :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |

docs/rules/attributes-order.md

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# enforce order of attributes (vue/attributes-order)
2+
3+
## :book: Rule Details
4+
5+
This rule aims to enfore ordering of component attributes. The default order is specified in the [Vue styleguide](https://vuejs.org/v2/style-guide/#Element-attribute-order-recommended) and is:
6+
- DEFINITION
7+
ex: 'is'
8+
- LIST_RENDERING
9+
ex: 'v-for item in items'
10+
- CONDITIONALS
11+
ex: 'v-if', 'v-else-if', 'v-else', 'v-show', 'v-cloak'
12+
- RENDER_MODIFIERS
13+
ex: 'v-once', 'v-pre'
14+
- GLOBAL
15+
ex: 'id'
16+
- UNIQUE
17+
ex: 'ref', 'key', 'slot'
18+
- BINDING
19+
ex: 'v-model'
20+
- OTHER_ATTR
21+
ex: 'customProp="foo"'
22+
- EVENTS
23+
ex: '@click="functionCall"'
24+
- CONTENT
25+
ex: 'v-text', 'v-html'
26+
27+
:+1: Examples of **correct** code`:
28+
29+
```html
30+
<div
31+
is="header"
32+
v-for="item in items"
33+
v-if="!visible"
34+
v-once id="uniqueID"
35+
ref="header"
36+
v-model="headerData"
37+
myProp="prop"
38+
@click="functionCall"
39+
v-text="textContent">
40+
</div>
41+
```
42+
43+
```html
44+
<div
45+
v-for="item in items"
46+
v-if="!visible"
47+
propOne="prop"
48+
propTwo="prop"
49+
propThree="prop"
50+
@click="functionCall"
51+
v-text="textContent">
52+
</div>
53+
```
54+
55+
```html
56+
<div
57+
propOne="prop"
58+
propTwo="prop"
59+
propThree="prop">
60+
</div>
61+
```
62+
63+
:-1: Examples of **incorrect** code`:
64+
65+
```html
66+
<div
67+
ref="header"
68+
v-for="item in items"
69+
v-once id="uniqueID"
70+
v-model="headerData"
71+
myProp="prop"
72+
v-if="!visible"
73+
is="header"
74+
@click="functionCall"
75+
v-text="textContent">
76+
</div>
77+
```
78+
79+
### `order`
80+
81+
Specify custom order of attribute groups
82+
83+
:+1: Examples of **correct** code with custom order`:
84+
85+
```html
86+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT', 'DEFINITION'] }] -->
87+
<div
88+
propOne="prop"
89+
propTwo="prop"
90+
is="header">
91+
</div>
92+
```
93+
94+
```html
95+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
96+
<div
97+
ref="header"
98+
is="header"
99+
propOne="prop"
100+
propTwo="prop">
101+
</div>
102+
```
103+
104+
:-1: Examples of **incorrect** code with custom order`:
105+
106+
```html
107+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
108+
<div
109+
ref="header"
110+
propOne="prop"
111+
is="header">
112+
</div>
113+
```

docs/rules/script-indent.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# enforce consistent indentation in `<script>` (script-indent)
1+
# enforce consistent indentation in `<script>` (vue/script-indent)
22

33
- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
44

lib/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
module.exports = {
99
rules: {
1010
'attribute-hyphenation': require('./rules/attribute-hyphenation'),
11+
'attributes-order': require('./rules/attributes-order'),
1112
'comment-directive': require('./rules/comment-directive'),
1213
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
1314
'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
@@ -39,6 +40,7 @@ module.exports = {
3940
'require-v-for-key': require('./rules/require-v-for-key'),
4041
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
4142
'return-in-computed-property': require('./rules/return-in-computed-property'),
43+
'script-indent': require('./rules/script-indent'),
4244
'this-in-template': require('./rules/this-in-template'),
4345
'v-bind-style': require('./rules/v-bind-style'),
4446
'v-on-style': require('./rules/v-on-style'),

lib/recommended-rules.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* IMPORTANT!
3+
* This file has been automatically generated,
4+
* in order to update it's content execute "npm run update"
5+
*/
6+
module.exports = {
7+
"vue/attribute-hyphenation": "error",
8+
"vue/attributes-order": "off",
9+
"vue/html-end-tags": "error",
10+
"vue/html-indent": "error",
11+
"vue/html-quotes": "error",
12+
"vue/html-self-closing": "error",
13+
"vue/jsx-uses-vars": "error",
14+
"vue/max-attributes-per-line": "error",
15+
"vue/mustache-interpolation-spacing": "error",
16+
"vue/name-property-casing": "error",
17+
"vue/no-async-in-computed-properties": "error",
18+
"vue/no-confusing-v-for-v-if": "error",
19+
"vue/no-dupe-keys": "error",
20+
"vue/no-duplicate-attributes": "error",
21+
"vue/no-multi-spaces": "error",
22+
"vue/no-parsing-error": "error",
23+
"vue/no-reserved-keys": "error",
24+
"vue/no-shared-component-data": "error",
25+
"vue/no-side-effects-in-computed-properties": "error",
26+
"vue/no-template-key": "error",
27+
"vue/no-textarea-mustache": "error",
28+
"vue/no-unused-vars": "error",
29+
"vue/order-in-components": "error",
30+
"vue/require-component-is": "error",
31+
"vue/require-default-prop": "error",
32+
"vue/require-prop-types": "error",
33+
"vue/require-render-return": "error",
34+
"vue/require-v-for-key": "error",
35+
"vue/require-valid-default-prop": "error",
36+
"vue/return-in-computed-property": "error",
37+
"vue/this-in-template": "error",
38+
"vue/v-bind-style": "error",
39+
"vue/v-on-style": "error",
40+
"vue/valid-template-root": "error",
41+
"vue/valid-v-bind": "error",
42+
"vue/valid-v-cloak": "error",
43+
"vue/valid-v-else-if": "error",
44+
"vue/valid-v-else": "error",
45+
"vue/valid-v-for": "error",
46+
"vue/valid-v-html": "error",
47+
"vue/valid-v-if": "error",
48+
"vue/valid-v-model": "error",
49+
"vue/valid-v-on": "error",
50+
"vue/valid-v-once": "error",
51+
"vue/valid-v-pre": "error",
52+
"vue/valid-v-show": "error",
53+
"vue/valid-v-text": "error"
54+
}

lib/rules/attributes-order.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @fileoverview enforce ordering of attributes
3+
* @author Erin Depew
4+
*/
5+
'use strict'
6+
const utils = require('../utils')
7+
8+
// ------------------------------------------------------------------------------
9+
// Rule Definition
10+
// ------------------------------------------------------------------------------
11+
12+
13+
function getAttributeType(name, isDirective) {
14+
if (isDirective) {
15+
if (name === 'for') {
16+
return 'LIST_RENDERING'
17+
} else if (name === 'if' || name === 'else-if' || name === 'else' || name === 'show' || name === 'cloak') {
18+
return 'CONDITIONALS'
19+
} else if (name === 'pre' || name === 'once') {
20+
return 'RENDER_MODIFIERS'
21+
} else if (name === 'model' || name === 'bind') {
22+
return 'BINDING'
23+
} else if (name === 'on') {
24+
return 'EVENTS'
25+
} else if (name === 'html' || name === 'text') {
26+
return 'CONTENT'
27+
}
28+
}
29+
else {
30+
if (name === 'is') {
31+
return 'DEFINITION'
32+
} else if (name === 'id') {
33+
return 'GLOBAL'
34+
} else if (name === 'ref' || name === 'key' || name === 'slot') {
35+
return 'UNIQUE'
36+
} else {
37+
return 'OTHER_ATTR'
38+
}
39+
}
40+
}
41+
function getPosition (attribute, attributeOrder) {
42+
const attributeType = getAttributeType(attribute.key.name, attribute.directive)
43+
return attributeOrder.indexOf(attributeType)
44+
}
45+
46+
function create (context) {
47+
const sourceCode = context.getSourceCode()
48+
let attributeOrder = ['DEFINITION', 'LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT']
49+
if (context.options[0] && context.options[0].order) {
50+
attributeOrder = context.options[0].order
51+
}
52+
let currentPosition
53+
let previousNode
54+
55+
function reportIssue (node, previousNode) {
56+
const currentNode = sourceCode.getText(node.key)
57+
const prevNode = sourceCode.getText(previousNode.key)
58+
context.report({
59+
node: node.key,
60+
loc: node.loc,
61+
message: `Attribute "${currentNode}" should go before "${prevNode}".`,
62+
data: {
63+
currentNode
64+
}
65+
})
66+
}
67+
68+
return utils.defineTemplateBodyVisitor(context, {
69+
'VStartTag' () {
70+
currentPosition = -1
71+
previousNode = null
72+
},
73+
'VAttribute' (node) {
74+
if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributeOrder))) {
75+
currentPosition = getPosition(node, attributeOrder)
76+
previousNode = node
77+
} else {
78+
reportIssue(node, previousNode)
79+
}
80+
}
81+
})
82+
}
83+
84+
module.exports = {
85+
meta: {
86+
docs: {
87+
description: 'enforce order of attributes',
88+
category: undefined,
89+
recommended: false
90+
},
91+
fixable: null,
92+
schema: {
93+
type: 'array',
94+
properties: {
95+
order: {
96+
items: {
97+
type: 'string'
98+
},
99+
maxItems: 10,
100+
minItems: 10
101+
}
102+
}
103+
}
104+
},
105+
create
106+
}

0 commit comments

Comments
 (0)