Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vue/no-v-for-template-key-on-child rule #1289

Merged
merged 5 commits into from Aug 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}
]
}
]
})