Skip to content

Commit

Permalink
Add vue/no-useless-template-attributes rule (#1644)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Oct 5, 2021
1 parent a56c7ec commit fbeb887
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -325,6 +325,7 @@ For example:
| [vue/no-unused-refs](./no-unused-refs.md) | disallow unused refs | |
| [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method | |
| [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
| [vue/no-useless-template-attributes](./no-useless-template-attributes.md) | disallow useless attribute on `<template>` | |
| [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: |
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
Expand Down
64 changes: 64 additions & 0 deletions docs/rules/no-useless-template-attributes.md
@@ -0,0 +1,64 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-useless-template-attributes
description: disallow useless attribute on `<template>`
---
# vue/no-useless-template-attributes

> disallow useless attribute on `<template>`
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :book: Rule Details

This rule to prevent any useless attribute on `<template>` tags.

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

```vue
<template>
<!-- ✓ GOOD -->
<template v-if="foo">...</template>
<template v-if="foo">...</template>
<template v-else-if="foo">...</template>
<template v-else>...</template>
<template v-for="i in foo" :key="i">...</template>
<template v-slot:foo>...</template>
<!-- for Vue<=2.5 -->
<template slot="foo">...</template>
<template :slot="foo">...</template>
<template slot-scope="param">...</template>
<!-- for Vue<=2.4 -->
<template scope="param">...</template>
<!-- ✗ BAD -->
<template v-if="foo" class="heading">...</template>
<template v-for="i in foo" :bar="i">...</template>
<template v-slot:foo="foo" ref="input">...</template>
<template v-if="foo" @click="click">...</template>
<!-- Ignore -->
<template class="heading">...</template>
<template :bar="i">...</template>
<template ref="input">...</template>
<template @click="click">...</template>
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :couple: Related Rules

- [vue/no-lone-template]

[vue/no-lone-template]: ./no-lone-template.md

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-useless-template-attributes.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-useless-template-attributes.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -129,6 +129,7 @@ module.exports = {
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
'no-useless-concat': require('./rules/no-useless-concat'),
'no-useless-mustaches': require('./rules/no-useless-mustaches'),
'no-useless-template-attributes': require('./rules/no-useless-template-attributes'),
'no-useless-v-bind': require('./rules/no-useless-v-bind'),
'no-v-for-template-key-on-child': require('./rules/no-v-for-template-key-on-child'),
'no-v-for-template-key': require('./rules/no-v-for-template-key'),
Expand Down
121 changes: 121 additions & 0 deletions lib/rules/no-useless-template-attributes.js
@@ -0,0 +1,121 @@
/**
* @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 useless attribute on `<template>`',
// TODO Switch to `vue3-essential` and `essential` in the major version.
// categories: ['vue3-essential', 'essential'],
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-useless-template-attributes.html'
},
fixable: null,
schema: [],
messages: {
unexpectedAttr: 'Unexpected useless attribute on `<template>`.',
unexpectedDir: 'Unexpected useless directive on `<template>`.'
}
},
/** @param {RuleContext} context */
create(context) {
/**
* @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
}

/**
* @param {VAttribute | VDirective} attr
*/
function isFragmentTemplateAttribute(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 utils.defineTemplateBodyVisitor(context, {
/** @param {VStartTag} node */
"VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
if (!node.attributes.some(isFragmentTemplateAttribute)) {
return
}

for (const attr of node.attributes) {
if (isFragmentTemplateAttribute(attr)) {
continue
}
const keyName = getKeyName(attr)
if (keyName === 'key') {
continue
}
context.report({
node: attr,
messageId: attr.directive ? 'unexpectedDir' : 'unexpectedAttr'
})
}
}
})
}
}
156 changes: 156 additions & 0 deletions tests/lib/rules/no-useless-template-attributes.js
@@ -0,0 +1,156 @@
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-useless-template-attributes')

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

tester.run('no-useless-template-attributes', rule, {
valid: [
{
filename: 'test.vue',
code: `
<template>
<template v-if="foo">...</template>
<template v-else-if="bar">...</template>
<template v-else>...</template>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<template v-for="e in list">...</template>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<template v-slot>...</template>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<CoolButton>
<template slot="foo">...</template>
</CoolButton>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<CoolButton>
<template slot-scope="foo">...</template>
</CoolButton>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<CoolButton>
<template scope="foo">...</template>
</CoolButton>
</template>
`
},
{
filename: 'test.vue',
code: `
<template>
<!-- ignore -->
<template foo="a">...</template>
<template :foo="a">...</template>
<template v-unknown="a">...</template>
</template>
`
},
// not template
{
filename: 'test.vue',
code: `
<template>
<div v-if="foo" class="heading">...</div>
<div v-for="i in foo" :bar="i">...</div>
<div v-slot:foo="foo" ref="input">...</div>
<div v-if="foo" @click="click">...</div>
</template>
`
}
],
invalid: [
{
filename: 'test.vue',
code: `
<template>
<!-- ✓ GOOD -->
<template v-if="foo">...</template>
<template v-if="foo">...</template>
<template v-else-if="foo">...</template>
<template v-else>...</template>
<template v-for="i in foo" :key="i">...</template>
<template v-slot:foo>...</template>
<!-- for Vue<=2.5 -->
<template slot="foo">...</template>
<template :slot="foo">...</template>
<template slot-scope="param">...</template>
<!-- for Vue<=2.4 -->
<template scope="param">...</template>
<!-- ✗ BAD -->
<template v-if="foo" class="heading">...</template>
<template v-for="i in foo" :bar="i">...</template>
<template v-slot:foo="foo" ref="input">...</template>
<template v-if="foo" @click="click">...</template>
<!-- Ignore -->
<template class="heading">...</template>
<template :bar="i">...</template>
<template ref="input">...</template>
<template @click="click">...</template>
</template>
`,
errors: [
{
message: 'Unexpected useless attribute on `<template>`.',
line: 18,
column: 30
},
{
message: 'Unexpected useless directive on `<template>`.',
line: 19,
column: 36
},
{
message: 'Unexpected useless attribute on `<template>`.',
line: 20,
column: 36
},
{
message: 'Unexpected useless directive on `<template>`.',
line: 21,
column: 30
}
]
}
]
})

0 comments on commit fbeb887

Please sign in to comment.