Skip to content

Commit

Permalink
Added valid-v-if-template-root rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
perrysong authored and songpengyuan committed May 8, 2023
1 parent 9184384 commit cdd3ac9
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/rules/index.md
Expand Up @@ -118,6 +118,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
| [vue/valid-v-else](./valid-v-else.md) | enforce valid `v-else` directives | | :three::two::warning: |
| [vue/valid-v-for](./valid-v-for.md) | enforce valid `v-for` directives | | :three::two::warning: |
| [vue/valid-v-html](./valid-v-html.md) | enforce valid `v-html` directives | | :three::two::warning: |
| [vue/valid-v-if-template-root](./valid-v-if-template-root.md) | enforce valid `v-if` directives on root element | | :three::two::warning: |
| [vue/valid-v-if](./valid-v-if.md) | enforce valid `v-if` directives | | :three::two::warning: |
| [vue/valid-v-is](./valid-v-is.md) | enforce valid `v-is` directives | | :three::warning: |
| [vue/valid-v-memo](./valid-v-memo.md) | enforce valid `v-memo` directives | | :three::warning: |
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/valid-template-root.md
Expand Up @@ -46,4 +46,4 @@ This rule was introduced in eslint-plugin-vue v3.11.0
## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-template-root.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-template-root.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-template-root.js)
40 changes: 40 additions & 0 deletions docs/rules/valid-v-if-template-root.md
@@ -0,0 +1,40 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/valid-v-if-template-root
description: enforce valid `v-if` directives on root element
---
# vue/valid-v-if-template-root

> enforce valid `v-if` directives on root element
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.

This rule checks whether every template root is valid.

## :book: Rule Details

This rule reports the template root in the following cases:

<eslint-code-block :rules="{'vue/valid-v-if-template-root': ['error']}">

