Skip to content

Commit fbeb887

Browse files
authored
Add vue/no-useless-template-attributes rule (#1644)
1 parent a56c7ec commit fbeb887

File tree

5 files changed

+343
-0
lines changed

5 files changed

+343
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ For example:
325325
| [vue/no-unused-refs](./no-unused-refs.md) | disallow unused refs | |
326326
| [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method | |
327327
| [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
328+
| [vue/no-useless-template-attributes](./no-useless-template-attributes.md) | disallow useless attribute on `<template>` | |
328329
| [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: |
329330
| [vue/no-v-text](./no-v-text.md) | disallow use of v-text | |
330331
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-useless-template-attributes
5+
description: disallow useless attribute on `<template>`
6+
---
7+
# vue/no-useless-template-attributes
8+
9+
> disallow useless attribute on `<template>`
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
## :book: Rule Details
14+
15+
This rule to prevent any useless attribute on `<template>` tags.
16+
17+
<eslint-code-block :rules="{'vue/no-useless-template-attributes': ['error']}">
18+
19+
```vue
20+
<template>
21+
<!-- ✓ GOOD -->
22+
<template v-if="foo">...</template>
23+
<template v-if="foo">...</template>
24+
<template v-else-if="foo">...</template>
25+
<template v-else>...</template>
26+
<template v-for="i in foo" :key="i">...</template>
27+
<template v-slot:foo>...</template>
28+
<!-- for Vue<=2.5 -->
29+
<template slot="foo">...</template>
30+
<template :slot="foo">...</template>
31+
<template slot-scope="param">...</template>
32+
<!-- for Vue<=2.4 -->
33+
<template scope="param">...</template>
34+
35+
<!-- ✗ BAD -->
36+
<template v-if="foo" class="heading">...</template>
37+
<template v-for="i in foo" :bar="i">...</template>
38+
<template v-slot:foo="foo" ref="input">...</template>
39+
<template v-if="foo" @click="click">...</template>
40+
41+
<!-- Ignore -->
42+
<template class="heading">...</template>
43+
<template :bar="i">...</template>
44+
<template ref="input">...</template>
45+
<template @click="click">...</template>
46+
</template>
47+
```
48+
49+
</eslint-code-block>
50+
51+
## :wrench: Options
52+
53+
Nothing.
54+
55+
## :couple: Related Rules
56+
57+
- [vue/no-lone-template]
58+
59+
[vue/no-lone-template]: ./no-lone-template.md
60+
61+
## :mag: Implementation
62+
63+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-useless-template-attributes.js)
64+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-useless-template-attributes.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ module.exports = {
129129
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
130130
'no-useless-concat': require('./rules/no-useless-concat'),
131131
'no-useless-mustaches': require('./rules/no-useless-mustaches'),
132+
'no-useless-template-attributes': require('./rules/no-useless-template-attributes'),
132133
'no-useless-v-bind': require('./rules/no-useless-v-bind'),
133134
'no-v-for-template-key-on-child': require('./rules/no-v-for-template-key-on-child'),
134135
'no-v-for-template-key': require('./rules/no-v-for-template-key'),
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Helpers
15+
// ------------------------------------------------------------------------------
16+
17+
// https://github.com/vuejs/vue-next/blob/64e2f4643602c5980361e66674141e61ba60ef70/packages/compiler-core/src/parse.ts#L405
18+
const SPECIAL_TEMPLATE_DIRECTIVES = new Set([
19+
'if',
20+
'else',
21+
'else-if',
22+
'for',
23+
'slot'
24+
])
25+
26+
// ------------------------------------------------------------------------------
27+
// Rule Definition
28+
// ------------------------------------------------------------------------------
29+
30+
module.exports = {
31+
meta: {
32+
type: 'problem',
33+
docs: {
34+
description: 'disallow useless attribute on `<template>`',
35+
// TODO Switch to `vue3-essential` and `essential` in the major version.
36+
// categories: ['vue3-essential', 'essential'],
37+
categories: undefined,
38+
url: 'https://eslint.vuejs.org/rules/no-useless-template-attributes.html'
39+
},
40+
fixable: null,
41+
schema: [],
42+
messages: {
43+
unexpectedAttr: 'Unexpected useless attribute on `<template>`.',
44+
unexpectedDir: 'Unexpected useless directive on `<template>`.'
45+
}
46+
},
47+
/** @param {RuleContext} context */
48+
create(context) {
49+
/**
50+
* @param {VAttribute | VDirective} attr
51+
*/
52+
function getKeyName(attr) {
53+
if (attr.directive) {
54+
if (attr.key.name.name !== 'bind') {
55+
// no v-bind
56+
return null
57+
}
58+
if (
59+
!attr.key.argument ||
60+
attr.key.argument.type === 'VExpressionContainer'
61+
) {
62+
// unknown
63+
return null
64+
}
65+
return attr.key.argument.name
66+
}
67+
return attr.key.name
68+
}
69+
70+
/**
71+
* @param {VAttribute | VDirective} attr
72+
*/
73+
function isFragmentTemplateAttribute(attr) {
74+
if (attr.directive) {
75+
const directiveName = attr.key.name.name
76+
if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
77+
return true
78+
}
79+
if (directiveName === 'slot-scope') {
80+
// `slot-scope` is deprecated in Vue.js 2.6
81+
return true
82+
}
83+
if (directiveName === 'scope') {
84+
// `scope` is deprecated in Vue.js 2.5
85+
return true
86+
}
87+
}
88+
89+
const keyName = getKeyName(attr)
90+
if (keyName === 'slot') {
91+
// `slot` is deprecated in Vue.js 2.6
92+
return true
93+
}
94+
95+
return false
96+
}
97+
98+
return utils.defineTemplateBodyVisitor(context, {
99+
/** @param {VStartTag} node */
100+
"VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
101+
if (!node.attributes.some(isFragmentTemplateAttribute)) {
102+
return
103+
}
104+
105+
for (const attr of node.attributes) {
106+
if (isFragmentTemplateAttribute(attr)) {
107+
continue
108+
}
109+
const keyName = getKeyName(attr)
110+
if (keyName === 'key') {
111+
continue
112+
}
113+
context.report({
114+
node: attr,
115+
messageId: attr.directive ? 'unexpectedDir' : 'unexpectedAttr'
116+
})
117+
}
118+
}
119+
})
120+
}
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../lib/rules/no-useless-template-attributes')
9+
10+
const tester = new RuleTester({
11+
parser: require.resolve('vue-eslint-parser'),
12+
parserOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('no-useless-template-attributes', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<template>
24+
<template v-if="foo">...</template>
25+
<template v-else-if="bar">...</template>
26+
<template v-else>...</template>
27+
</template>
28+
`
29+
},
30+
{
31+
filename: 'test.vue',
32+
code: `
33+
<template>
34+
<template v-for="e in list">...</template>
35+
</template>
36+
`
37+
},
38+
{
39+
filename: 'test.vue',
40+
code: `
41+
<template>
42+
<template v-slot>...</template>
43+
</template>
44+
`
45+
},
46+
{
47+
filename: 'test.vue',
48+
code: `
49+
<template>
50+
<CoolButton>
51+
<template slot="foo">...</template>
52+
</CoolButton>
53+
</template>
54+
`
55+
},
56+
{
57+
filename: 'test.vue',
58+
code: `
59+
<template>
60+
<CoolButton>
61+
<template slot-scope="foo">...</template>
62+
</CoolButton>
63+
</template>
64+
`
65+
},
66+
{
67+
filename: 'test.vue',
68+
code: `
69+
<template>
70+
<CoolButton>
71+
<template scope="foo">...</template>
72+
</CoolButton>
73+
</template>
74+
`
75+
},
76+
{
77+
filename: 'test.vue',
78+
code: `
79+
<template>
80+
<!-- ignore -->
81+
<template foo="a">...</template>
82+
<template :foo="a">...</template>
83+
<template v-unknown="a">...</template>
84+
</template>
85+
`
86+
},
87+
// not template
88+
{
89+
filename: 'test.vue',
90+
code: `
91+
<template>
92+
<div v-if="foo" class="heading">...</div>
93+
<div v-for="i in foo" :bar="i">...</div>
94+
<div v-slot:foo="foo" ref="input">...</div>
95+
<div v-if="foo" @click="click">...</div>
96+
</template>
97+
`
98+
}
99+
],
100+
invalid: [
101+
{
102+
filename: 'test.vue',
103+
code: `
104+
<template>
105+
<!-- ✓ GOOD -->
106+
<template v-if="foo">...</template>
107+
<template v-if="foo">...</template>
108+
<template v-else-if="foo">...</template>
109+
<template v-else>...</template>
110+
<template v-for="i in foo" :key="i">...</template>
111+
<template v-slot:foo>...</template>
112+
<!-- for Vue<=2.5 -->
113+
<template slot="foo">...</template>
114+
<template :slot="foo">...</template>
115+
<template slot-scope="param">...</template>
116+
<!-- for Vue<=2.4 -->
117+
<template scope="param">...</template>
118+
119+
<!-- ✗ BAD -->
120+
<template v-if="foo" class="heading">...</template>
121+
<template v-for="i in foo" :bar="i">...</template>
122+
<template v-slot:foo="foo" ref="input">...</template>
123+
<template v-if="foo" @click="click">...</template>
124+
125+
<!-- Ignore -->
126+
<template class="heading">...</template>
127+
<template :bar="i">...</template>
128+
<template ref="input">...</template>
129+
<template @click="click">...</template>
130+
</template>
131+
`,
132+
errors: [
133+
{
134+
message: 'Unexpected useless attribute on `<template>`.',
135+
line: 18,
136+
column: 30
137+
},
138+
{
139+
message: 'Unexpected useless directive on `<template>`.',
140+
line: 19,
141+
column: 36
142+
},
143+
{
144+
message: 'Unexpected useless attribute on `<template>`.',
145+
line: 20,
146+
column: 36
147+
},
148+
{
149+
message: 'Unexpected useless directive on `<template>`.',
150+
line: 21,
151+
column: 30
152+
}
153+
]
154+
}
155+
]
156+
})

0 commit comments

Comments
 (0)