Skip to content

Commit

Permalink
Separate rule that report <template v-for key> from no-template-key r…
Browse files Browse the repository at this point in the history
…ule. (#1281)
  • Loading branch information
ota-meshi committed Aug 28, 2020
1 parent dab51e8 commit 1c52bb9
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -187,6 +187,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
| [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 | |
| [vue/no-v-for-template-key](./no-v-for-template-key.md) | disallow `key` attribute on `<template v-for>` | |
| [vue/no-v-model-argument](./no-v-model-argument.md) | disallow adding an argument to `v-model` used in custom component | |
| [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements | |
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
Expand Down
13 changes: 13 additions & 0 deletions docs/rules/no-template-key.md
Expand Up @@ -23,6 +23,9 @@ This rule reports the `<template>` elements which have `key` attribute.
<div key="foo"> ... </div>
<template> ... </template>
<!-- It's valid for Vue.js 3.x -->
<template v-for="item in list" :key="item.id"> ... </template>
<!-- ✗ BAD -->
<template key="foo"> ... </template>
<template v-bind:key="bar"> ... </template>
Expand All @@ -32,10 +35,20 @@ This rule reports the `<template>` elements which have `key` attribute.

</eslint-code-block>

::: tip Note
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.
:::

## :wrench: Options

Nothing.

## :couple: Related Rules

- [vue/no-v-for-template-key]

[vue/no-v-for-template-key]: ./no-v-for-template-key.md

## :books: Further Reading

- [API - Special Attributes - key](https://v3.vuejs.org/api/special-attributes.html#key)
Expand Down
58 changes: 58 additions & 0 deletions docs/rules/no-v-for-template-key.md
@@ -0,0 +1,58 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-v-for-template-key
description: disallow `key` attribute on `<template v-for>`
---
# vue/no-v-for-template-key
> disallow `key` attribute on `<template v-for>`
- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.

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

## :book: Rule Details

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

<eslint-code-block :rules="{'vue/no-v-for-template-key': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<template v-for="item in list">
<div :key="item.id" />
</template>
<!-- ✗ BAD -->
<template v-for="item in list" :key="item.id">
<div />
</template>
</template>
```

</eslint-code-block>

::: tip Note
If you want to report keys placed on `<template>` without `v-for`, use the [vue/no-template-key] rule.
:::

## :wrench: Options

Nothing.

## :couple: Related Rules

- [vue/no-template-key](./no-template-key.md)

[vue/no-template-key]: ./no-template-key.md

## :books: Further Reading

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

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-for-template-key.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-for-template-key.js)
1 change: 1 addition & 0 deletions lib/configs/essential.js
Expand Up @@ -24,6 +24,7 @@ module.exports = {
'vue/no-unused-components': 'error',
'vue/no-unused-vars': 'error',
'vue/no-use-v-if-with-v-for': 'error',
'vue/no-v-for-template-key': 'error',
'vue/no-v-model-argument': 'error',
'vue/require-component-is': 'error',
'vue/require-prop-type-constructor': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -109,6 +109,7 @@ module.exports = {
'no-useless-concat': require('./rules/no-useless-concat'),
'no-useless-mustaches': require('./rules/no-useless-mustaches'),
'no-useless-v-bind': require('./rules/no-useless-v-bind'),
'no-v-for-template-key': require('./rules/no-v-for-template-key'),
'no-v-html': require('./rules/no-v-html'),
'no-v-model-argument': require('./rules/no-v-model-argument'),
'no-watch-after-await': require('./rules/no-watch-after-await'),
Expand Down
27 changes: 18 additions & 9 deletions lib/rules/no-template-key.js
Expand Up @@ -24,22 +24,31 @@ module.exports = {
url: 'https://eslint.vuejs.org/rules/no-template-key.html'
},
fixable: null,
schema: []
schema: [],
messages: {
disallow:
"'<template>' cannot be keyed. Place the key on real elements instead."
}
},
/** @param {RuleContext} context */
create(context) {
return utils.defineTemplateBodyVisitor(context, {
/** @param {VElement} node */
"VElement[name='template']"(node) {
if (
utils.hasAttribute(node, 'key') ||
utils.hasDirective(node, 'bind', 'key')
) {
const keyNode =
utils.getAttribute(node, 'key') ||
utils.getDirective(node, 'bind', 'key')
if (keyNode) {
if (utils.hasDirective(node, 'for')) {
// It's valid for Vue.js 3.x.
// <template v-for="item in list" :key="item.id"> ... </template>
// see https://github.com/vuejs/vue-next/issues/1734
return
}
context.report({
node,
loc: node.loc,
message:
"'<template>' cannot be keyed. Place the key on real elements instead."
node: keyNode,
loc: keyNode.loc,
messageId: 'disallow'
})
}
}
Expand Down
48 changes: 48 additions & 0 deletions lib/rules/no-v-for-template-key.js
@@ -0,0 +1,48 @@
/**
* @author Yosuke Ota
*/
'use strict'

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow `key` attribute on `<template v-for>`',
categories: ['essential'],
url: 'https://eslint.vuejs.org/rules/no-v-for-template-key.html'
},
fixable: null,
schema: [],
messages: {
disallow:
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
}
},
/** @param {RuleContext} context */
create(context) {
return utils.defineTemplateBodyVisitor(context, {
/** @param {VDirective} node */
"VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
node
) {
const element = node.parent.parent
const keyNode =
utils.getAttribute(element, 'key') ||
utils.getDirective(element, 'bind', 'key')
if (keyNode) {
context.report({
node: keyNode,
loc: keyNode.loc,
messageId: 'disallow'
})
}
}
})
}
}
38 changes: 38 additions & 0 deletions tests/lib/rules/no-template-key.js
Expand Up @@ -42,6 +42,28 @@ tester.run('no-template-key', rule, {
{
filename: 'test.vue',
code: '<template><div :key="foo"></div></template>'
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list" :key="item.id"><div /></template></template>'
},
{
filename: 'test.vue',
code:
'<template><template v-for="(item, i) in list" :key="i"><div /></template></template>'
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list" :key="foo + item.id"><div /></template></template>'
},
{
filename: 'test.vue',
// It is probably not valid, but it works as the Vue.js 3.x compiler.
// We can prevent it with other rules. e.g. vue/require-v-for-key
code:
'<template><template v-for="item in list" key="foo"><div /></template></template>'
}
],
invalid: [
Expand All @@ -66,6 +88,22 @@ tester.run('no-template-key', rule, {
errors: [
"'<template>' cannot be keyed. Place the key on real elements instead."
]
},
{
filename: 'test.vue',
code:
'<template><template v-slot="item" :key="item.id"><div /></template></template>',
errors: [
"'<template>' cannot be keyed. Place the key on real elements instead."
]
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list"><template :key="item.id"><div /></template></template></template>',
errors: [
"'<template>' cannot be keyed. Place the key on real elements instead."
]
}
]
})
102 changes: 102 additions & 0 deletions tests/lib/rules/no-v-for-template-key.js
@@ -0,0 +1,102 @@
/**
* @author Yosuke Ota
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-v-for-template-key')

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2015 }
})

tester.run('no-v-for-template-key', rule, {
valid: [
{
filename: 'test.vue',
code: ''
},
{
filename: 'test.vue',
code: '<template><template></template></template>'
},
{
filename: 'test.vue',
code: '<template><div key="foo"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div v-bind:key="foo"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div :key="foo"></div></template>'
},
{
filename: 'test.vue',
code: '<template><div><template key="foo"></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><div><template v-bind:key="foo"></template></div></template>'
},
{
filename: 'test.vue',
code: '<template><div><template :key="foo"></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><template v-slot="item" :key="item.id"><div /></template></template>'
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list"><template :key="item.id"><div /></template></template></template>'
}
],
invalid: [
{
filename: 'test.vue',
code:
'<template><template v-for="item in list" :key="item.id"><div /></template></template>',
errors: [
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
]
},
{
filename: 'test.vue',
code:
'<template><template v-for="(item, i) in list" :key="i"><div /></template></template>',
errors: [
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
]
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list" :key="foo + item.id"><div /></template></template>',
errors: [
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
]
},
{
filename: 'test.vue',
code:
'<template><template v-for="item in list" key="foo"><div /></template></template>',
errors: [
"'<template v-for>' cannot be keyed. Place the key on real elements instead."
]
}
]
})

0 comments on commit 1c52bb9

Please sign in to comment.