Skip to content

Commit 61c62e9

Browse files
authored
Add v-for-delimiter-style rule (#1267)
* Add `v-for-delimiter-style` rule * Change rule type to "layout" * Include error columns in tests
1 parent 3a4aa1a commit 61c62e9

File tree

6 files changed

+266
-1
lines changed

6 files changed

+266
-1
lines changed

Diff for: docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ For example:
312312
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
313313
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
314314
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
315+
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: |
315316
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |
316317

317318
### Extension Rules

Diff for: docs/rules/v-for-delimiter-style.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/v-for-delimiter-style
5+
description: enforce `v-for` directive's delimiter style
6+
---
7+
# vue/v-for-delimiter-style
8+
> enforce `v-for` directive's delimiter style
9+
10+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
11+
12+
## :book: Rule Details
13+
14+
This rule enforces which delimiter (`in` or `of`) should be used in `v-for` directives.
15+
16+
<eslint-code-block fix :rules="{'vue/v-for-delimiter-style': ['error']}">
17+
18+
```vue
19+
<template>
20+
<!-- ✓ GOOD -->
21+
<div v-for="x in xs" />
22+
23+
<!-- ✗ BAD -->
24+
<div v-for="x of xs" />
25+
</template>
26+
```
27+
28+
</eslint-code-block>
29+
30+
## :wrench: Options
31+
Default is set to `in`.
32+
33+
```json
34+
{
35+
"vue/v-for-delimiter-style": ["error", "in" | "of"]
36+
}
37+
```
38+
39+
- `"in"` (default) ... requires using `in`.
40+
- `"of"` ... requires using `of`.
41+
42+
### `"of"`
43+
44+
<eslint-code-block fix :rules="{'vue/v-for-delimiter-style': ['error', 'of']}">
45+
46+
```vue
47+
<template>
48+
<!-- ✓ GOOD -->
49+
<div v-for="x of xs" />
50+
51+
<!-- ✗ BAD -->
52+
<div v-for="x in xs" />
53+
</template>
54+
```
55+
56+
</eslint-code-block>
57+
58+
## :books: Further Reading
59+
60+
- [Guide - List Rendering](https://v3.vuejs.org/guide/list.html)
61+
62+
## :mag: Implementation
63+
64+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-for-delimiter-style.js)
65+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-for-delimiter-style.js)

