Skip to content

Commit 6d6203f

Browse files
authored
Add casing option to vue/custom-event-name-casing rule & remove from configs. (#1364)
* Add casing option to vue/custom-event-name-casing rule & remove from configs. * update
1 parent af5c4c0 commit 6d6203f

File tree

6 files changed

+227
-46
lines changed

6 files changed

+227
-46
lines changed

docs/rules/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
3939

4040
| Rule ID | Description | |
4141
|:--------|:------------|:---|
42-
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce custom event names always use "kebab-case" | |
4342
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
4443
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
4544
| [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: |
@@ -172,7 +171,6 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
172171

173172
| Rule ID | Description | |
174173
|:--------|:------------|:---|
175-
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce custom event names always use "kebab-case" | |
176174
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
177175
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
178176
| [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | |
@@ -290,6 +288,7 @@ For example:
290288
|:--------|:------------|:---|
291289
| [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: |
292290
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
291+
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | |
293292
| [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: |
294293
| [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: |
295294
| [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: |

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

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,30 @@
22
pageClass: rule-details
33
sidebarDepth: 0
44
title: vue/custom-event-name-casing
5-
description: enforce custom event names always use "kebab-case"
5+
description: enforce specific casing for custom event name
66
---
77
# vue/custom-event-name-casing
8-
> enforce custom event names always use "kebab-case"
8+
> enforce specific casing for custom event name
99
10-
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
10+
Define a style for custom event name casing for consistency purposes.
1111

1212
## :book: Rule Details
1313

14-
This rule enforces using kebab-case custom event names.
14+
This rule aims to warn the custom event names other than the configured casing.
15+
16+
Vue 2 recommends using kebab-case for custom event names.
1517

1618
> Event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, `v-on` event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so `v-on:myEvent` would become `v-on:myevent` – making `myEvent` impossible to listen to.
1719
>
1820
> For these reasons, we recommend you **always use kebab-case for event names**.
1921
20-
See [Guide - Custom Events] for more details.
22+
See [Guide (for v2) - Custom Events] for more details.
23+
24+
Vue 3 recommends using camelCase for custom event names.
25+
26+
See [vuejs/docs-next#656](https://github.com/vuejs/docs-next/issues/656) for more details.
27+
28+
This rule enforces kebab-case by default.
2129

2230
<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error']}">
2331

@@ -51,14 +59,77 @@ export default {
5159

5260
```json
5361
{
54-
"vue/custom-event-name-casing": ["error", {
55-
"ignores": []
56-
}]
62+
"vue/custom-event-name-casing": ["error",
63+
"kebab-case" | "camelCase",
64+
{
65+
"ignores": []
66+
}
67+
]
5768
}
5869
```
5970

71+
- `"kebab-case"` (default) ... Enforce custom event names to kebab-case.
72+
- `"camelCase"` ... Enforce custom event names to camelCase.
6073
- `ignores` (`string[]`) ... The event names to ignore. Sets the event name to allow. For example, custom event names, Vue components event with special name, or Vue library component event name. You can set the regexp by writing it like `"/^name/"` or `click:row` or `fooBar`.
6174

75+
### `"kebab-case"`
76+
77+
<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', 'kebab-case']}">
78+
79+
```vue
80+
<template>
81+
<!-- ✓ GOOD -->
82+
<button @click="$emit('my-event')" />
83+
84+
<!-- ✗ BAD -->
85+
<button @click="$emit('myEvent')" />
86+
</template>
87+
<script>
88+
export default {
89+
methods: {
90+
onClick () {
91+
/* ✓ GOOD */
92+
this.$emit('my-event')
93+
94+
/* ✗ BAD */
95+
this.$emit('myEvent')
96+
}
97+
}
98+
}
99+
</script>
100+
```
101+
102+
</eslint-code-block>
103+
104+
### `"camelCase"`
105+
106+
<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', 'camelCase']}">
107+
108+
```vue
109+
<template>
110+
<!-- ✓ GOOD -->
111+
<button @click="$emit('myEvent')" />
112+
113+
<!-- ✗ BAD -->
114+
<button @click="$emit('my-event')" />
115+
</template>
116+
<script>
117+
export default {
118+
methods: {
119+
onClick () {
120+
/* ✓ GOOD */
121+
this.$emit('myEvent')
122+
123+
/* ✗ BAD */
124+
this.$emit('my-event')
125+
}
126+
}
127+
}
128+
</script>
129+
```
130+
131+
</eslint-code-block>
132+
62133
### `"ignores": ["fooBar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
63134

64135
<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', { ignores: ['fooBar', '/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]}">
@@ -93,6 +164,7 @@ export default {
93164
## :books: Further Reading
94165

95166
- [Guide - Custom Events]
167+
- [Guide (for v2) - Custom Events]
96168

97169
[Guide - Custom Events]: https://v3.vuejs.org/guide/component-custom-events.html
98170
[Guide (for v2) - Custom Events]: https://vuejs.org/v2/guide/components-custom-events.html

lib/configs/essential.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
module.exports = {
77
extends: require.resolve('./base'),
88
rules: {
9-
'vue/custom-event-name-casing': 'error',
109
'vue/no-arrow-functions-in-watch': 'error',
1110
'vue/no-async-in-computed-properties': 'error',
1211
'vue/no-custom-modifiers-on-v-model': 'error',

lib/configs/vue3-essential.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
module.exports = {
77
extends: require.resolve('./base'),
88
rules: {
9-
'vue/custom-event-name-casing': 'error',
109
'vue/no-arrow-functions-in-watch': 'error',
1110
'vue/no-async-in-computed-properties': 'error',
1211
'vue/no-deprecated-data-object-declaration': 'error',

lib/rules/custom-event-name-casing.js

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,15 @@
1010

1111
const { findVariable } = require('eslint-utils')
1212
const utils = require('../utils')
13-
const { isKebabCase } = require('../utils/casing')
13+
const casing = require('../utils/casing')
1414
const { toRegExp } = require('../utils/regexp')
1515

1616
// ------------------------------------------------------------------------------
1717
// Helpers
1818
// ------------------------------------------------------------------------------
1919

20-
/**
21-
* Check whether the given event name is valid.
22-
* @param {string} name The name to check.
23-
* @returns {boolean} `true` if the given event name is valid.
24-
*/
25-
function isValidEventName(name) {
26-
return isKebabCase(name) || name.startsWith('update:')
27-
}
20+
const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
21+
const DEFAULT_CASE = 'kebab-case'
2822

2923
/**
3024
* Get the name param node from the given CallExpression
@@ -64,53 +58,87 @@ function getCalleeMemberNode(node) {
6458
// Rule Definition
6559
// ------------------------------------------------------------------------------
6660

61+
const OBJECT_OPTION_SCHEMA = {
62+
type: 'object',
63+
properties: {
64+
ignores: {
65+
type: 'array',
66+
items: { type: 'string' },
67+
uniqueItems: true,
68+
additionalItems: false
69+
}
70+
},
71+
additionalProperties: false
72+
}
6773
module.exports = {
6874
meta: {
6975
type: 'suggestion',
7076
docs: {
71-
description: 'enforce custom event names always use "kebab-case"',
72-
categories: ['vue3-essential', 'essential'],
77+
description: 'enforce specific casing for custom event name',
78+
categories: undefined,
7379
url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html'
7480
},
7581
fixable: null,
76-
schema: [
77-
{
78-
type: 'object',
79-
properties: {
80-
ignores: {
81-
type: 'array',
82-
items: { type: 'string' },
83-
uniqueItems: true,
84-
additionalItems: false
85-
}
82+
schema: {
83+
anyOf: [
84+
{
85+
type: 'array',
86+
items: [
87+
{
88+
enum: ALLOWED_CASE_OPTIONS
89+
},
90+
OBJECT_OPTION_SCHEMA
91+
]
8692
},
87-
additionalProperties: false
88-
}
89-
],
93+
// For backward compatibility
94+
{
95+
type: 'array',
96+
items: [OBJECT_OPTION_SCHEMA]
97+
}
98+
]
99+
},
90100
messages: {
91-
unexpected: "Custom event name '{{name}}' must be kebab-case."
101+
unexpected: "Custom event name '{{name}}' must be {{caseType}}."
92102
}
93103
},
94104
/** @param {RuleContext} context */
95105
create(context) {
106+
/** @type {Map<ObjectExpression, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
96107
const setupContexts = new Map()
97-
const options = context.options[0] || {}
108+
const options =
109+
context.options.length === 1 && typeof context.options[0] !== 'string'
110+
? // For backward compatibility
111+
[undefined, context.options[0]]
112+
: context.options
113+
const caseType = options[0] || DEFAULT_CASE
114+
const objectOption = options[1] || {}
115+
const caseChecker = casing.getChecker(caseType)
98116
/** @type {RegExp[]} */
99-
const ignores = (options.ignores || []).map(toRegExp)
117+
const ignores = (objectOption.ignores || []).map(toRegExp)
118+
119+
/**
120+
* Check whether the given event name is valid.
121+
* @param {string} name The name to check.
122+
* @returns {boolean} `true` if the given event name is valid.
123+
*/
124+
function isValidEventName(name) {
125+
return caseChecker(name) || name.startsWith('update:')
126+
}
100127

101128
/**
102129
* @param { Literal & { value: string } } nameLiteralNode
103130
*/
104131
function verify(nameLiteralNode) {
105132
const name = nameLiteralNode.value
106-
if (ignores.some((re) => re.test(name)) || isValidEventName(name)) {
133+
if (isValidEventName(name) || ignores.some((re) => re.test(name))) {
107134
return
108135
}
109136
context.report({
110137
node: nameLiteralNode,
111138
messageId: 'unexpected',
112139
data: {
113-
name
140+
name,
141+
caseType
114142
}
115143
})
116144
}
@@ -190,14 +218,18 @@ module.exports = {
190218
const setupContext = setupContexts.get(vueNode)
191219
if (setupContext) {
192220
const { contextReferenceIds, emitReferenceIds } = setupContext
193-
if (emitReferenceIds.has(node.callee)) {
221+
if (
222+
node.callee.type === 'Identifier' &&
223+
emitReferenceIds.has(node.callee)
224+
) {
194225
// verify setup(props,{emit}) {emit()}
195226
verify(nameLiteralNode)
196227
} else {
197228
const emit = getCalleeMemberNode(node)
198229
if (
199230
emit &&
200231
emit.name === 'emit' &&
232+
emit.member.object.type === 'Identifier' &&
201233
contextReferenceIds.has(emit.member.object)
202234
) {
203235
// verify setup(props,context) {context.emit()}

0 commit comments

Comments
 (0)