Skip to content

Commit

Permalink
Add vue/no-v-for-template-key-on-child rule (#1289)
Browse files Browse the repository at this point in the history
* Add `vue/no-v-for-template-key-on-child` rule

* Add tests

* update

* Update docs
  • Loading branch information
ota-meshi committed Aug 28, 2020
1 parent e1366fd commit c3221b8
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -73,6 +73,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-on-child](./no-v-for-template-key-on-child.md) | disallow key of `<template v-for>` placed on child elements | |
| [vue/no-watch-after-await](./no-watch-after-await.md) | disallow asynchronously registered `watch` | |
| [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
54 changes: 54 additions & 0 deletions docs/rules/no-v-for-template-key-on-child.md
@@ -0,0 +1,54 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-v-for-template-key-on-child
description: disallow key of `<template v-for>` placed on child elements
---
# vue/no-v-for-template-key-on-child
> disallow key of `<template v-for>` placed on child elements
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.

## :book: Rule Details

This rule reports the key of the `<template v-for>` placed on the child elements.

In Vue.js 3.x, with the support for fragments, the `<template v-for>` key can be placed on the `<template>` tag.

::: warning Note
Do not use with the [vue/no-v-for-template-key] rule for Vue.js 2.x.
This rule conflicts with the [vue/no-v-for-template-key] rule.
:::

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

```vue
<template>
<!-- ✓ GOOD -->
<template v-for="todo in todos" :key="todo">
<Foo />
</template>
<!-- ✗ BAD -->
<template v-for="todo in todos">
<Foo :key="todo" />
</template>
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :couple: Related Rules

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

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

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-v-for-template-key-on-child.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-v-for-template-key-on-child.js)
12 changes: 10 additions & 2 deletions docs/rules/no-v-for-template-key.md
Expand Up @@ -9,12 +9,18 @@ description: 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.

In Vue.js 2.x, disallows `key` attribute on `<template>` elements.

::: warning Note
Do not use with the [vue/no-v-for-template-key-on-child] rule for Vue.js 3.x.
This rule conflicts with the [vue/no-v-for-template-key-on-child] rule.
:::

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

```vue
Expand Down Expand Up @@ -43,9 +49,11 @@ Nothing.

## :couple: Related Rules

- [vue/no-template-key](./no-template-key.md)
- [vue/no-template-key]
- [vue/no-v-for-template-key-on-child]

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

## :books: Further Reading

Expand Down
1 change: 1 addition & 0 deletions lib/configs/vue3-essential.js
Expand Up @@ -41,6 +41,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-on-child': 'error',
'vue/no-watch-after-await': '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-on-child': require('./rules/no-v-for-template-key-on-child'),
'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'),
Expand Down
95 changes: 95 additions & 0 deletions lib/rules/no-v-for-template-key-on-child.js
@@ -0,0 +1,95 @@
/**
* @author Yosuke Ota
* This rule is based on X_V_FOR_TEMPLATE_KEY_PLACEMENT error of Vue 3.
* see https://github.com/vuejs/vue-next/blob/b0d01e9db9ffe5781cce5a2d62c8552db3d615b0/packages/compiler-core/src/errors.ts#L70
*/
'use strict'

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

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

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Check whether the given attribute is using the variables which are defined by `v-for` directives.
* @param {VDirective} vFor The attribute node of `v-for` to check.
* @param {VDirective} vBindKey The attribute node of `v-bind:key` to check.
* @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives.
*/
function isUsingIterationVar(vFor, vBindKey) {
if (vBindKey.value == null) {
return false
}
const references = vBindKey.value.references
const variables = vFor.parent.parent.variables
return references.some((reference) =>
variables.some(
(variable) =>
variable.id.name === reference.id.name && variable.kind === 'v-for'
)
)
}

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

