Skip to content

Commit e804ca2

Browse files
erindepewmichalsnik
erindepew
authored andcommitted
attribute order linting
1 parent 789a2fc commit e804ca2

File tree

5 files changed

+391
-0
lines changed

5 files changed

+391
-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

+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', 'v-bind'
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+
```

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

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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: undefined,
87+
recommended: false
88+
},
89+
fixable: null,
90+
schema: {
91+
type: 'array',
92+
properties: {
93+
order: {
94+
items: {
95+
type: 'string'
96+
},
97+
maxItems: 10,
98+
minItems: 10
99+
}
100+
}
101+
}
102+
},
103+
create
104+
}

0 commit comments

Comments
 (0)