Skip to content

Commit c5d84cd

Browse files
committed
New: Add vue/require-v-if-inside-transition rule
1 parent 1cd5590 commit c5d84cd

File tree

6 files changed

+237
-0
lines changed

6 files changed

+237
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
6262
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
6363
| [vue/require-render-return](./require-render-return.md) | enforce render function to always return value | |
6464
| [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | |
65+
| [vue/require-v-if-inside-transition](./require-v-if-inside-transition.md) | require control the display of the content inside `<transition>` | |
6566
| [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | |
6667
| [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property | |
6768
| [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/require-v-if-inside-transition
5+
description: require control the display of the content inside `<transition>`
6+
---
7+
# vue/require-v-if-inside-transition
8+
> require control the display of the content inside `<transition>`
9+
10+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
11+
12+
## :book: Rule Details
13+
14+
This rule reports elements inside `<transition>` that do not control the display.
15+
16+
<eslint-code-block :rules="{'vue/require-v-if-inside-transition': ['error']}">
17+
18+
```vue
19+
<template>
20+
<!-- ✓ GOOD -->
21+
<transition><div v-if="show" /></transition>
22+
<transition><div v-show="show" /></transition>
23+
24+
<!-- ✗ BAD -->
25+
<transition><div /></transition>
26+
</template>
27+
```
28+
29+
</eslint-code-block>
30+
31+
## :wrench: Options
32+
33+
Nothing.
34+
35+
## :books: Further reading
36+
37+
- [Vue RFCs - 0017-transition-as-root](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0017-transition-as-root.md)
38+
39+
## :mag: Implementation
40+
41+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-v-if-inside-transition.js)
42+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-v-if-inside-transition.js)

lib/configs/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = {
3030
'vue/require-prop-type-constructor': 'error',
3131
'vue/require-render-return': 'error',
3232
'vue/require-v-for-key': 'error',
33+
'vue/require-v-if-inside-transition': 'error',
3334
'vue/require-valid-default-prop': 'error',
3435
'vue/return-in-computed-property': 'error',
3536
'vue/use-v-on-exact': 'error',

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ module.exports = {
8383
'require-prop-types': require('./rules/require-prop-types'),
8484
'require-render-return': require('./rules/require-render-return'),
8585
'require-v-for-key': require('./rules/require-v-for-key'),
86+
'require-v-if-inside-transition': require('./rules/require-v-if-inside-transition'),
8687
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
8788
'return-in-computed-property': require('./rules/return-in-computed-property'),
8889
'script-indent': require('./rules/script-indent'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @author Yosuke Ota
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+
* Check whether the given node is an well-known element or not.
19+
* @param {ASTNode} node The element node to check.
20+
* @returns {boolean} `true` if the name is an well-known element name.
21+
*/
22+
function isWellKnownElement (node) {
23+
if (
24+
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
25+
utils.isHtmlWellKnownElementName(node.rawName) ||
26+
utils.isSvgWellKnownElementName(node.rawName)
27+
) {
28+
return true
29+
}
30+
return false
31+
}
32+
33+
// ------------------------------------------------------------------------------
34+
// Rule Definition
35+
// ------------------------------------------------------------------------------
36+
37+
module.exports = {
38+
meta: {
39+
type: 'problem',
40+
docs: {
41+
description: 'require control the display of the content inside `<transition>`',
42+
categories: ['vue3-essential'],
43+
url: 'https://eslint.vuejs.org/rules/require-v-if-inside-transition.html'
44+
},
45+
fixable: null,
46+
schema: [],
47+
messages: {
48+
expected: 'The element inside `<transition>` is expected to have a `v-if` or `v-show` directive.'
49+
}
50+
},
51+
52+
create (context) {
53+
/**
54+
* Check if the given element has display control.
55+
* @param {VElement} element The element node to check.
56+
*/
57+
function verifyInsideElement (element) {
58+
if (utils.isCustomComponent(element)) {
59+
return
60+
}
61+
if (!isWellKnownElement(element)) {
62+
return
63+
}
64+
if (!utils.hasDirective(element, 'if') && !utils.hasDirective(element, 'show')) {
65+
context.report({
66+
node: element.startTag,
67+
loc: element.startTag.loc,
68+
messageId: 'expected'
69+
})
70+
}
71+
}
72+
73+
return utils.defineTemplateBodyVisitor(context, {
74+
"VElement[name='transition'] > VElement" (node) {
75+
if (node.parent.children[0] !== node) {
76+
return
77+
}
78+
verifyInsideElement(node)
79+
}
80+
})
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const RuleTester = require('eslint').RuleTester
12+
const rule = require('../../../lib/rules/require-v-if-inside-transition')
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
const tester = new RuleTester({
19+
parser: require.resolve('vue-eslint-parser'),
20+
parserOptions: { ecmaVersion: 2015 }
21+
})
22+
23+
tester.run('require-v-if-inside-transition', rule, {
24+
valid: [
25+
{
26+
filename: 'test.vue',
27+
code: ''
28+
},
29+
{
30+
filename: 'test.vue',
31+
code: '<template><transition><div v-if="show" /></transition></template>'
32+
},
33+
{
34+
filename: 'test.vue',
35+
code: '<template><transition><div v-show="show" /></transition></template>'
36+
},
37+
{
38+
filename: 'test.vue',
39+
code: '<template><Transition><div v-if="show" /></Transition></template>'
40+
},
41+
{
42+
filename: 'test.vue',
43+
code: '<template><Transition><div v-show="show" /></Transition></template>'
44+
},
45+
{
46+
filename: 'test.vue',
47+
code: '<template><Transition><MyComp /></Transition></template>'
48+
},
49+
{
50+
filename: 'test.vue',
51+
code: '<template><Transition><component :is="component" /></Transition></template>'
52+
},
53+
{
54+
filename: 'test.vue',
55+
code: '<template><Transition><div :is="component" /></Transition></template>'
56+
},
57+
{
58+
filename: 'test.vue',
59+
code: '<template><svg height="100" width="100"><transition><circle v-if="show" /></transition></svg> </template>'
60+
},
61+
{
62+
filename: 'test.vue',
63+
code: '<template><svg height="100" width="100"><transition><MyComponent /></transition></svg> </template>'
64+
},
65+
{
66+
filename: 'test.vue',
67+
code: '<template><transition><template v-if="show"><div /></template></transition></template>'
68+
}
69+
],
70+
invalid: [
71+
{
72+
filename: 'test.vue',
73+
code: '<template><transition><div /></transition></template>',
74+
errors: [
75+
{
76+
line: 1,
77+
column: 23,
78+
messageId: 'expected',
79+
endLine: 1,
80+
endColumn: 30
81+
}
82+
]
83+
},
84+
{
85+
filename: 'test.vue',
86+
code: '<template><Transition><div /></Transition></template>',
87+
errors: [{ messageId: 'expected' }]
88+
},
89+
{
90+
filename: 'test.vue',
91+
code: '<template><transition><div /><div /></transition></template>',
92+
errors: [{ messageId: 'expected' }]
93+
},
94+
{
95+
filename: 'test.vue',
96+
code: '<template><transition><div v-for="e in list" /></transition></template>',
97+
errors: [{ messageId: 'expected' }]
98+
},
99+
{
100+
filename: 'test.vue',
101+
code: '<template><svg height="100" width="100"><transition><circle /></transition></svg> </template>',
102+
errors: [{ messageId: 'expected' }]
103+
},
104+
{
105+
filename: 'test.vue',
106+
code: '<template><transition><template v-for="e in list"><div /></template></transition></template>',
107+
errors: [{ messageId: 'expected' }]
108+
}
109+
]
110+
})

0 commit comments

Comments
 (0)