Skip to content

Commit

Permalink
New: vue/html-button-has-type rule (#1001)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
SanterreJo committed Feb 5, 2021
1 parent 47e3f89 commit 9a9461a
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -263,6 +263,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
|:--------|:------------|:---|
| [vue/attributes-order](./attributes-order.md) | enforce order of attributes | :wrench: |
| [vue/component-tags-order](./component-tags-order.md) | enforce order of component top-level elements | |
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | |
| [vue/no-lone-template](./no-lone-template.md) | disallow unnecessary `<template>` | |
| [vue/no-multiple-slot-args](./no-multiple-slot-args.md) | disallow to pass multiple arguments to scoped slots | |
| [vue/no-v-html](./no-v-html.md) | disallow use of v-html to prevent XSS attack | |
Expand Down
64 changes: 64 additions & 0 deletions docs/rules/html-button-has-type.md
@@ -0,0 +1,64 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/html-button-has-type
description: disallow usage of button without an explicit type attribute
---
# vue/html-button-has-type

> disallow usage of button without an explicit type attribute
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :gear: This rule is included in `"plugin:vue/recommended"`.

Forgetting the type attribute on a button defaults it to being a submit type.
This is nearly never what is intended, especially in your average one-page application.

## :book: Rule Details

This rule aims to warn if no type or an invalid type is used on a button type attribute.

<eslint-code-block :rules="{'vue/html-button-has-type': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<button type="button">Hello World</button>
<button type="submit">Hello World</button>
<button type="reset">Hello World</button>
<!-- ✗ BAD -->
<button>Hello World</button>
<button type="">Hello World</button>
<button type="foo">Hello World</button>
</template>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/html-button-has-type": ["error", {
"button": true,
"submit": true,
"reset": true
}]
}
```

- `button` ... `<button type="button"></button>`
- `true` (default) ... allow value `button`.
- `false"` ... disallow value `button`.
- `sumbit` ... `<button type="sumbit"></button>`
- `true` (default) ... allow value `submit`.
- `false"` ... disallow value `submit`.
- `reset` ... `<button type="reset"></button>`
- `true` (default) ... allow value `reset`.
- `false"` ... disallow value `reset`.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/html-button-has-type.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/html-button-has-type.js)
1 change: 1 addition & 0 deletions lib/configs/recommended.js
Expand Up @@ -8,6 +8,7 @@ module.exports = {
rules: {
'vue/attributes-order': 'warn',
'vue/component-tags-order': 'warn',
'vue/html-button-has-type': 'warn',
'vue/no-lone-template': 'warn',
'vue/no-multiple-slot-args': 'warn',
'vue/no-v-html': 'warn',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -29,6 +29,7 @@ module.exports = {
eqeqeq: require('./rules/eqeqeq'),
'experimental-script-setup-vars': require('./rules/experimental-script-setup-vars'),
'func-call-spacing': require('./rules/func-call-spacing'),
'html-button-has-type': require('./rules/html-button-has-type'),
'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
'html-comment-content-newline': require('./rules/html-comment-content-newline'),
Expand Down
102 changes: 102 additions & 0 deletions lib/rules/html-button-has-type.js
@@ -0,0 +1,102 @@
/**
* @fileoverview Disallow usage of button without an explicit type attribute
* @author Jonathan Santerre <jonathan@santerre.dev>
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

const optionDefaults = {
button: true,
submit: true,
reset: true
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow usage of button without an explicit type attribute',
category: 'recommended',
url: 'https://eslint.vuejs.org/rules/html-button-has-type.html'
},
fixable: null,
schema: [
{
type: 'object',
properties: {
button: {
default: optionDefaults.button,
type: 'boolean'
},
submit: {
default: optionDefaults.submit,
type: 'boolean'
},
reset: {
default: optionDefaults.reset,
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
missingTypeAttribute: 'Missing an explicit type attribute for button.',
invalidTypeAttribute: '{{value}} is an invalid value for button type attribute.',
forbiddenTypeAttribute: '{{value}} is a forbidden value for button type attribute.',
emptyTypeAttribute: 'A value must be set for button type attribute.'
}
},

create: function (context) {
const configuration = Object.assign({}, optionDefaults, context.options[0])

function report (node, messageId, data) {
context.report({
node,
messageId,
data
})
}

function validateAttribute (attribute) {
const value = attribute.value
const strValue = value.value
if (strValue === '') {
report(value, 'emptyTypeAttribute')
} else if (!(strValue in configuration)) {
report(value, 'invalidTypeAttribute', { value: strValue })
} else if (!configuration[strValue]) {
report(value, 'forbiddenTypeAttribute', { value: strValue })
}
}

function validateDirective (directive) {
const value = directive.value
if (!value.expression) {
report(value, 'emptyTypeAttribute')
}
}

return utils.defineTemplateBodyVisitor(context, {
"VElement[name='button']" (node) {
if (utils.hasAttribute(node, 'type')) {
validateAttribute(utils.getAttribute(node, 'type'))
} else if (utils.hasDirective(node, 'bind', 'type')) {
validateDirective(utils.getDirective(node, 'bind', 'type'))
} else {
report(node, 'missingTypeAttribute')
}
}
})
}
}
170 changes: 170 additions & 0 deletions tests/lib/rules/html-button-has-type.js
@@ -0,0 +1,170 @@
/**
* @fileoverview Prevent usage of button without an explicit type attribute
* @author Jonathan Santerre
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/html-button-has-type')

var RuleTester = require('eslint').RuleTester

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2015 }
})
ruleTester.run('html-button-has-type', rule, {

valid: [
{
filename: 'test.vue',
code: '<template><button type="button">Hello World</button></template>'
},
{
filename: 'test.vue',
code: '<template><button type="submit">Hello World</button></template>'
},
{
filename: 'test.vue',
code: '<template><button type="reset">Hello World</button></template>'
},
{
filename: 'test.vue',
code: '<template><slot><button type="button">Hello World</button></slot></template>'
},
{
filename: 'test.vue',
code: `<template>
<button type="button">Hello World</button>
<button type="submit">Hello World</button>
<button type="reset">Hello World</button>
</template>`
},
{
filename: 'test.vue',
code: `<template><button :type="buttonType">Hello World</button></template>`
},
{
filename: 'test.vue',
code: ''
}
],

invalid: [
{
filename: 'test.vue',
code: '<template><button>Hello World</button></template>',
errors: [{
message: 'Missing an explicit type attribute for button.'
}]
},
{
filename: 'test.vue',
code: '<template><button type="">Hello World</button></template>',
errors: [{
message: 'A value must be set for button type attribute.'
}]
},
{
filename: 'test.vue',
code: '<template><button type="foo">Hello World</button></template>',
errors: [{
message: 'foo is an invalid value for button type attribute.'
}]
},
{
filename: 'test.vue',
options: [{ button: false }],
code: '<template><button type="button">Hello World</button></template>',
errors: [{
message: 'button is a forbidden value for button type attribute.'
}]
},
{
filename: 'test.vue',
options: [{ submit: false }],
code: '<template><button type="submit">Hello World</button></template>',
errors: [{
message: 'submit is a forbidden value for button type attribute.'
}]
},
{
filename: 'test.vue',
options: [{ reset: false }],
code: '<template><button type="reset">Hello World</button></template>',
errors: [{
message: 'reset is a forbidden value for button type attribute.'
}]
},
{
filename: 'test.vue',
options: [{ button: false, submit: false, reset: false }],
code: `<template>
<button type="button">Hello World</button>
<button type="submit">Hello World</button>
<button type="reset">Hello World</button>
</template>`,
errors: [
{
message: 'button is a forbidden value for button type attribute.'
},
{
message: 'submit is a forbidden value for button type attribute.'
},
{
message: 'reset is a forbidden value for button type attribute.'
}
]
},
{
filename: 'test.vue',
options: [{ button: true, submit: true, reset: false }],
code: `<template>
<button type="button">Hello World</button>
<button type="submit">Hello World</button>
<button type="reset">Hello World</button>
<button type="">Hello World</button>
<button type="foo">Hello World</button>
</template>`,
errors: [
{
message: 'reset is a forbidden value for button type attribute.'
},
{
message: 'A value must be set for button type attribute.'
},
{
message: 'foo is an invalid value for button type attribute.'
}
]
},
{
filename: 'test.vue',
code: '<template><button>Hello World</button><button>Hello World</button></template>',
errors: [
{
message: 'Missing an explicit type attribute for button.'
},
{
message: 'Missing an explicit type attribute for button.'
}
]
},
{
filename: 'test.vue',
code: `<template><button :type="">Hello World</button></template>`,
errors: [
{
message: 'A value must be set for button type attribute.'
}
]
}
]
})

0 comments on commit 9a9461a

Please sign in to comment.