Diff for: lib/configs/no-layout-rules.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
'vue/space-in-parens': 'off',
4242
'vue/space-infix-ops': 'off',
4343
'vue/space-unary-ops': 'off',
44-
'vue/template-curly-spacing': 'off'
44+
'vue/template-curly-spacing': 'off',
45+
'vue/v-for-delimiter-style': 'off'
4546
}
4647
}

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ module.exports = {
146146
'this-in-template': require('./rules/this-in-template'),
147147
'use-v-on-exact': require('./rules/use-v-on-exact'),
148148
'v-bind-style': require('./rules/v-bind-style'),
149+
'v-for-delimiter-style': require('./rules/v-for-delimiter-style'),
149150
'v-on-function-call': require('./rules/v-on-function-call'),
150151
'v-on-style': require('./rules/v-on-style'),
151152
'v-slot-style': require('./rules/v-slot-style'),

Diff for: lib/rules/v-for-delimiter-style.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @fileoverview enforce `v-for` directive's delimiter style
3+
* @author Flo Edelmann
4+
* @copyright 2020 Flo Edelmann. All rights reserved.
5+
* See LICENSE file in root directory for full license.
6+
*/
7+
'use strict'
8+
9+
// ------------------------------------------------------------------------------
10+
// Requirements
11+
// ------------------------------------------------------------------------------
12+
13+
const utils = require('../utils')
14+
15+
// ------------------------------------------------------------------------------
16+
// Rule Definition
17+
// ------------------------------------------------------------------------------
18+
19+
module.exports = {
20+
meta: {
21+
type: 'layout',
22+
docs: {
23+
description: "enforce `v-for` directive's delimiter style",
24+
categories: undefined,
25+
recommended: false,
26+
url: 'https://eslint.vuejs.org/rules/v-for-delimiter-style.html'
27+
},
28+
fixable: 'code',
29+
schema: [{ enum: ['in', 'of'] }]
30+
},
31+
/** @param {RuleContext} context */
32+
create(context) {
33+
const preferredDelimiter =
34+
/** @type {string|undefined} */ (context.options[0]) || 'in'
35+
36+
return utils.defineTemplateBodyVisitor(context, {
37+
/** @param {VForExpression} node */
38+
VForExpression(node) {
39+
const tokenStore =
40+
context.parserServices.getTemplateBodyTokenStore &&
41+
context.parserServices.getTemplateBodyTokenStore()
42+
43+
const delimiterToken = /** @type {Token} */ (tokenStore.getTokenAfter(
44+
node.left.length
45+
? node.left[node.left.length - 1]
46+
: tokenStore.getFirstToken(node),
47+
(token) => token.type !== 'Punctuator' || token.value !== ')'
48+
))
49+
50+
if (delimiterToken.value === preferredDelimiter) {
51+
return
52+
}
53+
54+
context.report({
55+
node,
56+
loc: node.loc,
57+
message: `Expected '{{preferredDelimiter}}' instead of '{{usedDelimiter}}' in 'v-for'.`,
58+
data: {
59+
preferredDelimiter,
60+
usedDelimiter: delimiterToken.value
61+
},
62+
*fix(fixer) {
63+
yield fixer.replaceText(delimiterToken, preferredDelimiter)
64+
}
65+
})
66+
}
67+
})
68+
}
69+
}

Diff for: tests/lib/rules/v-for-delimiter-style.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* @fileoverview enforce `v-for` directive's delimiter style
3+
* @author Flo Edelmann
4+
* @copyright 2020 Flo Edelmann. All rights reserved.
5+
* See LICENSE file in root directory for full license.
6+
*/
7+
'use strict'
8+
9+
// ------------------------------------------------------------------------------
10+
// Requirements
11+
// ------------------------------------------------------------------------------
12+
13+
const RuleTester = require('eslint').RuleTester
14+
const rule = require('../../../lib/rules/v-for-delimiter-style')
15+
16+
// ------------------------------------------------------------------------------
17+
// Tests
18+
// ------------------------------------------------------------------------------
19+
20+
const tester = new RuleTester({
21+
parser: require.resolve('vue-eslint-parser'),
22+
parserOptions: { ecmaVersion: 2015 }
23+
})
24+
25+
tester.run('v-for-delimiter-style', rule, {
26+
valid: [
27+
{
28+
filename: 'test.vue',
29+
code: ''
30+
},
31+
{
32+
filename: 'test.vue',
33+
code: '<template><div v-for="x in xs"></div></template>'
34+
},
35+
{
36+
filename: 'test.vue',
37+
code: '<template><div v-for="x in xs"></div></template>'
38+
},
39+
{
40+
filename: 'test.vue',
41+
code: '<template><div v-for="x in xs"></div></template>'
42+
},
43+
{
44+
filename: 'test.vue',
45+
code: '<template><div v-for="x in xs"></div></template>'
46+
},
47+
{
48+
filename: 'test.vue',
49+
code: '<template><div v-for="x in xs"></div></template>',
50+
options: ['in']
51+
},
52+
{
53+
filename: 'test.vue',
54+
code: '<template><div v-for="x of xs"></div></template>',
55+
options: ['of']
56+
}
57+
],
58+
invalid: [
59+
{
60+
filename: 'test.vue',
61+
code: '<template><div v-for="x of xs"></div></template>',
62+
output: '<template><div v-for="x in xs"></div></template>',
63+
errors: [
64+
{
65+
message: "Expected 'in' instead of 'of' in 'v-for'.",
66+
column: 23
67+
}
68+
]
69+
},
70+
{
71+
filename: 'test.vue',
72+
code: '<template><div v-for="x of xs"></div></template>',
73+
output: '<template><div v-for="x in xs"></div></template>',
74+
errors: [
75+
{
76+
message: "Expected 'in' instead of 'of' in 'v-for'.",
77+
column: 23
78+
}
79+
]
80+
},
81+
{
82+
filename: 'test.vue',
83+
code: '<template><div v-for="x of xs"></div></template>',
84+
output: '<template><div v-for="x in xs"></div></template>',
85+
errors: [
86+
{
87+
message: "Expected 'in' instead of 'of' in 'v-for'.",
88+
column: 23
89+
}
90+
]
91+
},
92+
{
93+
filename: 'test.vue',
94+
code: '<template><div v-for="x of xs"></div></template>',
95+
output: '<template><div v-for="x in xs"></div></template>',
96+
errors: [
97+
{
98+
message: "Expected 'in' instead of 'of' in 'v-for'.",
99+
column: 23
100+
}
101+
]
102+
},
103+
{
104+
filename: 'test.vue',
105+
options: ['in'],
106+
code: '<template><div v-for="x of xs"></div></template>',
107+
output: '<template><div v-for="x in xs"></div></template>',
108+
errors: [
109+
{
110+
message: "Expected 'in' instead of 'of' in 'v-for'.",
111+
column: 23
112+
}
113+
]
114+
},
115+
{
116+
filename: 'test.vue',
117+
options: ['of'],
118+
code: '<template><div v-for="x in xs"></div></template>',
119+
output: '<template><div v-for="x of xs"></div></template>',
120+
errors: [
121+
{
122+
message: "Expected 'of' instead of 'in' in 'v-for'.",
123+
column: 23
124+
}
125+
]
126+
}
127+
]
128+
})

0 commit comments

Comments
 (0)