Skip to content

Commit c360057

Browse files
authored
⭐️New: Add vue/component-tags-order rule (#763)
* ⭐️New: Add `vue/component-tags-order` rule * Upgrade vue-eslint-parser@^7.0.0 * Use parserServices.getDocumentFragment
1 parent 8d7cadf commit c360057

File tree

5 files changed

+408
-0
lines changed

5 files changed

+408
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ For example:
146146
| [vue/camelcase](./camelcase.md) | enforce camelcase naming convention | |
147147
| [vue/comma-dangle](./comma-dangle.md) | require or disallow trailing commas | :wrench: |
148148
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
149+
| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
149150
| [vue/dot-location](./dot-location.md) | enforce consistent newlines before and after dots | :wrench: |
150151
| [vue/eqeqeq](./eqeqeq.md) | require the use of `===` and `!==` | :wrench: |
151152
| [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: |

docs/rules/component-tags-order.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/component-tags-order
5+
description: enforce order of component top-level elements
6+
---
7+
# vue/component-tags-order
8+
> enforce order of component top-level elements
9+
10+
## :book: Rule Details
11+
12+
This rule warns about the order of the `<script>`, `<template>` & `<style>` tags.
13+
14+
## :wrench: Options
15+
16+
```json
17+
{
18+
"vue/component-tags-order": ["error", {
19+
"order": ["script", "template", "style"]
20+
}]
21+
}
22+
```
23+
24+
- `order` (`string[]`) ... The order of top-level element names. default `["script", "template", "style"]`.
25+
26+
### `{ "order": ["script", "template", "style"] }` (default)
27+
28+
<eslint-code-block :rules="{'vue/component-tags-order': ['error']}">
29+
30+
```vue
31+
<!-- ✓ GOOD -->
32+
<script>/* ... */</script>
33+
<template>...</template>
34+
<style>/* ... */</style>
35+
```
36+
37+
</eslint-code-block>
38+
39+
<eslint-code-block :rules="{'vue/component-tags-order': ['error']}">
40+
41+
```vue
42+
<!-- ✗ BAD -->
43+
<style>/* ... */</style>
44+
<script>/* ... */</script>
45+
<template>...</template>
46+
```
47+
48+
</eslint-code-block>
49+
50+
### `{ "order": ["template", "script", "style"] }`
51+
52+
<eslint-code-block :rules="{'vue/component-tags-order': ['error', { 'order': ['template', 'script', 'style'] }]}">
53+
54+
```vue
55+
<!-- ✓ GOOD -->
56+
<template>...</template>
57+
<script>/* ... */</script>
58+
<style>/* ... */</style>
59+
```
60+
61+
</eslint-code-block>
62+
63+
### `{ "order": ["docs", "template", "script", "style"] }`
64+
65+
<eslint-code-block :rules="{'vue/component-tags-order': ['error', { 'order': ['docs', 'template', 'script', 'style'] }]}">
66+
67+
```vue
68+
<!-- ✓ GOOD -->
69+
<docs> documents </docs>
70+
<template>...</template>
71+
<script>/* ... */</script>
72+
<style>/* ... */</style>
73+
```
74+
75+
</eslint-code-block>
76+
77+
<eslint-code-block :rules="{'vue/component-tags-order': ['error', { 'order': ['docs', 'template', 'script', 'style'] }]}">
78+
79+
```vue
80+
<!-- ✗ BAD -->
81+
<template>...</template>
82+
<script>/* ... */</script>
83+
<docs> documents </docs>
84+
<style>/* ... */</style>
85+
```
86+
87+
</eslint-code-block>
88+
89+
## :books: Further reading
90+
91+
- [Style guide - Single-file component top-level element order](https://vuejs.org/v2/style-guide/#Single-file-component-top-level-element-order-recommended)
92+
93+
## :mag: Implementation
94+
95+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-tags-order.js)
96+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-tags-order.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'comma-dangle': require('./rules/comma-dangle'),
1818
'comment-directive': require('./rules/comment-directive'),
1919
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
20+
'component-tags-order': require('./rules/component-tags-order'),
2021
'dot-location': require('./rules/dot-location'),
2122
'eqeqeq': require('./rules/eqeqeq'),
2223
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),

lib/rules/component-tags-order.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @author Yosuke Ota
3+
* issue https://github.com/vuejs/eslint-plugin-vue/issues/140
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
const DEFAULT_ORDER = Object.freeze(['script', 'template', 'style'])
14+
15+
// ------------------------------------------------------------------------------
16+
// Rule Definition
17+
// ------------------------------------------------------------------------------
18+
19+
module.exports = {
20+
meta: {
21+
type: 'suggestion',
22+
docs: {
23+
description: 'enforce order of component top-level elements',
24+
category: undefined,
25+
url: 'https://eslint.vuejs.org/rules/component-tags-order.html'
26+
},
27+
fixable: null,
28+
schema: {
29+
type: 'array',
30+
properties: {
31+
order: {
32+
type: 'array'
33+
}
34+
}
35+
},
36+
messages: {
37+
unexpected: 'The <{{name}}> should be above the <{{firstUnorderedName}}> on line {{line}}.'
38+
}
39+
},
40+
create (context) {
41+
const order = (context.options[0] && context.options[0].order) || DEFAULT_ORDER
42+
const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()
43+
44+
function getTopLevelHTMLElements () {
45+
if (documentFragment) {
46+
return documentFragment.children
47+
}
48+
return []
49+
}
50+
51+
function report (element, firstUnorderedElement) {
52+
context.report({
53+
node: element,
54+
loc: element.loc,
55+
messageId: 'unexpected',
56+
data: {
57+
name: element.name,
58+
firstUnorderedName: firstUnorderedElement.name,
59+
line: firstUnorderedElement.loc.start.line
60+
}
61+
})
62+
}
63+
64+
return utils.defineTemplateBodyVisitor(
65+
context,
66+
{},
67+
{
68+
Program (node) {
69+
if (utils.hasInvalidEOF(node)) {
70+
return
71+
}
72+
const elements = getTopLevelHTMLElements()
73+
74+
elements.forEach((element, index) => {
75+
const expectedIndex = order.indexOf(element.name)
76+
if (expectedIndex < 0) {
77+
return
78+
}
79+
const firstUnordered = elements
80+
.slice(0, index)
81+
.filter(e => expectedIndex < order.indexOf(e.name))
82+
.sort(
83+
(e1, e2) => order.indexOf(e1.name) - order.indexOf(e2.name)
84+
)[0]
85+
if (firstUnordered) {
86+
report(element, firstUnordered)
87+
}
88+
})
89+
}
90+
}
91+
)
92+
}
93+
}

0 commit comments

Comments
 (0)