Skip to content

Commit

Permalink
Add vue/no-lone-template rule (#1238)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Jul 14, 2020
1 parent c37f953 commit cc8450d
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/rules/README.md
Expand Up @@ -148,6 +148,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
|:--------|:------------|:---|
| [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
| [vue/no-lone-template](./no-lone-template.md) | disallow unnecessary `<template>` | |
| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | |
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | |
| [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: |
Expand Down Expand Up @@ -256,6 +257,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
|:--------|:------------|:---|
| [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
| [vue/no-lone-template](./no-lone-template.md) | disallow unnecessary `<template>` | |
| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | |
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | |
| [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: |
Expand Down
82 changes: 82 additions & 0 deletions docs/rules/no-lone-template.md
@@ -0,0 +1,82 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-lone-template
description: disallow unnecessary `<template>`
---
# vue/no-lone-template
> disallow unnecessary `<template>`
- :gear: This rule is included in `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.

## :book: Rule Details

This rule aims to eliminate unnecessary and potentially confusing `<template>`.
In Vue.js 2.x, the `<template>` elements that have no specific directives have no effect.
In Vue.js 3.x, the `<template>` elements that have no specific directives render the `<template>` elements as is, but in most cases this may not be what you intended.

<eslint-code-block :rules="{'vue/no-lone-template': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<template v-if="foo">...</template>
<template v-else-if="bar">...</template>
<template v-else>...</template>
<template v-for="e in list">...</template>
<template v-slot>...</template>
<!-- ✗ BAD -->
<template>...</template>
<template/>
</template>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/no-lone-template": ["error", {
"ignoreAccessible": false
}]
}
```

- `ignoreAccessible` ... If `true`, ignore accessible `<template>` elements. default `false`.
Note: this option is useless if you are using Vue.js 2.x.

### `"ignoreAccessible": true`

<eslint-code-block :rules="{'vue/no-lone-template': ['error', { 'ignoreAccessible': true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<template ref="foo">...</template>
<template id="bar">...</template>
<!-- ✗ BAD -->
<template class="baz">...</template>
</template>
```

</eslint-code-block>

## :mute: When Not To Use It

If you are using Vue.js 3.x and want to define the `<template>` element intentionally, you will have to turn this rule off or use `"ignoreAccessible"` option.

## :couple: Related rules

- [vue/no-template-key]
- [no-lone-blocks]

[no-lone-blocks]: https://eslint.org/docs/rules/no-lone-blocks
[vue/no-template-key]: ./no-template-key.md

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-lone-template.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-lone-template.js)
1 change: 1 addition & 0 deletions lib/configs/recommended.js
Expand Up @@ -8,6 +8,7 @@ module.exports = {
rules: {
'vue/attributes-order': 'warn',
'vue/component-tags-order': 'warn',
'vue/no-lone-template': 'warn',
'vue/no-multiple-slot-args': 'warn',
'vue/no-v-html': 'warn',
'vue/order-in-components': 'warn',
Expand Down
1 change: 1 addition & 0 deletions lib/configs/vue3-recommended.js
Expand Up @@ -8,6 +8,7 @@ module.exports = {
rules: {
'vue/attributes-order': 'warn',
'vue/component-tags-order': 'warn',
'vue/no-lone-template': 'warn',
'vue/no-multiple-slot-args': 'warn',
'vue/no-v-html': 'warn',
'vue/order-in-components': 'warn',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -74,6 +74,7 @@ module.exports = {
'no-extra-parens': require('./rules/no-extra-parens'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
'no-lone-template': require('./rules/no-lone-template'),
'no-multi-spaces': require('./rules/no-multi-spaces'),
'no-multiple-objects-in-class': require('./rules/no-multiple-objects-in-class'),
'no-multiple-slot-args': require('./rules/no-multiple-slot-args'),
Expand Down
129 changes: 129 additions & 0 deletions lib/rules/no-lone-template.js
@@ -0,0 +1,129 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

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

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

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

// https://github.com/vuejs/vue-next/blob/64e2f4643602c5980361e66674141e61ba60ef70/packages/compiler-core/src/parse.ts#L405
const SPECIAL_TEMPLATE_DIRECTIVES = new Set([
'if',
'else',
'else-if',
'for',
'slot'
])

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

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow unnecessary `<template>`',
categories: ['vue3-recommended', 'recommended'],
url: 'https://eslint.vuejs.org/rules/no-lone-template.html'
},
fixable: null,
schema: [
{
type: 'object',
properties: {
ignoreAccessible: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
requireDirective: '`<template>` require directive.'
}
},
/** @param {RuleContext} context */
create(context) {
const options = context.options[0] || {}
const ignoreAccessible = options.ignoreAccessible === true

/**
* @param {VAttribute | VDirective} attr
*/
function getKeyName(attr) {
if (attr.directive) {
if (attr.key.name.name !== 'bind') {
// no v-bind
return null
}
if (
!attr.key.argument ||
attr.key.argument.type === 'VExpressionContainer'
) {
// unknown
return null
}
return attr.key.argument.name
}
return attr.key.name
}

return utils.defineTemplateBodyVisitor(context, {
/** @param {VStartTag} node */
"VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
if (
node.attributes.some((attr) => {
if (attr.directive) {
const directiveName = attr.key.name.name
if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
return true
}
if (directiveName === 'slot-scope') {
// `slot-scope` is deprecated in Vue.js 2.6
return true
}
if (directiveName === 'scope') {
// `scope` is deprecated in Vue.js 2.5
return true
}
}

const keyName = getKeyName(attr)
if (keyName === 'slot') {
// `slot` is deprecated in Vue.js 2.6
return true
}

return false
})
) {
return
}

if (
ignoreAccessible &&
node.attributes.some((attr) => {
const keyName = getKeyName(attr)
return keyName === 'id' || keyName === 'ref'
})
) {
return
}

context.report({
node,
messageId: 'requireDirective'
})
}
})
}
}

0 comments on commit cc8450d

Please sign in to comment.