```vue
<!-- `v-if` should not be used on root element without `v-else` -->
<template>
<div v-if="foo"></div>
</template>
<template><custom-component v-if="shouldShow" /></template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-v-if-template-root.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-v-if-template-root.js)
1 change: 1 addition & 0 deletions lib/configs/essential.js
Expand Up @@ -62,6 +62,7 @@ module.exports = {
'vue/valid-v-else': 'error',
'vue/valid-v-for': 'error',
'vue/valid-v-html': 'error',
'vue/valid-v-if-template-root': 'error',
'vue/valid-v-if': 'error',
'vue/valid-v-model': 'error',
'vue/valid-v-on': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/configs/vue3-essential.js
Expand Up @@ -77,6 +77,7 @@ module.exports = {
'vue/valid-v-else': 'error',
'vue/valid-v-for': 'error',
'vue/valid-v-html': 'error',
'vue/valid-v-if-template-root': 'error',
'vue/valid-v-if': 'error',
'vue/valid-v-is': 'error',
'vue/valid-v-memo': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -220,6 +220,7 @@ module.exports = {
'valid-v-else': require('./rules/valid-v-else'),
'valid-v-for': require('./rules/valid-v-for'),
'valid-v-html': require('./rules/valid-v-html'),
'valid-v-if-template-root': require('./rules/valid-v-if-template-root'),
'valid-v-if': require('./rules/valid-v-if'),
'valid-v-is': require('./rules/valid-v-is'),
'valid-v-memo': require('./rules/valid-v-memo'),
Expand Down
72 changes: 72 additions & 0 deletions lib/rules/valid-v-if-template-root.js
@@ -0,0 +1,72 @@
/**
* @author Perry Song
* @copyright 2023 Perry Song. All rights reserved.
* See LICENSE file in root directory for full license.
*/
'use strict'

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

/**
* Get the number of root element directive
* @param {VNode[]} rootElements The start tag node to check.
* @param {string} directiveName The directive name to check.
*/
function getDirectiveLength(rootElements, directiveName) {
if (!directiveName) return 0
return rootElements.filter(
(element) =>
element.type === 'VElement' && utils.hasDirective(element, directiveName)
).length
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce valid `v-if` directives on root element',
categories: ['vue3-essential', 'essential'],
url: 'https://eslint.vuejs.org/rules/valid-v-if-template-root.html'
},
fixable: null,
schema: []
},
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.getSourceCode()

return {
/** @param {Program} program */
Program(program) {
const element = program.templateBody
if (element == null) {
return
}

const rootElements = []
for (const child of element.children) {
if (sourceCode.getText(child).trim() !== '') {
rootElements.push(child)
}
}

if (rootElements.length === 0) return
const hasRootVIfLength = getDirectiveLength(rootElements, 'if')
const hasRootVElseLength = getDirectiveLength(rootElements, 'else')
const hasRootVElseIfLength = getDirectiveLength(rootElements, 'else-if')
if (
hasRootVIfLength === 1 &&
hasRootVElseLength === 0 &&
hasRootVElseIfLength === 0
) {
context.report({
node: element,
loc: element.loc,
message:
'`v-if` should not be used on root element without `v-else`.'
})
}
}
}
}
}
111 changes: 111 additions & 0 deletions tests/lib/rules/valid-v-if-template-root.js
@@ -0,0 +1,111 @@
/**
* @author Perry Song
* @copyright 2023 Perry Song. All rights reserved.
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/valid-v-if-template-root')

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

tester.run('valid-v-if-template-root', rule, {
valid: [
{
filename: 'test.vue',
code: ''
},
{
filename: 'test.vue',
code: '<template><div>abc</div></template>'
},
{
filename: 'test.vue',
code: '<template>\n <div>abc</div>\n</template>'
},
{
filename: 'test.vue',
code: '<template>\n <!-- comment -->\n <div>abc</div>\n</template>'
},
{
filename: 'test.vue',
code: '<template>\n <!-- comment -->\n <div v-if="foo">abc</div>\n <div v-else>abc</div>\n</template>'
},
{
filename: 'test.vue',
code: '<template>\n <!-- comment -->\n <div v-if="foo">abc</div>\n <div v-else-if="bar">abc</div>\n <div v-else>abc</div>\n</template>'
},
{
filename: 'test.vue',
code: `<template>\n <c1 v-if="1" />\n <c2 v-else-if="1" />\n <c3 v-else />\n</template>`
},
{
filename: 'test.vue',
code: '<template><div v-if="foo"></div><div v-else-if="bar"></div></template>'
},
{
filename: 'test.vue',
code: '<template src="foo.html"></template>'
},
{
filename: 'test.vue',
code: '<template><div><textarea/>test</div></template>'
},
{
filename: 'test.vue',
code: '<template><table><custom-thead></custom-thead></table></template>'
},
{
filename: 'test.vue',
code: '<template><div></div><div></div></template>'
},
{
filename: 'test.vue',
code: '<template>\n <div></div>\n <div></div>\n</template>'
},
{
filename: 'test.vue',
code: '<template>{{a b c}}</template>'
},
{
filename: 'test.vue',
code: '<template><div></div>aaaaaa</template>'
},
{
filename: 'test.vue',
code: '<template>aaaaaa<div></div></template>'
},
{
filename: 'test.vue',
code: '<template><div v-for="x in list"></div></template>'
},
{
filename: 'test.vue',
code: '<template><slot></slot></template>'
},
{
filename: 'test.vue',
code: '<template><template></template></template>'
},
{
filename: 'test.vue',
code: '<template> <div v-if="mode === \'a\'"></div><div v-if="mode === \'b\'"></div></template>'
}
],
invalid: [
{
filename: 'test.vue',
code: '<template><custom-component v-if="foo"></custom-component></template>',
errors: ['`v-if` should not be used on root element without `v-else`.']
},
{
filename: 'test.vue',
code: '<template><div v-if="foo"></div></template>',
errors: ['`v-if` should not be used on root element without `v-else`.']
}
]
})

0 comments on commit cdd3ac9

Please sign in to comment.