module.exports = {
meta: {
type: 'problem',
docs: {
description:
'disallow key of `<template v-for>` placed on child elements',
categories: ['vue3-essential'],
url: 'https://eslint.vuejs.org/rules/no-v-for-template-key-on-child.html'
},
fixable: null,
schema: [],
messages: {
vForTemplateKeyPlacement:
'`<template v-for>` key should be placed on the `<template>` tag.'
}
},
/** @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 template = node.parent.parent
const vBindKeyOnTemplate = utils.getDirective(template, 'bind', 'key')
if (
vBindKeyOnTemplate &&
isUsingIterationVar(node, vBindKeyOnTemplate)
) {
return
}

for (const child of template.children.filter(utils.isVElement)) {
if (
utils.hasDirective(child, 'if') ||
utils.hasDirective(child, 'else-if') ||
utils.hasDirective(child, 'else') ||
utils.hasDirective(child, 'for')
) {
continue
}
const vBindKeyOnChild = utils.getDirective(child, 'bind', 'key')
if (vBindKeyOnChild && isUsingIterationVar(node, vBindKeyOnChild)) {
context.report({
node: vBindKeyOnChild,
loc: vBindKeyOnChild.loc,
messageId: 'vForTemplateKeyPlacement'
})
}
}
}
})
}
}
167 changes: 167 additions & 0 deletions tests/lib/rules/no-v-for-template-key-on-child.js
@@ -0,0 +1,167 @@
/**
* @author Yosuke Ota
*/
'use strict'

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

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

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

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

tester.run('no-v-for-template-key-on-child', rule, {
valid: [
{
filename: 'test.vue',
code: ''
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list"><Foo /></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list" :key="x"><Foo /></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list" :key="x.id"><Foo :key="x.id" /></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="(x, i) in list" :key="i"><Foo :key="x" /></template></div></template>'
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="(x, i) in list"><Foo :key="foo" /></template></div></template>'
},
{
filename: 'test.vue',
code: `
<template>
<div>
<template v-for="x in list">
<Foo v-if="a" :key="x" />
</template>
</div>
</template>`
},
{
filename: 'test.vue',
code: `
<template>
<div>
<template v-for="x in list">
<Foo v-if="a" :key="x.key1" />
<Foo v-else-if="a" :key="x.key2" />
<Foo v-else :key="x.key3" />
<Foo v-for="y in list" :key="x.key4" />
</template>
</div>
</template>`
}
],
invalid: [
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list"><Foo :key="x" /></template></div></template>',
errors: [
{
message:
'`<template v-for>` key should be placed on the `<template>` tag.',
column: 49
}
]
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list"><Foo :key="x.id" /></template></div></template>',
errors: [
'`<template v-for>` key should be placed on the `<template>` tag.'
]
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list" :key="foo"><Foo :key="x.id" /></template></div></template>',
errors: [
'`<template v-for>` key should be placed on the `<template>` tag.'
]
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list" :key><Foo :key="x.id" /></template></div></template>',
errors: [
'`<template v-for>` key should be placed on the `<template>` tag.'
]
},
{
filename: 'test.vue',
code:
'<template><div><template v-for="x in list" :key><div /><Foo :key="x.id" /></template></div></template>',
errors: [
'`<template v-for>` key should be placed on the `<template>` tag.'
]
},
{
filename: 'test.vue',
code: `<template><div><template v-for="x in list" :key><Foo :key="'foo' + x.id" /><Bar :key="'bar' + x.id" /></template></div></template>`,
errors: [
'`<template v-for>` key should be placed on the `<template>` tag.',
'`<template v-for>` key should be placed on the `<template>` tag.'
]
},
{
filename: 'test.vue',
code: `
<template>
<div>
<template v-for="x in list">
<Foo v-if="a" :key="x.key1" />
<Foo v-else-if="a" :key="x.key2" />
<Foo v-else :key="x.key3" />
<Foo v-for="y in list" :key="x.key4" />
<Foo :key="x.error1" />
<div :key="x.error2" />
<slot :key="x.error3" ></slot>
</template>
</div>
</template>`,
errors: [
{
message:
'`<template v-for>` key should be placed on the `<template>` tag.',
line: 9
},
{
message:
'`<template v-for>` key should be placed on the `<template>` tag.',
line: 10
},
{
message:
'`<template v-for>` key should be placed on the `<template>` tag.',
line: 11
}
]
}
]
})

0 comments on commit c3221b8

Please sign in to comment.