Skip to content

Commit 1c52bb9

Browse files
authored
Separate rule that report <template v-for key> from no-template-key rule. (#1281)
1 parent dab51e8 commit 1c52bb9

9 files changed

+280
-9
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
187187
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
188188
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
189189
| [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 | |
190+
| [vue/no-v-for-template-key](./no-v-for-template-key.md) | disallow `key` attribute on `<template v-for>` | |
190191
| [vue/no-v-model-argument](./no-v-model-argument.md) | disallow adding an argument to `v-model` used in custom component | |
191192
| [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements | |
192193
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |

docs/rules/no-template-key.md

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ This rule reports the `<template>` elements which have `key` attribute.
2323
<div key="foo"> ... </div>
2424
<template> ... </template>
2525
26+
<!-- It's valid for Vue.js 3.x -->
27+
<template v-for="item in list" :key="item.id"> ... </template>
28+
2629
<!-- ✗ BAD -->
2730
<template key="foo"> ... </template>
2831
<template v-bind:key="bar"> ... </template>
@@ -32,10 +35,20 @@ This rule reports the `<template>` elements which have `key` attribute.
3235

3336
</eslint-code-block>
3437

38+
::: tip Note
39+
This rule does not report keys placed on `<template v-for>`. It's valid for Vue.js 3.x. If you want to report keys placed on `<template v-for>` invalid for Vue.js 2.x, use [vue/no-v-for-template-key] rule.
40+
:::
41+
3542
## :wrench: Options
3643

3744
Nothing.
3845

46+
## :couple: Related Rules
47+
48+
- [vue/no-v-for-template-key]
49+
50+
[vue/no-v-for-template-key]: ./no-v-for-template-key.md
51+
3952
## :books: Further Reading
4053

4154
- [API - Special Attributes - key](https://v3.vuejs.org/api/special-attributes.html#key)

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

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-v-for-template-key
5+
description: disallow `key` attribute on `<template v-for>`
6+
---
7+
# vue/no-v-for-template-key
8+
> disallow `key` attribute on `<template v-for>`
9+
10+
- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
11+
12+
Vue.js disallows `key` attribute on `<template>` elements.
13+
14+
## :book: Rule Details
15+
16+
This rule reports the `<template v-for>` elements which have `key` attribute.
17+
18+
<eslint-code-block :rules="{'vue/no-v-for-template-key': ['error']}">
19+
20+
```vue
21+
<template>
22+
<!-- ✓ GOOD -->
23+
<template v-for="item in list">
24+
<div :key="item.id" />
25+
</template>
26+
27+
<!-- ✗ BAD -->
28+
<template v-for="item in list" :key="item.id">
29+
<div />
30+
</template>
31+
</template>
32+
```
33+
34+
</eslint-code-block>
35+
36+
::: tip Note
37+
If you want to report keys placed on `<template>` without `v-for`, use the [vue/no-template-key] rule.
38+
:::
39+
40+
## :wrench: Options
41+
42+
Nothing.
43+
44+
## :couple: Related Rules
45+
46+
- [vue/no-template-key](./no-template-key.md)
47+
48+
[vue/no-template-key]: ./no-template-key.md
49+
50+
## :books: Further Reading
51+
52+
- [API - Special Attributes - key](https://v3.vuejs.org/api/special-attributes.html#key)
53+
- [API (for v2) - Special Attributes - key](https://vuejs.org/v2/api/#key)
54+
55+
## :mag: Implementation
56+
57+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-for-template-key.js)
58+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-for-template-key.js)

lib/configs/essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module.exports = {
2424
'vue/no-unused-components': 'error',
2525
'vue/no-unused-vars': 'error',
2626
'vue/no-use-v-if-with-v-for': 'error',
27+
'vue/no-v-for-template-key': 'error',
2728
'vue/no-v-model-argument': 'error',
2829
'vue/require-component-is': 'error',
2930
'vue/require-prop-type-constructor': 'error',

lib/index.js

+1
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': require('./rules/no-v-for-template-key'),
112113
'no-v-html': require('./rules/no-v-html'),
113114
'no-v-model-argument': require('./rules/no-v-model-argument'),
114115
'no-watch-after-await': require('./rules/no-watch-after-await'),

lib/rules/no-template-key.js

+18-9
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,31 @@ module.exports = {
2424
url: 'https://eslint.vuejs.org/rules/no-template-key.html'
2525
},
2626
fixable: null,
27-
schema: []
27+
schema: [],
28+
messages: {
29+
disallow:
30+
"'<template>' cannot be keyed. Place the key on real elements instead."
31+
}
2832
},
2933
/** @param {RuleContext} context */
3034
create(context) {
3135
return utils.defineTemplateBodyVisitor(context, {
3236
/** @param {VElement} node */
3337
"VElement[name='template']"(node) {
34-
if (
35-
utils.hasAttribute(node, 'key') ||
36-
utils.hasDirective(node, 'bind', 'key')
37-
) {
38+
const keyNode =
39+
utils.getAttribute(node, 'key') ||
40+
utils.getDirective(node, 'bind', 'key')
41+
if (keyNode) {
42+
if (utils.hasDirective(node, 'for')) {
43+
// It's valid for Vue.js 3.x.
44+
// <template v-for="item in list" :key="item.id"> ... </template>
45+
// see https://github.com/vuejs/vue-next/issues/1734
46+
return
47+
}
3848
context.report({
39-
node,
40-
loc: node.loc,
41-
message:
42-
"'<template>' cannot be keyed. Place the key on real elements instead."
49+
node: keyNode,
50+
loc: keyNode.loc,
51+
messageId: 'disallow'
4352
})
4453
}
4554
}

lib/rules/no-v-for-template-key.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @author Yosuke Ota
3+
*/
4+
'use strict'
5+
6+
const utils = require('../utils')
7+
8+
// ------------------------------------------------------------------------------
9+
// Rule Definition
10+
// ------------------------------------------------------------------------------
11+
12+
module.exports = {
13+
meta: {
14+
type: 'problem',
15+
docs: {
16+
description: 'disallow `key` attribute on `<template v-for>`',
17+
categories: ['essential'],
18+
url: 'https://eslint.vuejs.org/rules/no-v-for-template-key.html'
19+
},
20+
fixable: null,
21+
schema: [],
22+
messages: {
23+
disallow:
24+
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
25+
}
26+
},
27+
/** @param {RuleContext} context */
28+
create(context) {
29+
return utils.defineTemplateBodyVisitor(context, {
30+
/** @param {VDirective} node */
31+
"VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
32+
node
33+
) {
34+
const element = node.parent.parent
35+
const keyNode =
36+
utils.getAttribute(element, 'key') ||
37+
utils.getDirective(element, 'bind', 'key')
38+
if (keyNode) {
39+
context.report({
40+
node: keyNode,
41+
loc: keyNode.loc,
42+
messageId: 'disallow'
43+
})
44+
}
45+
}
46+
})
47+
}
48+
}

tests/lib/rules/no-template-key.js

+38
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ tester.run('no-template-key', rule, {
4242
{
4343
filename: 'test.vue',
4444
code: '<template><div :key="foo"></div></template>'
45+
},
46+
{
47+
filename: 'test.vue',
48+
code:
49+
'<template><template v-for="item in list" :key="item.id"><div /></template></template>'
50+
},
51+
{
52+
filename: 'test.vue',
53+
code:
54+
'<template><template v-for="(item, i) in list" :key="i"><div /></template></template>'
55+
},
56+
{
57+
filename: 'test.vue',
58+
code:
59+
'<template><template v-for="item in list" :key="foo + item.id"><div /></template></template>'
60+
},
61+
{
62+
filename: 'test.vue',
63+
// It is probably not valid, but it works as the Vue.js 3.x compiler.
64+
// We can prevent it with other rules. e.g. vue/require-v-for-key
65+
code:
66+
'<template><template v-for="item in list" key="foo"><div /></template></template>'
4567
}
4668
],
4769
invalid: [
@@ -66,6 +88,22 @@ tester.run('no-template-key', rule, {
6688
errors: [
6789
"'<template>' cannot be keyed. Place the key on real elements instead."
6890
]
91+
},
92+
{
93+
filename: 'test.vue',
94+
code:
95+
'<template><template v-slot="item" :key="item.id"><div /></template></template>',
96+
errors: [
97+
"'<template>' cannot be keyed. Place the key on real elements instead."
98+
]
99+
},
100+
{
101+
filename: 'test.vue',
102+
code:
103+
'<template><template v-for="item in list"><template :key="item.id"><div /></template></template></template>',
104+
errors: [
105+
"'<template>' cannot be keyed. Place the key on real elements instead."
106+
]
69107
}
70108
]
71109
})
+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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')
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', rule, {
23+
valid: [
24+
{
25+
filename: 'test.vue',
26+
code: ''
27+
},
28+
{
29+
filename: 'test.vue',
30+
code: '<template><template></template></template>'
31+
},
32+
{
33+
filename: 'test.vue',
34+
code: '<template><div key="foo"></div></template>'
35+
},
36+
{
37+
filename: 'test.vue',
38+
code: '<template><div v-bind:key="foo"></div></template>'
39+
},
40+
{
41+
filename: 'test.vue',
42+
code: '<template><div :key="foo"></div></template>'
43+
},
44+
{
45+
filename: 'test.vue',
46+
code: '<template><div><template key="foo"></template></div></template>'
47+
},
48+
{
49+
filename: 'test.vue',
50+
code:
51+
'<template><div><template v-bind:key="foo"></template></div></template>'
52+
},
53+
{
54+
filename: 'test.vue',
55+
code: '<template><div><template :key="foo"></template></div></template>'
56+
},
57+
{
58+
filename: 'test.vue',
59+
code:
60+
'<template><template v-slot="item" :key="item.id"><div /></template></template>'
61+
},
62+
{
63+
filename: 'test.vue',
64+
code:
65+
'<template><template v-for="item in list"><template :key="item.id"><div /></template></template></template>'
66+
}
67+
],
68+
invalid: [
69+
{
70+
filename: 'test.vue',
71+
code:
72+
'<template><template v-for="item in list" :key="item.id"><div /></template></template>',
73+
errors: [
74+
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
75+
]
76+
},
77+
{
78+
filename: 'test.vue',
79+
code:
80+
'<template><template v-for="(item, i) in list" :key="i"><div /></template></template>',
81+
errors: [
82+
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
83+
]
84+
},
85+
{
86+
filename: 'test.vue',
87+
code:
88+
'<template><template v-for="item in list" :key="foo + item.id"><div /></template></template>',
89+
errors: [
90+
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
91+
]
92+
},
93+
{
94+
filename: 'test.vue',
95+
code:
96+
'<template><template v-for="item in list" key="foo"><div /></template></template>',
97+
errors: [
98+
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
99+
]
100+
}
101+
]
102+
})

0 commit comments

Comments
 (0)