Skip to content

Commit 6a8f18a

Browse files
committed
fix vuejs#255: Add new rule require-v-if-else-key
1 parent a473a0d commit 6a8f18a

File tree

5 files changed

+247
-0
lines changed

5 files changed

+247
-0
lines changed

docs/rules/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ sidebarDepth: 0
1212
:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
1313
:::
1414

15+
1516
## Base Rules (Enabling Correct ESLint Parsing)
1617

1718
Enforce all the rules in this category, as well as all higher priority rules, with:
@@ -361,6 +362,7 @@ For example:
361362
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: |
362363
| [vue/require-expose](./require-expose.md) | require declare public properties using `expose` | :bulb: |
363364
| [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | |
365+
| [vue/require-v-if-else-key](./require-v-if-else-key.md) | require `key` with `v-if/v-else-if/v-else` directives | |
364366
| [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: |
365367
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
366368
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |

docs/rules/require-v-if-else-key.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/require-v-if-else-key
5+
description: require `key` with `v-if/v-else-if/v-else` directives
6+
---
7+
# vue/require-v-if-else-key
8+
9+
> require `key` with `v-if/v-else-if/v-else` directives
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
## :book: Rule Details
14+
15+
This rule reports the elements which have `v-if`, `v-else-if`, and/or `v-else` and do not have a `v-bind:key` or `key`.
16+
17+
<eslint-code-block :rules="{'vue/require-v-if-else-key': ['error']}">
18+
19+
```vue
20+
<template>
21+
<!-- ✓ GOOD -->
22+
<div v-if="condition"/>
23+
24+
<div v-if="condition"/>
25+
<span v-else>
26+
27+
<div v-if="condition" key="one"/>
28+
<div v-else key="two"/>
29+
30+
<div v-if="condition" :key="one"/>
31+
<div v-else :key="two"/>
32+
33+
<!-- ✗ BAD -->
34+
<div v-if="condition"/>
35+
<div v-else/>
36+
</template>
37+
```
38+
39+
</eslint-code-block>
40+
41+
## :wrench: Options
42+
43+
Nothing.
44+
45+
## :books: Further Reading
46+
47+
- [Guide (for v2) - v-if without key](https://v2.vuejs.org/v2/style-guide/?redirect=true#v-if-v-else-if-v-else-without-key-use-with-caution)
48+
49+
## :mag: Implementation
50+
51+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-v-if-else-key.js)
52+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-v-if-else-key.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ module.exports = {
179179
'require-slots-as-functions': require('./rules/require-slots-as-functions'),
180180
'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'),
181181
'require-v-for-key': require('./rules/require-v-for-key'),
182+
'require-v-if-else-key': require('./rules/require-v-if-else-key'),
182183
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
183184
'return-in-computed-property': require('./rules/return-in-computed-property'),
184185
'return-in-emits-validator': require('./rules/return-in-emits-validator'),

lib/rules/require-v-if-else-key.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @author Doug Wade <[email protected]>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
// ...
18+
19+
// ------------------------------------------------------------------------------
20+
// Rule Definition
21+
// ------------------------------------------------------------------------------
22+
23+
module.exports = {
24+
meta: {
25+
type: 'problem',
26+
docs: {
27+
description: 'require `key` with `v-if/v-else-if/v-else` directives',
28+
categories: undefined,
29+
url: 'https://eslint.vuejs.org/rules/require-v-if-else-key.html'
30+
},
31+
fixable: null,
32+
schema: [],
33+
messages: {
34+
unexpected:
35+
'Elements in v-if/v-else-if/v-else expect to have distinct keys if they are of the same type.'
36+
}
37+
},
38+
/** @param {RuleContext} context */
39+
create(context) {
40+
const NO_MATCH = Symbol('no match')
41+
42+
/**
43+
* Gets the key from an element.
44+
* @param {VElement} element The element node to get the key from.
45+
* @return {String | null}
46+
*/
47+
function getKey(element) {
48+
const keyAttribute = utils.getAttribute(element, 'key')
49+
if (keyAttribute) {
50+
return keyAttribute?.value?.value || ''
51+
}
52+
53+
const keyDirective = utils.getDirective(element, 'bind', 'key')
54+
if (!keyDirective) {
55+
return ''
56+
}
57+
58+
if (keyDirective.value?.expression?.type !== 'Identifier') {
59+
return null
60+
}
61+
62+
return `v-bind:${keyDirective.value?.expression?.name}`
63+
}
64+
65+
/**
66+
* Check the given element about `v-bind:key` attributes.
67+
* @param {VElement} element The element node to check.
68+
*/
69+
function checkKey(element) {
70+
const prevSibling = utils.prevSibling(element)
71+
72+
if (!prevSibling) {
73+
return
74+
}
75+
76+
if (prevSibling.name !== element.name) {
77+
return
78+
}
79+
80+
const key = getKey(element)
81+
82+
if (key === null) {
83+
return
84+
}
85+
86+
if (!key || key === getKey(prevSibling)) {
87+
context.report({ node: element, messageId: 'unexpected' })
88+
}
89+
}
90+
91+
return utils.defineTemplateBodyVisitor(context, {
92+
/** @param {VDirective} node */
93+
"VAttribute[directive=true][key.name.name='else']"(node) {
94+
checkKey(node.parent.parent)
95+
},
96+
/** @param {VDirective} node */
97+
"VAttribute[directive=true][key.name.name='else-if']"(node) {
98+
checkKey(node.parent.parent)
99+
}
100+
})
101+
}
102+
}
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @author Doug Wade <[email protected]>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../lib/rules/require-v-if-else-key')
9+
10+
const tester = new RuleTester({
11+
parser: require.resolve('vue-eslint-parser'),
12+
parserOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('require-v-if-else-key', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<template>
24+
<div>
25+
<div v-if="conditional" :key="foo">foo</div>
26+
<div v-else :key="bar">bar</div>
27+
</div>
28+
</template>
29+
`
30+
},
31+
{
32+
filename: 'test.vue',
33+
code: `
34+
<template>
35+
<div>
36+
<div v-if="conditional" key="foo">foo</div>
37+
<div v-else key="bar">bar</div>
38+
</div>
39+
</template>
40+
`
41+
},
42+
{
43+
filename: 'test.vue',
44+
code: `
45+
<template>
46+
<div>
47+
<div v-if="conditional">foo</div>
48+
</div>
49+
</template>
50+
`
51+
},
52+
{
53+
filename: 'test.vue',
54+
code: `
55+
<template>
56+
<div>
57+
<div v-if="conditional">foo</div>
58+
<span v-else>bar</span>
59+
</div>
60+
</template>
61+
`
62+
}
63+
],
64+
invalid: [
65+
{
66+
filename: 'test.vue',
67+
code: `
68+
<template>
69+
<div v-if="conditional">foo</div>
70+
<div v-else>bar</div>
71+
</template>
72+
`,
73+
errors: [
74+
'Elements in v-if/v-else-if/v-else expect to have distinct keys if they are of the same type.'
75+
]
76+
},
77+
{
78+
filename: 'test.vue',
79+
code: `
80+
<template>
81+
<div v-if="conditional" key="foo">foo</div>
82+
<div v-else key="foo">bar</div>
83+
</template>
84+
`,
85+
errors: [
86+
'Elements in v-if/v-else-if/v-else expect to have distinct keys if they are of the same type.'
87+
]
88+
}
89+
]
90+
})

0 commit comments

Comments
 (0)