Skip to content

Commit 9a9461a

Browse files
authored
New: vue/html-button-has-type rule (#1001)
* New: vue/html-button-has-type rule (fixes #894) * more precise error location * revert npm run update deleted script indent doc ref * fix: eslint-plugin-import when running lint * fix lint errors * update readme and docs * Support bind type attribute
1 parent 47e3f89 commit 9a9461a

File tree

6 files changed

+339
-0
lines changed

6 files changed

+339
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
263263
|:--------|:------------|:---|
264264
| [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
265265
| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
266+
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | |
266267
| [vue/no-lone-template](./no-lone-template.md) | disallow unnecessary `<template>` | |
267268
| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | |
268269
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | |

docs/rules/html-button-has-type.md

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/html-button-has-type
5+
description: disallow usage of button without an explicit type attribute
6+
---
7+
# vue/html-button-has-type
8+
9+
> disallow usage of button without an explicit type attribute
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+
- :gear: This rule is included in `"plugin:vue/recommended"`.
13+
14+
Forgetting the type attribute on a button defaults it to being a submit type.
15+
This is nearly never what is intended, especially in your average one-page application.
16+
17+
## :book: Rule Details
18+
19+
This rule aims to warn if no type or an invalid type is used on a button type attribute.
20+
21+
<eslint-code-block :rules="{'vue/html-button-has-type': ['error']}">
22+
23+
```vue
24+
<template>
25+
<!-- ✓ GOOD -->
26+
<button type="button">Hello World</button>
27+
<button type="submit">Hello World</button>
28+
<button type="reset">Hello World</button>
29+
30+
<!-- ✗ BAD -->
31+
<button>Hello World</button>
32+
<button type="">Hello World</button>
33+
<button type="foo">Hello World</button>
34+
</template>
35+
```
36+
37+
</eslint-code-block>
38+
39+
## :wrench: Options
40+
41+
```json
42+
{
43+
"vue/html-button-has-type": ["error", {
44+
"button": true,
45+
"submit": true,
46+
"reset": true
47+
}]
48+
}
49+
```
50+
51+
- `button` ... `<button type="button"></button>`
52+
- `true` (default) ... allow value `button`.
53+
- `false"` ... disallow value `button`.
54+
- `sumbit` ... `<button type="sumbit"></button>`
55+
- `true` (default) ... allow value `submit`.
56+
- `false"` ... disallow value `submit`.
57+
- `reset` ... `<button type="reset"></button>`
58+
- `true` (default) ... allow value `reset`.
59+
- `false"` ... disallow value `reset`.
60+
61+
## :mag: Implementation
62+
63+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-button-has-type.js)
64+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-button-has-type.js)

lib/configs/recommended.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
rules: {
99
'vue/attributes-order': 'warn',
1010
'vue/component-tags-order': 'warn',
11+
'vue/html-button-has-type': 'warn',
1112
'vue/no-lone-template': 'warn',
1213
'vue/no-multiple-slot-args': 'warn',
1314
'vue/no-v-html': 'warn',

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = {
2929
eqeqeq: require('./rules/eqeqeq'),
3030
'experimental-script-setup-vars': require('./rules/experimental-script-setup-vars'),
3131
'func-call-spacing': require('./rules/func-call-spacing'),
32+
'html-button-has-type': require('./rules/html-button-has-type'),
3233
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
3334
'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
3435
'html-comment-content-newline': require('./rules/html-comment-content-newline'),

lib/rules/html-button-has-type.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @fileoverview Disallow usage of button without an explicit type attribute
3+
* @author Jonathan Santerre <[email protected]>
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
13+
// ------------------------------------------------------------------------------
14+
// Rule Definition
15+
// ------------------------------------------------------------------------------
16+
17+
const optionDefaults = {
18+
button: true,
19+
submit: true,
20+
reset: true
21+
}
22+
23+
module.exports = {
24+
meta: {
25+
type: 'suggestion',
26+
docs: {
27+
description: 'disallow usage of button without an explicit type attribute',
28+
category: 'recommended',
29+
url: 'https://eslint.vuejs.org/rules/html-button-has-type.html'
30+
},
31+
fixable: null,
32+
schema: [
33+
{
34+
type: 'object',
35+
properties: {
36+
button: {
37+
default: optionDefaults.button,
38+
type: 'boolean'
39+
},
40+
submit: {
41+
default: optionDefaults.submit,
42+
type: 'boolean'
43+
},
44+
reset: {
45+
default: optionDefaults.reset,
46+
type: 'boolean'
47+
}
48+
},
49+
additionalProperties: false
50+
}
51+
],
52+
messages: {
53+
missingTypeAttribute: 'Missing an explicit type attribute for button.',
54+
invalidTypeAttribute: '{{value}} is an invalid value for button type attribute.',
55+
forbiddenTypeAttribute: '{{value}} is a forbidden value for button type attribute.',
56+
emptyTypeAttribute: 'A value must be set for button type attribute.'
57+
}
58+
},
59+
60+
create: function (context) {
61+
const configuration = Object.assign({}, optionDefaults, context.options[0])
62+
63+
function report (node, messageId, data) {
64+
context.report({
65+
node,
66+
messageId,
67+
data
68+
})
69+
}
70+
71+
function validateAttribute (attribute) {
72+
const value = attribute.value
73+
const strValue = value.value
74+
if (strValue === '') {
75+
report(value, 'emptyTypeAttribute')
76+
} else if (!(strValue in configuration)) {
77+
report(value, 'invalidTypeAttribute', { value: strValue })
78+
} else if (!configuration[strValue]) {
79+
report(value, 'forbiddenTypeAttribute', { value: strValue })
80+
}
81+
}
82+
83+
function validateDirective (directive) {
84+
const value = directive.value
85+
if (!value.expression) {
86+
report(value, 'emptyTypeAttribute')
87+
}
88+
}
89+
90+
return utils.defineTemplateBodyVisitor(context, {
91+
"VElement[name='button']" (node) {
92+
if (utils.hasAttribute(node, 'type')) {
93+
validateAttribute(utils.getAttribute(node, 'type'))
94+
} else if (utils.hasDirective(node, 'bind', 'type')) {
95+
validateDirective(utils.getDirective(node, 'bind', 'type'))
96+
} else {
97+
report(node, 'missingTypeAttribute')
98+
}
99+
}
100+
})
101+
}
102+
}
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/**
2+
* @fileoverview Prevent usage of button without an explicit type attribute
3+
* @author Jonathan Santerre
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/html-button-has-type')
12+
13+
var RuleTester = require('eslint').RuleTester
14+
15+
// ------------------------------------------------------------------------------
16+
// Tests
17+
// ------------------------------------------------------------------------------
18+
19+
var ruleTester = new RuleTester({
20+
parser: require.resolve('vue-eslint-parser'),
21+
parserOptions: { ecmaVersion: 2015 }
22+
})
23+
ruleTester.run('html-button-has-type', rule, {
24+
25+
valid: [
26+
{
27+
filename: 'test.vue',
28+
code: '<template><button type="button">Hello World</button></template>'
29+
},
30+
{
31+
filename: 'test.vue',
32+
code: '<template><button type="submit">Hello World</button></template>'
33+
},
34+
{
35+
filename: 'test.vue',
36+
code: '<template><button type="reset">Hello World</button></template>'
37+
},
38+
{
39+
filename: 'test.vue',
40+
code: '<template><slot><button type="button">Hello World</button></slot></template>'
41+
},
42+
{
43+
filename: 'test.vue',
44+
code: `<template>
45+
<button type="button">Hello World</button>
46+
<button type="submit">Hello World</button>
47+
<button type="reset">Hello World</button>
48+
</template>`
49+
},
50+
{
51+
filename: 'test.vue',
52+
code: `<template><button :type="buttonType">Hello World</button></template>`
53+
},
54+
{
55+
filename: 'test.vue',
56+
code: ''
57+
}
58+
],
59+
60+
invalid: [
61+
{
62+
filename: 'test.vue',
63+
code: '<template><button>Hello World</button></template>',
64+
errors: [{
65+
message: 'Missing an explicit type attribute for button.'
66+
}]
67+
},
68+
{
69+
filename: 'test.vue',
70+
code: '<template><button type="">Hello World</button></template>',
71+
errors: [{
72+
message: 'A value must be set for button type attribute.'
73+
}]
74+
},
75+
{
76+
filename: 'test.vue',
77+
code: '<template><button type="foo">Hello World</button></template>',
78+
errors: [{
79+
message: 'foo is an invalid value for button type attribute.'
80+
}]
81+
},
82+
{
83+
filename: 'test.vue',
84+
options: [{ button: false }],
85+
code: '<template><button type="button">Hello World</button></template>',
86+
errors: [{
87+
message: 'button is a forbidden value for button type attribute.'
88+
}]
89+
},
90+
{
91+
filename: 'test.vue',
92+
options: [{ submit: false }],
93+
code: '<template><button type="submit">Hello World</button></template>',
94+
errors: [{
95+
message: 'submit is a forbidden value for button type attribute.'
96+
}]
97+
},
98+
{
99+
filename: 'test.vue',
100+
options: [{ reset: false }],
101+
code: '<template><button type="reset">Hello World</button></template>',
102+
errors: [{
103+
message: 'reset is a forbidden value for button type attribute.'
104+
}]
105+
},
106+
{
107+
filename: 'test.vue',
108+
options: [{ button: false, submit: false, reset: false }],
109+
code: `<template>
110+
<button type="button">Hello World</button>
111+
<button type="submit">Hello World</button>
112+
<button type="reset">Hello World</button>
113+
</template>`,
114+
errors: [
115+
{
116+
message: 'button is a forbidden value for button type attribute.'
117+
},
118+
{
119+
message: 'submit is a forbidden value for button type attribute.'
120+
},
121+
{
122+
message: 'reset is a forbidden value for button type attribute.'
123+
}
124+
]
125+
},
126+
{
127+
filename: 'test.vue',
128+
options: [{ button: true, submit: true, reset: false }],
129+
code: `<template>
130+
<button type="button">Hello World</button>
131+
<button type="submit">Hello World</button>
132+
<button type="reset">Hello World</button>
133+
<button type="">Hello World</button>
134+
<button type="foo">Hello World</button>
135+
</template>`,
136+
errors: [
137+
{
138+
message: 'reset is a forbidden value for button type attribute.'
139+
},
140+
{
141+
message: 'A value must be set for button type attribute.'
142+
},
143+
{
144+
message: 'foo is an invalid value for button type attribute.'
145+
}
146+
]
147+
},
148+
{
149+
filename: 'test.vue',
150+
code: '<template><button>Hello World</button><button>Hello World</button></template>',
151+
errors: [
152+
{
153+
message: 'Missing an explicit type attribute for button.'
154+
},
155+
{
156+
message: 'Missing an explicit type attribute for button.'
157+
}
158+
]
159+
},
160+
{
161+
filename: 'test.vue',
162+
code: `<template><button :type="">Hello World</button></template>`,
163+
errors: [
164+
{
165+
message: 'A value must be set for button type attribute.'
166+
}
167+
]
168+
}
169+
]
170+
})

0 commit comments

Comments
 (0)