Skip to content

Commit

Permalink
Add vue/v-on-event-hyphenation rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Dec 25, 2020
1 parent 30e89ec commit 9fd6139
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 1 deletion.
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
3 changes: 3 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 Down
108 changes: 108 additions & 0 deletions docs/rules/v-on-event-hyphenation.md
@@ -0,0 +1,108 @@
---
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>

## :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)
}
})
}
}
107 changes: 107 additions & 0 deletions tests/lib/rules/v-on-event-hyphenation.js
@@ -0,0 +1,107 @@
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/v-on-event-hyphenation.js')

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
ecmaVersion: 2019
}
})

tester.run('v-on-event-hyphenation', rule, {
valid: [
`
<template>
<VueComponent @custom-event="onEvent"/>
</template>
`,
`
<template>
<VueComponent :customEvent="onEvent"/>
</template>
`,
`
<template>
<VueComponent v-on="events"/>
</template>
`,
`
<template>
<div v-on:unknownEvent="onEvent"/>
</template>
`,
{
code: `
<template>
<VueComponent v-on:customEvent="events"/>
</template>
`,
options: ['never']
},
{
code: `
<template>
<VueComponent v-on:customEvent="events"/>
</template>
`,
options: ['never', { ignore: ['custom'] }]
}
],
invalid: [
{
code: `
<template>
<VueComponent @customEvent="onEvent"/>
</template>
`,
output: null,
errors: [
{
message: "v-on event '@customEvent' must be hyphenated.",
line: 3,
column: 25,
endLine: 3,
endColumn: 47
}
]
},
{
code: `
<template>
<VueComponent @customEvent="onEvent"/>
</template>
`,
options: ['always', { autofix: true }],
output: `
<template>
<VueComponent @custom-event="onEvent"/>
</template>
`,
errors: [
{
message: "v-on event '@customEvent' must be hyphenated.",
line: 3,
column: 25,
endLine: 3,
endColumn: 47
}
]
},
{
code: `
<template>
<VueComponent v-on:custom-event="events"/>
</template>
`,
options: ['never', { autofix: true }],
output: `
<template>
<VueComponent v-on:customEvent="events"/>
</template>
`,
errors: ["v-on event 'v-on:custom-event' can't be hyphenated."]
}
]
})

0 comments on commit 9fd6139

Please sign in to comment.