Skip to content

Commit

Permalink
Add vue/v-on-event-hyphenation rule (#1388)
Browse files Browse the repository at this point in the history
* Add vue/v-on-event-hyphenation rule

* update docs

* Update doc
  • Loading branch information
ota-meshi committed Dec 27, 2020
1 parent 543361b commit 01f7732
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -321,6 +321,7 @@ For example:
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: |
| [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) | enforce v-on event naming style on custom components in template | :wrench: |
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |

### Extension Rules
Expand Down
8 changes: 8 additions & 0 deletions docs/rules/attribute-hyphenation.md
Expand Up @@ -47,6 +47,7 @@ Default casing is set to `always` with `['data-', 'aria-', 'slot-scope']` set to
- `"ignore"` ... Array of ignored names

### `"always"`

It errors on upper case letters.

<eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'always']}">
Expand All @@ -64,6 +65,7 @@ It errors on upper case letters.
</eslint-code-block>

### `"never"`

It errors on hyphens except `data-`, `aria-` and `slot-scope`.

<eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'never']}">
Expand All @@ -84,6 +86,7 @@ It errors on hyphens except `data-`, `aria-` and `slot-scope`.
</eslint-code-block>

### `"never", { "ignore": ["custom-prop"] }`

Don't use hyphenated name but allow custom attributes

<eslint-code-block fix :rules="{'vue/attribute-hyphenation': ['error', 'never', { ignore: ['custom-prop']}]}">
Expand All @@ -104,6 +107,11 @@ Don't use hyphenated name but allow custom attributes

</eslint-code-block>

## :couple: Related Rules

- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
- [vue/prop-name-casing](./prop-name-casing.md)

## :rocket: Version

This rule was introduced in eslint-plugin-vue v3.9.0
Expand Down
9 changes: 7 additions & 2 deletions docs/rules/custom-event-name-casing.md
Expand Up @@ -23,9 +23,9 @@ Vue 2 recommends using kebab-case for custom event names.
See [Guide (for v2) - Custom Events] for more details.

Vue 3 recommends using camelCase for custom event names.
In Vue 3, using either camelCase or kebab-case for your custom event name does not limit its use in v-on. However, following JavaScript conventions, camelCase is more natural.

