Skip to content

Commit 33ce7aa

Browse files
erindepewmichalsnik
erindepew
authored andcommitted
New: Add vue/attribute-order rule (#209)
1 parent ccb6488 commit 33ce7aa

File tree

5 files changed

+521
-0
lines changed

5 files changed

+521
-0
lines changed

README.md

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

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

docs/rules/attributes-order.md

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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', 'v-bind', ':property="foo"'
20+
- OTHER_ATTR
21+
ex: 'customProp="foo"'
22+
- EVENTS
23+
ex: '@click="functionCall"', 'v-on="event"'
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
35+
id="uniqueID"
36+
ref="header"
37+
v-model="headerData"
38+
myProp="prop"
39+
@click="functionCall"
40+
v-text="textContent">
41+
</div>
42+
```
43+
44+
```html
45+
<div
46+
v-for="item in items"
47+
v-if="!visible"
48+
propOne="prop"
49+
propTwo="prop"
50+
propThree="prop"
51+
@click="functionCall"
52+
v-text="textContent">
53+
</div>
54+
```
55+
56+
```html
57+
<div
58+
propOne="prop"
59+
propTwo="prop"
60+
propThree="prop">
61+
</div>
62+
```
63+
64+
:-1: Examples of **incorrect** code`:
65+
66+
```html
67+
<div
68+
ref="header"
69+
v-for="item in items"
70+
v-once id="uniqueID"
71+
v-model="headerData"
72+
myProp="prop"
73+
v-if="!visible"
74+
is="header"
75+
@click="functionCall"
76+
v-text="textContent">
77+
</div>
78+
```
79+
80+
### `order`
81+
82+
Specify custom order of attribute groups
83+
84+
:+1: Examples of **correct** code with custom order`:
85+
86+
```html
87+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT', 'DEFINITION'] }] -->
88+
<div
89+
propOne="prop"
90+
propTwo="prop"
91+
is="header">
92+
</div>
93+
```
94+
95+
```html
96+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
97+
<div
98+
ref="header"
99+
is="header"
100+
propOne="prop"
101+
propTwo="prop">
102+
</div>
103+
```
104+
105+
:-1: Examples of **incorrect** code with custom order`:
106+
107+
```html
108+
<!-- 'vue/attribute-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'DEFINITION', 'OTHER_ATTR', 'EVENTS', 'CONTENT'] }] -->
109+
<div
110+
ref="header"
111+
propOne="prop"
112+
is="header">
113+
</div>
114+
```

lib/index.js

+1
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'),

lib/rules/attributes-order.js

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

0 commit comments

Comments
 (0)