Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vue/no-useless-template-attributes rule #1644

Merged
merged 1 commit into from Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}
]
}
]
})