Skip to content

Commit 0e4bb63

Browse files
author
Jonathan Santerre
committed
New: vue/html-has-button-type rule (fixes vuejs#894)
1 parent e8f130c commit 0e4bb63

File tree

6 files changed

+318
-0
lines changed

6 files changed

+318
-0
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
118118
| Rule ID | Description | |
119119
|:--------|:------------|:---|
120120
| [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
121+
| [vue/html-button-has-type](./html-button-has-type.md) | Prevent usage of button without an explicit type attribute | |
121122
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | |
122123
| [vue/order-in-components](./order-in-components.md) | enforce order of properties in components | :wrench: |
123124
| [vue/this-in-template](./this-in-template.md) | disallow usage of `this` in template | |

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

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

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = {
1919
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
2020
'dot-location': require('./rules/dot-location'),
2121
'eqeqeq': require('./rules/eqeqeq'),
22+
'html-button-has-type': require('./rules/html-button-has-type'),
2223
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
2324
'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
2425
'html-end-tags': require('./rules/html-end-tags'),

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

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
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: 'Prevent 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+
return utils.defineTemplateBodyVisitor(context, {
63+
VElement (node) {
64+
if (utils.isHtmlElementNode(node) && node.name === 'button') {
65+
if (!utils.hasAttribute(node, 'type')) {
66+
context.report({
67+
node: node.startTag,
68+
loc: node.startTag.loc,
69+
messageId: 'missingTypeAttribute'
70+
})
71+
} else {
72+
const value = utils.getAttribute(node, 'type').value.value
73+
if (value === '') {
74+
context.report({
75+
node,
76+
messageId: 'emptyTypeAttribute',
77+
})
78+
} else if (!(value in configuration)) {
79+
context.report({
80+
node,
81+
messageId: 'invalidTypeAttribute',
82+
data: { value }
83+
})
84+
} else if (!configuration[value]) {
85+
context.report({
86+
node,
87+
messageId: 'forbiddenTypeAttribute',
88+
data: { value }
89+
})
90+
}
91+
}
92+
}
93+
}
94+
})
95+
}
96+
}
+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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: ''
53+
}
54+
],
55+
56+
invalid: [
57+
{
58+
filename: 'test.vue',
59+
code: '<template><button>Hello World</button></template>',
60+
errors: [{
61+
message: 'Missing an explicit type attribute for button.'
62+
}]
63+
},
64+
{
65+
filename: 'test.vue',
66+
code: '<template><button type="">Hello World</button></template>',
67+
errors: [{
68+
message: 'A value must be set for button type attribute.'
69+
}]
70+
},
71+
{
72+
filename: 'test.vue',
73+
code: '<template><button type="foo">Hello World</button></template>',
74+
errors: [{
75+
message: 'foo is an invalid value for button type attribute.'
76+
}]
77+
},
78+
{
79+
filename: 'test.vue',
80+
options: [{ button: false }],
81+
code: '<template><button type="button">Hello World</button></template>',
82+
errors: [{
83+
message: 'button is a forbidden value for button type attribute.'
84+
}]
85+
},
86+
{
87+
filename: 'test.vue',
88+
options: [{ submit: false }],
89+
code: '<template><button type="submit">Hello World</button></template>',
90+
errors: [{
91+
message: 'submit is a forbidden value for button type attribute.'
92+
}]
93+
},
94+
{
95+
filename: 'test.vue',
96+
options: [{ reset: false }],
97+
code: '<template><button type="reset">Hello World</button></template>',
98+
errors: [{
99+
message: 'reset is a forbidden value for button type attribute.'
100+
}]
101+
},
102+
{
103+
filename: 'test.vue',
104+
options: [{ button: false, submit: false, reset: false }],
105+
code: `<template>
106+
<button type="button">Hello World</button>
107+
<button type="submit">Hello World</button>
108+
<button type="reset">Hello World</button>
109+
</template>`,
110+
errors: [
111+
{
112+
message: 'button is a forbidden value for button type attribute.'
113+
},
114+
{
115+
message: 'submit is a forbidden value for button type attribute.'
116+
},
117+
{
118+
message: 'reset is a forbidden value for button type attribute.'
119+
}
120+
]
121+
},
122+
{
123+
filename: 'test.vue',
124+
options: [{ button: true, submit: true, reset: false }],
125+
code: `<template>
126+
<button type="button">Hello World</button>
127+
<button type="submit">Hello World</button>
128+
<button type="reset">Hello World</button>
129+
<button type="">Hello World</button>
130+
<button type="foo">Hello World</button>
131+
</template>`,
132+
errors: [
133+
{
134+
message: 'reset is a forbidden value for button type attribute.'
135+
},
136+
{
137+
message: 'A value must be set for button type attribute.'
138+
},
139+
{
140+
message: 'foo is an invalid value for button type attribute.'
141+
}
142+
]
143+
},
144+
{
145+
filename: 'test.vue',
146+
code: '<template><button>Hello World</button><button>Hello World</button></template>',
147+
errors: [
148+
{
149+
message: 'Missing an explicit type attribute for button.'
150+
},
151+
{
152+
message: 'Missing an explicit type attribute for button.'
153+
}
154+
]
155+
}
156+
]
157+
})

0 commit comments

Comments
 (0)