Skip to content

Commit c3221b8

Browse files
authored
Add vue/no-v-for-template-key-on-child rule (#1289)
* Add `vue/no-v-for-template-key-on-child` rule * Add tests * update * Update docs
1 parent e1366fd commit c3221b8

File tree

7 files changed

+329
-2
lines changed

7 files changed

+329
-2
lines changed

docs/rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
7373
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
7474
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
7575
| [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow use v-if on the same element as v-for | |
76+
| [vue/no-v-for-template-key-on-child](./no-v-for-template-key-on-child.md) | disallow key of `<template v-for>` placed on child elements | |
7677
| [vue/no-watch-after-await](./no-watch-after-await.md) | disallow asynchronously registered `watch` | |
7778
| [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements | |
7879
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-v-for-template-key-on-child
5+
description: disallow key of `<template v-for>` placed on child elements
6+
---
7+
# vue/no-v-for-template-key-on-child
8+
> disallow key of `<template v-for>` placed on child elements
9+
10+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
11+
12+
## :book: Rule Details
13+
14+
This rule reports the key of the `<template v-for>` placed on the child elements.
15+
16+
In Vue.js 3.x, with the support for fragments, the `<template v-for>` key can be placed on the `<template>` tag.
17+
18+
::: warning Note
19+
Do not use with the [vue/no-v-for-template-key] rule for Vue.js 2.x.
20+
This rule conflicts with the [vue/no-v-for-template-key] rule.
21+
:::
22+
23+
<eslint-code-block :rules="{'vue/no-v-for-template-key-on-child': ['error']}">
24+
25+
```vue
26+
<template>
27+
<!-- ✓ GOOD -->
28+
<template v-for="todo in todos" :key="todo">
29+
<Foo />
30+
</template>
31+
32+
<!-- ✗ BAD -->
33+
<template v-for="todo in todos">
34+
<Foo :key="todo" />
35+
</template>
36+
</template>
37+
```
38+
39+
</eslint-code-block>
40+
41+
## :wrench: Options
42+
43+
Nothing.
44+
45+
## :couple: Related Rules
46+
47+
- [vue/no-v-for-template-key]
48+
49+
[vue/no-v-for-template-key]: ./no-v-for-template-key.md
50+
51+
## :mag: Implementation
52+
53+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-for-template-key-on-child.js)
54+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-for-template-key-on-child.js)

docs/rules/no-v-for-template-key.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ description: disallow `key` attribute on `<template v-for>`
99
1010
- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
1111

12-
Vue.js disallows `key` attribute on `<template>` elements.
1312

1413
## :book: Rule Details
1514

1615
This rule reports the `<template v-for>` elements which have `key` attribute.
1716

17+
In Vue.js 2.x, disallows `key` attribute on `<template>` elements.
18+
19+
::: warning Note
20+
Do not use with the [vue/no-v-for-template-key-on-child] rule for Vue.js 3.x.
21+
This rule conflicts with the [vue/no-v-for-template-key-on-child] rule.
22+
:::
23+
1824
<eslint-code-block :rules="{'vue/no-v-for-template-key': ['error']}">
1925

2026
```vue
@@ -43,9 +49,11 @@ Nothing.
4349

4450
## :couple: Related Rules
4551

46-
- [vue/no-template-key](./no-template-key.md)
52+
- [vue/no-template-key]
53+
- [vue/no-v-for-template-key-on-child]
4754

4855
[vue/no-template-key]: ./no-template-key.md
56+
[vue/no-v-for-template-key-on-child]: ./no-v-for-template-key-on-child.md
4957

5058
## :books: Further Reading
5159

lib/configs/vue3-essential.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
'vue/no-unused-components': 'error',
4242
'vue/no-unused-vars': 'error',
4343
'vue/no-use-v-if-with-v-for': 'error',
44+
'vue/no-v-for-template-key-on-child': 'error',
4445
'vue/no-watch-after-await': 'error',
4546
'vue/require-component-is': 'error',
4647
'vue/require-prop-type-constructor': 'error',

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ module.exports = {
109109
'no-useless-concat': require('./rules/no-useless-concat'),
110110
'no-useless-mustaches': require('./rules/no-useless-mustaches'),
111111
'no-useless-v-bind': require('./rules/no-useless-v-bind'),
112+
'no-v-for-template-key-on-child': require('./rules/no-v-for-template-key-on-child'),
112113
'no-v-for-template-key': require('./rules/no-v-for-template-key'),
113114
'no-v-html': require('./rules/no-v-html'),
114115
'no-v-model-argument': require('./rules/no-v-model-argument'),
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @author Yosuke Ota
3+
* This rule is based on X_V_FOR_TEMPLATE_KEY_PLACEMENT error of Vue 3.
4+
* see https://github.com/vuejs/vue-next/blob/b0d01e9db9ffe5781cce5a2d62c8552db3d615b0/packages/compiler-core/src/errors.ts#L70
5+
*/
6+
'use strict'
7+
8+
// ------------------------------------------------------------------------------
9+
// Requirements
10+
// ------------------------------------------------------------------------------
11+
12+
const utils = require('../utils')
13+
14+
// ------------------------------------------------------------------------------
15+
// Helpers
16+
// ------------------------------------------------------------------------------
17+
18+
/**
19+
* Check whether the given attribute is using the variables which are defined by `v-for` directives.
20+
* @param {VDirective} vFor The attribute node of `v-for` to check.
21+
* @param {VDirective} vBindKey The attribute node of `v-bind:key` to check.
22+
* @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives.
23+
*/
24+
function isUsingIterationVar(vFor, vBindKey) {
25+
if (vBindKey.value == null) {
26+
return false
27+
}
28+
const references = vBindKey.value.references
29+
const variables = vFor.parent.parent.variables
30+
return references.some((reference) =>
31+
variables.some(
32+
(variable) =>
33+
variable.id.name === reference.id.name && variable.kind === 'v-for'
34+
)
35+
)
36+
}
37+
38+
// ------------------------------------------------------------------------------
39+
// Rule Definition
40+
// ------------------------------------------------------------------------------
41+
42+
module.exports = {
43+
meta: {
44+
type: 'problem',
45+
docs: {
46+
description:
47+
'disallow key of `<template v-for>` placed on child elements',
48+
categories: ['vue3-essential'],
49+
url: 'https://eslint.vuejs.org/rules/no-v-for-template-key-on-child.html'
50+
},
51+
fixable: null,
52+
schema: [],
53+
messages: {
54+
vForTemplateKeyPlacement:
55+
'`<template v-for>` key should be placed on the `<template>` tag.'
56+
}
57+
},
58+
/** @param {RuleContext} context */
59+
create(context) {
60+
return utils.defineTemplateBodyVisitor(context, {
61+
/** @param {VDirective} node */
62+
"VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
63+
node
64+
) {
65+
const template = node.parent.parent
66+
const vBindKeyOnTemplate = utils.getDirective(template, 'bind', 'key')
67+
if (
68+
vBindKeyOnTemplate &&
69+
isUsingIterationVar(node, vBindKeyOnTemplate)
70+
) {
71+
return
72+
}
73+
74+
for (const child of template.children.filter(utils.isVElement)) {
75+
if (
76+
utils.hasDirective(child, 'if') ||
77+
utils.hasDirective(child, 'else-if') ||
78+
utils.hasDirective(child, 'else') ||
79+
utils.hasDirective(child, 'for')
80+
) {
81+
continue
82+
}
83+
const vBindKeyOnChild = utils.getDirective(child, 'bind', 'key')
84+
if (vBindKeyOnChild && isUsingIterationVar(node, vBindKeyOnChild)) {
85+
context.report({
86+
node: vBindKeyOnChild,
87+
loc: vBindKeyOnChild.loc,
88+
messageId: 'vForTemplateKeyPlacement'
89+
})
90+
}
91+
}
92+
}
93+
})
94+
}
95+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
// ------------------------------------------------------------------------------
7+
// Requirements
8+
// ------------------------------------------------------------------------------
9+
10+
const RuleTester = require('eslint').RuleTester
11+
const rule = require('../../../lib/rules/no-v-for-template-key-on-child')
12+
13+
// ------------------------------------------------------------------------------
14+
// Tests
15+
// ------------------------------------------------------------------------------
16+
17+
const tester = new RuleTester({
18+
parser: require.resolve('vue-eslint-parser'),
19+
parserOptions: { ecmaVersion: 2015 }
20+
})
21+
22+
tester.run('no-v-for-template-key-on-child', rule, {
23+
valid: [
24+
{
25+
filename: 'test.vue',
26+
code: ''
27+
},
28+
{
29+
filename: 'test.vue',
30+
code:
31+
'<template><div><template v-for="x in list"><Foo /></template></div></template>'
32+
},
33+
{
34+
filename: 'test.vue',
35+
code:
36+
'<template><div><template v-for="x in list" :key="x"><Foo /></template></div></template>'
37+
},
38+
{
39+
filename: 'test.vue',
40+
code:
41+
'<template><div><template v-for="x in list" :key="x.id"><Foo :key="x.id" /></template></div></template>'
42+
},
43+
{
44+
filename: 'test.vue',
45+
code:
46+
'<template><div><template v-for="(x, i) in list" :key="i"><Foo :key="x" /></template></div></template>'
47+
},
48+
{
49+
filename: 'test.vue',
50+
code:
51+
'<template><div><template v-for="(x, i) in list"><Foo :key="foo" /></template></div></template>'
52+
},
53+
{
54+
filename: 'test.vue',
55+
code: `
56+
<template>
57+
<div>
58+
<template v-for="x in list">
59+
<Foo v-if="a" :key="x" />
60+
</template>
61+
</div>
62+
</template>`
63+
},
64+
{
65+
filename: 'test.vue',
66+
code: `
67+
<template>
68+
<div>
69+
<template v-for="x in list">
70+
<Foo v-if="a" :key="x.key1" />
71+
<Foo v-else-if="a" :key="x.key2" />
72+
<Foo v-else :key="x.key3" />
73+
<Foo v-for="y in list" :key="x.key4" />
74+
</template>
75+
</div>
76+
</template>`
77+
}
78+
],
79+
invalid: [
80+
{
81+
filename: 'test.vue',
82+
code:
83+
'<template><div><template v-for="x in list"><Foo :key="x" /></template></div></template>',
84+
errors: [
85+
{
86+
message:
87+
'`<template v-for>` key should be placed on the `<template>` tag.',
88+
column: 49
89+
}
90+
]
91+
},
92+
{
93+
filename: 'test.vue',
94+
code:
95+
'<template><div><template v-for="x in list"><Foo :key="x.id" /></template></div></template>',
96+
errors: [
97+
'`<template v-for>` key should be placed on the `<template>` tag.'
98+
]
99+
},
100+
{
101+
filename: 'test.vue',
102+
code:
103+
'<template><div><template v-for="x in list" :key="foo"><Foo :key="x.id" /></template></div></template>',
104+
errors: [
105+
'`<template v-for>` key should be placed on the `<template>` tag.'
106+
]
107+
},
108+
{
109+
filename: 'test.vue',
110+
code:
111+
'<template><div><template v-for="x in list" :key><Foo :key="x.id" /></template></div></template>',
112+
errors: [
113+
'`<template v-for>` key should be placed on the `<template>` tag.'
114+
]
115+
},
116+
{
117+
filename: 'test.vue',
118+
code:
119+
'<template><div><template v-for="x in list" :key><div /><Foo :key="x.id" /></template></div></template>',
120+
errors: [
121+
'`<template v-for>` key should be placed on the `<template>` tag.'
122+
]
123+
},
124+
{
125+
filename: 'test.vue',
126+
code: `<template><div><template v-for="x in list" :key><Foo :key="'foo' + x.id" /><Bar :key="'bar' + x.id" /></template></div></template>`,
127+
errors: [
128+
'`<template v-for>` key should be placed on the `<template>` tag.',
129+
'`<template v-for>` key should be placed on the `<template>` tag.'
130+
]
131+
},
132+
{
133+
filename: 'test.vue',
134+
code: `
135+
<template>
136+
<div>
137+
<template v-for="x in list">
138+
<Foo v-if="a" :key="x.key1" />
139+
<Foo v-else-if="a" :key="x.key2" />
140+
<Foo v-else :key="x.key3" />
141+
<Foo v-for="y in list" :key="x.key4" />
142+
<Foo :key="x.error1" />
143+
<div :key="x.error2" />
144+
<slot :key="x.error3" ></slot>
145+
</template>
146+
</div>
147+
</template>`,
148+
errors: [
149+
{
150+
message:
151+
'`<template v-for>` key should be placed on the `<template>` tag.',
152+
line: 9
153+
},
154+
{
155+
message:
156+
'`<template v-for>` key should be placed on the `<template>` tag.',
157+
line: 10
158+
},
159+
{
160+
message:
161+
'`<template v-for>` key should be placed on the `<template>` tag.',
162+
line: 11
163+
}
164+
]
165+
}
166+
]
167+
})

0 commit comments

Comments
 (0)