Skip to content

Commit

Permalink
Change vue/html-button-has-type rule (#1432)
Browse files Browse the repository at this point in the history
- Enable type checking
- Format
- Exclude from recommended category
- Exclude `Button` component from check
  • Loading branch information
ota-meshi committed Feb 5, 2021
1 parent 9a9461a commit 5129cef
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 64 deletions.
2 changes: 1 addition & 1 deletion docs/rules/README.md
Expand Up @@ -263,7 +263,6 @@ 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 All @@ -290,6 +289,7 @@ For example:
| [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: |
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | |
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | |
| [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: |
| [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: |
| [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: |
Expand Down
17 changes: 8 additions & 9 deletions docs/rules/html-button-has-type.md
Expand Up @@ -9,9 +9,8 @@ description: disallow usage of button without an explicit type attribute
> 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.
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
Expand Down Expand Up @@ -49,14 +48,14 @@ This rule aims to warn if no type or an invalid type is used on a button type at
```

- `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`.
- `true` (default) ... allow value `button`.
- `false"` ... disallow value `button`.
- `submit` ... `<button type="submit"></button>`
- `true` (default) ... allow value `submit`.
- `false"` ... disallow value `submit`.
- `reset` ... `<button type="reset"></button>`
- `true` (default) ... allow value `reset`.
- `false"` ... disallow value `reset`.
- `true` (default) ... allow value `reset`.
- `false"` ... disallow value `reset`.

## :mag: Implementation

Expand Down
1 change: 0 additions & 1 deletion lib/configs/recommended.js
Expand Up @@ -8,7 +8,6 @@ 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
84 changes: 64 additions & 20 deletions lib/rules/html-button-has-type.js
Expand Up @@ -24,8 +24,9 @@ module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow usage of button without an explicit type attribute',
category: 'recommended',
description:
'disallow usage of button without an explicit type attribute',
categories: null,
url: 'https://eslint.vuejs.org/rules/html-button-has-type.html'
},
fixable: null,
Expand All @@ -51,51 +52,94 @@ module.exports = {
],
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.',
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) {
/**
* @param {RuleContext} context - The rule context.
* @returns {RuleListener} AST event handlers.
*/
create(context) {
/**
* @typedef {object} Configuration
* @property {boolean} button
* @property {boolean} submit
* @property {boolean} reset
*/
/** @type {Configuration} */
const configuration = Object.assign({}, optionDefaults, context.options[0])

function report (node, messageId, data) {
/**
*
* @param {string} type
* @returns {type is 'button' | 'submit' | 'reset'}
*/
function isButtonType(type) {
return type === 'button' || type === 'submit' || type === 'reset'
}
/**
* @param {ASTNode} node
* @param {string} messageId
* @param {any} [data]
*/
function report(node, messageId, data) {
context.report({
node,
messageId,
data
})
}

function validateAttribute (attribute) {
/**
* @param {VAttribute} attribute
*/
function validateAttribute(attribute) {
const value = attribute.value
if (!value || !value.value) {
report(value || attribute, 'emptyTypeAttribute')
return
}

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

function validateDirective (directive) {
/**
* @param {VDirective} directive
*/
function validateDirective(directive) {
const value = directive.value
if (!value.expression) {
report(value, 'emptyTypeAttribute')
if (!value || !value.expression) {
report(value || directive, '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')
/**
* @param {VElement} node
*/
"VElement[rawName='button']"(node) {
const typeAttr = utils.getAttribute(node, 'type')
if (typeAttr) {
validateAttribute(typeAttr)
return
}
const typeDir = utils.getDirective(node, 'bind', 'type')
if (typeDir) {
validateDirective(typeDir)
return
}

report(node.startTag, 'missingTypeAttribute')
}
})
}
Expand Down

0 comments on commit 5129cef

Please sign in to comment.