See [vuejs/docs-next#656](https://github.com/vuejs/docs-next/issues/656) for more details.
See [Guide - Custom Events] for more details.

This rule enforces kebab-case by default.

Expand Down Expand Up @@ -171,6 +171,11 @@ export default {
[Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html
[Guide (for v2) - Custom Events]: https://vuejs.org/v2/guide/components-custom-events.html

## :couple: Related Rules

- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
- [vue/prop-name-casing](./prop-name-casing.md)

## :rocket: Version

This rule was introduced in eslint-plugin-vue v7.0.0
Expand Down
5 changes: 5 additions & 0 deletions docs/rules/prop-name-casing.md
Expand Up @@ -70,6 +70,11 @@ export default {

- [Style guide - Prop name casing](https://v3.vuejs.org/style-guide/#prop-name-casing-strongly-recommended)

## :couple: Related Rules

- [vue/attribute-hyphenation](./attribute-hyphenation.md)
- [vue/custom-event-name-casing](./custom-event-name-casing.md)

## :rocket: Version

This rule was introduced in eslint-plugin-vue v4.3.0
Expand Down
119 changes: 119 additions & 0 deletions docs/rules/v-on-event-hyphenation.md
@@ -0,0 +1,119 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/v-on-event-hyphenation
description: enforce v-on event naming style on custom components in template
---
# vue/v-on-event-hyphenation

> enforce v-on event naming style on custom components in template
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

This rule enforces using hyphenated v-on event names on custom components in Vue templates.

<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<MyComponent v-on:custom-event="handleEvent"/>
<MyComponent @custom-event="handleEvent"/>
<!-- ✗ BAD -->
<MyComponent v-on:customEvent="handleEvent"/>
<MyComponent @customEvent="handleEvent"/>
</template>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/v-on-event-hyphenation": ["error", "always" | "never", {
"autofix": false,
"ignore": []
}]
}
```

- `"always"` (default) ... Use hyphenated name.
- `"never"` ... Don't use hyphenated name.
- `"ignore"` ... Array of ignored names
- `"autofix"` ... If `true`, enable autofix. If you are using Vue 2, we recommend that you do not use it due to its side effects.

### `"always"`

It errors on upper case letters.

<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<MyComponent v-on:custom-event="handleEvent"/>
<!-- ✗ BAD -->
<MyComponent v-on:customEvent="handleEvent"/>
</template>
```

</eslint-code-block>

### `"never"`

It errors on hyphens.

<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<MyComponent v-on:customEvent="handleEvent"/>
<!-- ✗ BAD -->
<MyComponent v-on:custom-event="handleEvent"/>
</template>
```

</eslint-code-block>

### `"never", { "ignore": ["custom-event"] }`

Don't use hyphenated name but allow custom event names

<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { ignore: ['custom-event'], autofix: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<MyComponent v-on:custom-event="handleEvent"/>
<MyComponent v-on:myEvent="handleEvent"/>
<!-- ✗ BAD -->
<MyComponent v-on:my-event="handleEvent"/>
</template>
```

</eslint-code-block>

## :books: Further Reading

- [Guide - Custom Events]

[Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html

## :couple: Related Rules

- [vue/custom-event-name-casing](./custom-event-name-casing.md)
- [vue/attribute-hyphenation](./attribute-hyphenation.md)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-event-hyphenation.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -157,6 +157,7 @@ module.exports = {
'use-v-on-exact': require('./rules/use-v-on-exact'),
'v-bind-style': require('./rules/v-bind-style'),
'v-for-delimiter-style': require('./rules/v-for-delimiter-style'),
'v-on-event-hyphenation': require('./rules/v-on-event-hyphenation'),
'v-on-function-call': require('./rules/v-on-function-call'),
'v-on-style': require('./rules/v-on-style'),
'v-slot-style': require('./rules/v-slot-style'),
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/attribute-hyphenation.js
Expand Up @@ -87,7 +87,7 @@ module.exports = {
*/
function isIgnoredAttribute(value) {
const isIgnored = ignoredAttributes.some((attr) => {
return value.indexOf(attr) !== -1
return value.includes(attr)
})

if (isIgnored) {
Expand Down
113 changes: 113 additions & 0 deletions lib/rules/v-on-event-hyphenation.js
@@ -0,0 +1,113 @@
'use strict'

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

module.exports = {
meta: {
docs: {
description:
'enforce v-on event naming style on custom components in template',
// TODO Change with major version.
// categories: ['vue3-strongly-recommended'],
categories: undefined,
url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html'
},
fixable: 'code',
schema: [
{
enum: ['always', 'never']
},
{
type: 'object',
properties: {
autofix: { type: 'boolean' },
ignore: {
type: 'array',
items: {
allOf: [
{ type: 'string' },
{ not: { type: 'string', pattern: ':exit$' } },
{ not: { type: 'string', pattern: '^\\s*$' } }
]
},
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
}
],
type: 'suggestion'
},

/** @param {RuleContext} context */
create(context) {
const sourceCode = context.getSourceCode()
const option = context.options[0]
const optionsPayload = context.options[1]
const useHyphenated = option !== 'never'
/** @type {string[]} */
const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
const autofix = Boolean(optionsPayload && optionsPayload.autofix)

const caseConverter = casing.getExactConverter(
useHyphenated ? 'kebab-case' : 'camelCase'
)

/**
* @param {VDirective} node
* @param {string} name
*/
function reportIssue(node, name) {
const text = sourceCode.getText(node.key)

context.report({
node: node.key,
loc: node.loc,
message: useHyphenated
? "v-on event '{{text}}' must be hyphenated."
: "v-on event '{{text}}' can't be hyphenated.",
data: {
text
},
fix: autofix
? (fixer) =>
fixer.replaceText(
node.key,
text.replace(name, caseConverter(name))
)
: null
})
}

/**
* @param {string} value
*/
function isIgnoredAttribute(value) {
const isIgnored = ignoredAttributes.some((attr) => {
return value.includes(attr)
})

if (isIgnored) {
return true
}

return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
}

return utils.defineTemplateBodyVisitor(context, {
"VAttribute[directive=true][key.name.name='on']"(node) {
if (!utils.isCustomComponent(node.parent.parent)) return

const name =
node.key.argument &&
node.key.argument.type === 'VIdentifier' &&
node.key.argument.rawName
if (!name || isIgnoredAttribute(name)) return

reportIssue(node, name)
}
})
}
}

0 comments on commit 01f7732

Please sign in to comment.