Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add vue/no-useless-template-attributes rule (#1644)
- Loading branch information
Showing
5 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' | ||
}) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
] | ||
} | ||
] | ||
}) |