Skip to content

Commit 9fd6139

Browse files
committed
Add vue/v-on-event-hyphenation rule
1 parent 30e89ec commit 9fd6139

File tree

7 files changed

+334
-1
lines changed

7 files changed

+334
-1
lines changed

docs/rules/README.md

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

325326
### Extension Rules

docs/rules/attribute-hyphenation.md

Lines changed: 3 additions & 0 deletions
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']}]}">

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
## :mag: Implementation
106+
107+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-event-hyphenation.js)
108+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-event-hyphenation.js)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ module.exports = {
156156
'use-v-on-exact': require('./rules/use-v-on-exact'),
157157
'v-bind-style': require('./rules/v-bind-style'),
158158
'v-for-delimiter-style': require('./rules/v-for-delimiter-style'),
159+
'v-on-event-hyphenation': require('./rules/v-on-event-hyphenation'),
159160
'v-on-function-call': require('./rules/v-on-function-call'),
160161
'v-on-style': require('./rules/v-on-style'),
161162
'v-slot-style': require('./rules/v-slot-style'),

lib/rules/attribute-hyphenation.js

Lines changed: 1 addition & 1 deletion
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

Lines changed: 113 additions & 0 deletions
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+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use strict'
2+
3+
const RuleTester = require('eslint').RuleTester
4+
const rule = require('../../../lib/rules/v-on-event-hyphenation.js')
5+
6+
const tester = new RuleTester({
7+
parser: require.resolve('vue-eslint-parser'),
8+
parserOptions: {
9+
ecmaVersion: 2019
10+
}
11+
})
12+
13+
tester.run('v-on-event-hyphenation', rule, {
14+
valid: [
15+
`
16+
<template>
17+
<VueComponent @custom-event="onEvent"/>
18+
</template>
19+
`,
20+
`
21+
<template>
22+
<VueComponent :customEvent="onEvent"/>
23+
</template>
24+
`,
25+
`
26+
<template>
27+
<VueComponent v-on="events"/>
28+
</template>
29+
`,
30+
`
31+
<template>
32+
<div v-on:unknownEvent="onEvent"/>
33+
</template>
34+
`,
35+
{
36+
code: `
37+
<template>
38+
<VueComponent v-on:customEvent="events"/>
39+
</template>
40+
`,
41+
options: ['never']
42+
},
43+
{
44+
code: `
45+
<template>
46+
<VueComponent v-on:customEvent="events"/>
47+
</template>
48+
`,
49+
options: ['never', { ignore: ['custom'] }]
50+
}
51+
],
52+
invalid: [
53+
{
54+
code: `
55+
<template>
56+
<VueComponent @customEvent="onEvent"/>
57+
</template>
58+
`,
59+
output: null,
60+
errors: [
61+
{
62+
message: "v-on event '@customEvent' must be hyphenated.",
63+
line: 3,
64+
column: 25,
65+
endLine: 3,
66+
endColumn: 47
67+
}
68+
]
69+
},
70+
{
71+
code: `
72+
<template>
73+
<VueComponent @customEvent="onEvent"/>
74+
</template>
75+
`,
76+
options: ['always', { autofix: true }],
77+
output: `
78+
<template>
79+
<VueComponent @custom-event="onEvent"/>
80+
</template>
81+
`,
82+
errors: [
83+
{
84+
message: "v-on event '@customEvent' must be hyphenated.",
85+
line: 3,
86+
column: 25,
87+
endLine: 3,
88+
endColumn: 47
89+
}
90+
]
91+
},
92+
{
93+
code: `
94+
<template>
95+
<VueComponent v-on:custom-event="events"/>
96+
</template>
97+
`,
98+
options: ['never', { autofix: true }],
99+
output: `
100+
<template>
101+
<VueComponent v-on:customEvent="events"/>
102+
</template>
103+
`,
104+
errors: ["v-on event 'v-on:custom-event' can't be hyphenated."]
105+
}
106+
]
107+
})

0 commit comments

Comments
 (0)