Skip to content

Commit

Permalink
New: Add vue/require-v-if-inside-transition rule (#1099)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Apr 21, 2020
1 parent f4de98e commit 3e90a0c
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -66,6 +66,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: |
| [vue/require-render-return](./require-render-return.md) | enforce render function to always return value | |
| [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | |
| [vue/require-v-if-inside-transition](./require-v-if-inside-transition.md) | require control the display of the content inside `<transition>` | |
| [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | |
| [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property | |
| [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | |
Expand Down
42 changes: 42 additions & 0 deletions docs/rules/require-v-if-inside-transition.md
@@ -0,0 +1,42 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/require-v-if-inside-transition
description: require control the display of the content inside `<transition>`
---
# vue/require-v-if-inside-transition
> require control the display of the content inside `<transition>`
- :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 elements inside `<transition>` that do not control the display.

<eslint-code-block :rules="{'vue/require-v-if-inside-transition': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<transition><div v-if="show" /></transition>
<transition><div v-show="show" /></transition>
<!-- ✗ BAD -->
<transition><div /></transition>
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :books: Further reading

- [Vue RFCs - 0017-transition-as-root](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0017-transition-as-root.md)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-v-if-inside-transition.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-v-if-inside-transition.js)
1 change: 1 addition & 0 deletions lib/configs/vue3-essential.js
Expand Up @@ -34,6 +34,7 @@ module.exports = {
'vue/require-prop-type-constructor': 'error',
'vue/require-render-return': 'error',
'vue/require-v-for-key': 'error',
'vue/require-v-if-inside-transition': 'error',
'vue/require-valid-default-prop': 'error',
'vue/return-in-computed-property': 'error',
'vue/use-v-on-exact': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -88,6 +88,7 @@ module.exports = {
'require-prop-types': require('./rules/require-prop-types'),
'require-render-return': require('./rules/require-render-return'),
'require-v-for-key': require('./rules/require-v-for-key'),
'require-v-if-inside-transition': require('./rules/require-v-if-inside-transition'),
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
'return-in-computed-property': require('./rules/return-in-computed-property'),
'script-indent': require('./rules/script-indent'),
Expand Down
82 changes: 82 additions & 0 deletions lib/rules/require-v-if-inside-transition.js
@@ -0,0 +1,82 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

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

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

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

/**
* Check whether the given node is an well-known element or not.
* @param {ASTNode} node The element node to check.
* @returns {boolean} `true` if the name is an well-known element name.
*/
function isWellKnownElement (node) {
if (
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName)
) {
return true
}
return false
}

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

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'require control the display of the content inside `<transition>`',
categories: ['vue3-essential'],
url: 'https://eslint.vuejs.org/rules/require-v-if-inside-transition.html'
},
fixable: null,
schema: [],
messages: {
expected: 'The element inside `<transition>` is expected to have a `v-if` or `v-show` directive.'
}
},

create (context) {
/**
* Check if the given element has display control.
* @param {VElement} element The element node to check.
*/
function verifyInsideElement (element) {
if (utils.isCustomComponent(element)) {
return
}
if (!isWellKnownElement(element)) {
return
}
if (!utils.hasDirective(element, 'if') && !utils.hasDirective(element, 'show')) {
context.report({
node: element.startTag,
loc: element.startTag.loc,
messageId: 'expected'
})
}
}

return utils.defineTemplateBodyVisitor(context, {
"VElement[name='transition'] > VElement" (node) {
if (node.parent.children[0] !== node) {
return
}
verifyInsideElement(node)
}
})
}
}
110 changes: 110 additions & 0 deletions tests/lib/rules/require-v-if-inside-transition.js
@@ -0,0 +1,110 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

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

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/require-v-if-inside-transition')

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

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

tester.run('require-v-if-inside-transition', rule, {
valid: [
{
filename: 'test.vue',
code: ''
},
{
filename: 'test.vue',
code: '<template><transition><div v-if="show" /></transition></template>'
},
{
filename: 'test.vue',
code: '<template><transition><div v-show="show" /></transition></template>'
},
{
filename: 'test.vue',
code: '<template><Transition><div v-if="show" /></Transition></template>'
},
{
filename: 'test.vue',
code: '<template><Transition><div v-show="show" /></Transition></template>'
},
{
filename: 'test.vue',
code: '<template><Transition><MyComp /></Transition></template>'
},
{
filename: 'test.vue',
code: '<template><Transition><component :is="component" /></Transition></template>'
},
{
filename: 'test.vue',
code: '<template><Transition><div :is="component" /></Transition></template>'
},
{
filename: 'test.vue',
code: '<template><svg height="100" width="100"><transition><circle v-if="show" /></transition></svg> </template>'
},
{
filename: 'test.vue',
code: '<template><svg height="100" width="100"><transition><MyComponent /></transition></svg> </template>'
},
{
filename: 'test.vue',
code: '<template><transition><template v-if="show"><div /></template></transition></template>'
}
],
invalid: [
{
filename: 'test.vue',
code: '<template><transition><div /></transition></template>',
errors: [
{
line: 1,
column: 23,
messageId: 'expected',
endLine: 1,
endColumn: 30
}
]
},
{
filename: 'test.vue',
code: '<template><Transition><div /></Transition></template>',
errors: [{ messageId: 'expected' }]
},
{
filename: 'test.vue',
code: '<template><transition><div /><div /></transition></template>',
errors: [{ messageId: 'expected' }]
},
{
filename: 'test.vue',
code: '<template><transition><div v-for="e in list" /></transition></template>',
errors: [{ messageId: 'expected' }]
},
{
filename: 'test.vue',
code: '<template><svg height="100" width="100"><transition><circle /></transition></svg> </template>',
errors: [{ messageId: 'expected' }]
},
{
filename: 'test.vue',
code: '<template><transition><template v-for="e in list"><div /></template></transition></template>',
errors: [{ messageId: 'expected' }]
}
]
})

0 comments on commit 3e90a0c

Please sign in to comment.