Skip to content

Commit 01f7732

Browse files
authored
Add vue/v-on-event-hyphenation rule (#1388)
* Add vue/v-on-event-hyphenation rule * update docs * Update doc
1 parent 543361b commit 01f7732

9 files changed

+362
-3
lines changed

docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ For example:
321321
| [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | |
322322
| [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: |
323323
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: |
324+
| [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md) | enforce v-on event naming style on custom components in template | :wrench: |
324325
| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: |
325326

326327
### Extension Rules

docs/rules/attribute-hyphenation.md

+8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Default casing is set to `always` with `['data-', 'aria-', 'slot-scope']` set to
4747
- `"ignore"` ... Array of ignored names
4848

4949
### `"always"`
50+
5051
It errors on upper case letters.
5152

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

6667
### `"never"`
68+
6769
It errors on hyphens except `data-`, `aria-` and `slot-scope`.
6870

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

8688
### `"never", { "ignore": ["custom-prop"] }`
89+
8790
Don't use hyphenated name but allow custom attributes
8891

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

105108
</eslint-code-block>
106109

110+
## :couple: Related Rules
111+
112+
- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
113+
- [vue/prop-name-casing](./prop-name-casing.md)
114+
107115
## :rocket: Version
108116

109117
This rule was introduced in eslint-plugin-vue v3.9.0

docs/rules/custom-event-name-casing.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ Vue 2 recommends using kebab-case for custom event names.
2323
2424
See [Guide (for v2) - Custom Events] for more details.
2525

26-
Vue 3 recommends using camelCase for custom event names.
26+
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.
2727

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

3030
This rule enforces kebab-case by default.
3131

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

174+
## :couple: Related Rules
175+
176+
- [vue/v-on-event-hyphenation](./v-on-event-hyphenation.md)
177+
- [vue/prop-name-casing](./prop-name-casing.md)
178+
174179
## :rocket: Version
175180

176181
This rule was introduced in eslint-plugin-vue v7.0.0

docs/rules/prop-name-casing.md

+5
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ export default {
7070

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

73+
## :couple: Related Rules
74+
75+
- [vue/attribute-hyphenation](./attribute-hyphenation.md)
76+
- [vue/custom-event-name-casing](./custom-event-name-casing.md)
77+
7378
## :rocket: Version
7479

7580
This rule was introduced in eslint-plugin-vue v4.3.0

docs/rules/v-on-event-hyphenation.md

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/v-on-event-hyphenation
5+
description: enforce v-on event naming style on custom components in template
6+
---
7+
# vue/v-on-event-hyphenation
8+
9+
> enforce v-on event naming style on custom components in template
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+
- :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.
13+
14+
## :book: Rule Details
15+
16+
This rule enforces using hyphenated v-on event names on custom components in Vue templates.
17+
18+
<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }]}">
19+
20+
```vue
21+
<template>
22+
<!-- ✓ GOOD -->
23+
<MyComponent v-on:custom-event="handleEvent"/>
24+
<MyComponent @custom-event="handleEvent"/>
25+
26+
<!-- ✗ BAD -->
27+
<MyComponent v-on:customEvent="handleEvent"/>
28+
<MyComponent @customEvent="handleEvent"/>
29+
</template>
30+
```
31+
32+
</eslint-code-block>
33+
34+
## :wrench: Options
35+
36+
```json
37+
{
38+
"vue/v-on-event-hyphenation": ["error", "always" | "never", {
39+
"autofix": false,
40+
"ignore": []
41+
}]
42+
}
43+
```
44+
45+
- `"always"` (default) ... Use hyphenated name.
46+
- `"never"` ... Don't use hyphenated name.
47+
- `"ignore"` ... Array of ignored names
48+
- `"autofix"` ... If `true`, enable autofix. If you are using Vue 2, we recommend that you do not use it due to its side effects.
49+
50+
### `"always"`
51+
52+
It errors on upper case letters.
53+
54+
<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }]}">
55+
56+
```vue
57+
<template>
58+
<!-- ✓ GOOD -->
59+
<MyComponent v-on:custom-event="handleEvent"/>
60+
61+
<!-- ✗ BAD -->
62+
<MyComponent v-on:customEvent="handleEvent"/>
63+
</template>
64+
```
65+
66+
</eslint-code-block>
67+
68+
### `"never"`
69+
70+
It errors on hyphens.
71+
72+
<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }]}">
73+
74+
```vue
75+
<template>
76+
<!-- ✓ GOOD -->
77+
<MyComponent v-on:customEvent="handleEvent"/>
78+
79+
<!-- ✗ BAD -->
80+
<MyComponent v-on:custom-event="handleEvent"/>
81+
</template>
82+
```
83+
84+
</eslint-code-block>
85+
86+
### `"never", { "ignore": ["custom-event"] }`
87+
88+
Don't use hyphenated name but allow custom event names
89+
90+
<eslint-code-block fix :rules="{'vue/v-on-event-hyphenation': ['error', 'never', { ignore: ['custom-event'], autofix: true }]}">
91+
92+
```vue
93+
<template>
94+
<!-- ✓ GOOD -->
95+
<MyComponent v-on:custom-event="handleEvent"/>
96+
<MyComponent v-on:myEvent="handleEvent"/>
97+
98+
<!-- ✗ BAD -->
99+
<MyComponent v-on:my-event="handleEvent"/>
100+
</template>
101+
```
102+
103+
</eslint-code-block>
104+
105+
## :books: Further Reading
106+
107+
- [Guide - Custom Events]
108+
109+
[Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html
110+
111+
## :couple: Related Rules
112+
113+
- [vue/custom-event-name-casing](./custom-event-name-casing.md)
114+
- [vue/attribute-hyphenation](./attribute-hyphenation.md)
115+
116+
## :mag: Implementation
117+
118+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js)
119+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-event-hyphenation.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ module.exports = {
157157
'use-v-on-exact': require('./rules/use-v-on-exact'),
158158
'v-bind-style': require('./rules/v-bind-style'),
159159
'v-for-delimiter-style': require('./rules/v-for-delimiter-style'),
160+
'v-on-event-hyphenation': require('./rules/v-on-event-hyphenation'),
160161
'v-on-function-call': require('./rules/v-on-function-call'),
161162
'v-on-style': require('./rules/v-on-style'),
162163
'v-slot-style': require('./rules/v-slot-style'),

lib/rules/attribute-hyphenation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ module.exports = {
8787
*/
8888
function isIgnoredAttribute(value) {
8989
const isIgnored = ignoredAttributes.some((attr) => {
90-
return value.indexOf(attr) !== -1
90+
return value.includes(attr)
9191
})
9292

9393
if (isIgnored) {

lib/rules/v-on-event-hyphenation.js

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use strict'
2+
3+
const utils = require('../utils')
4+
const casing = require('../utils/casing')
5+
6+
module.exports = {
7+
meta: {
8+
docs: {
9+
description:
10+
'enforce v-on event naming style on custom components in template',
11+
// TODO Change with major version.
12+
// categories: ['vue3-strongly-recommended'],
13+
categories: undefined,
14+
url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html'
15+
},
16+
fixable: 'code',
17+
schema: [
18+
{
19+
enum: ['always', 'never']
20+
},
21+
{
22+
type: 'object',
23+
properties: {
24+
autofix: { type: 'boolean' },
25+
ignore: {
26+
type: 'array',
27+
items: {
28+
allOf: [
29+
{ type: 'string' },
30+
{ not: { type: 'string', pattern: ':exit$' } },
31+
{ not: { type: 'string', pattern: '^\\s*$' } }
32+
]
33+
},
34+
uniqueItems: true,
35+
additionalItems: false
36+
}
37+
},
38+
additionalProperties: false
39+
}
40+
],
41+
type: 'suggestion'
42+
},
43+
44+
/** @param {RuleContext} context */
45+
create(context) {
46+
const sourceCode = context.getSourceCode()
47+
const option = context.options[0]
48+
const optionsPayload = context.options[1]
49+
const useHyphenated = option !== 'never'
50+
/** @type {string[]} */
51+
const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
52+
const autofix = Boolean(optionsPayload && optionsPayload.autofix)
53+
54+
const caseConverter = casing.getExactConverter(
55+
useHyphenated ? 'kebab-case' : 'camelCase'
56+
)
57+
58+
/**
59+
* @param {VDirective} node
60+
* @param {string} name
61+
*/
62+
function reportIssue(node, name) {
63+
const text = sourceCode.getText(node.key)
64+
65+
context.report({
66+
node: node.key,
67+
loc: node.loc,
68+
message: useHyphenated
69+
? "v-on event '{{text}}' must be hyphenated."
70+
: "v-on event '{{text}}' can't be hyphenated.",
71+
data: {
72+
text
73+
},
74+
fix: autofix
75+
? (fixer) =>
76+
fixer.replaceText(
77+
node.key,
78+
text.replace(name, caseConverter(name))
79+
)
80+
: null
81+
})
82+
}
83+
84+
/**
85+
* @param {string} value
86+
*/
87+
function isIgnoredAttribute(value) {
88+
const isIgnored = ignoredAttributes.some((attr) => {
89+
return value.includes(attr)
90+
})
91+
92+
if (isIgnored) {
93+
return true
94+
}
95+
96+
return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
97+
}
98+
99+
return utils.defineTemplateBodyVisitor(context, {
100+
"VAttribute[directive=true][key.name.name='on']"(node) {
101+
if (!utils.isCustomComponent(node.parent.parent)) return
102+
103+
const name =
104+
node.key.argument &&
105+
node.key.argument.type === 'VIdentifier' &&
106+
node.key.argument.rawName
107+
if (!name || isIgnoredAttribute(name)) return
108+
109+
reportIssue(node, name)
110+
}
111+
})
112+
}
113+
}

0 commit comments

Comments
 (0)