Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vue/v-on-event-hyphenation rule #1388

Merged
merged 3 commits into from Dec 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -320,6 +320,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 @@ -156,6 +156,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)
}
})
}
}