Skip to content

feat(v-on-handler-style): allow ["inline", "inline-function"] option #2471

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion docs/rules/v-on-handler-style.md
Original file line number Diff line number Diff line change
@@ -47,7 +47,7 @@ This rule aims to enforce a consistent style in `v-on` event handlers:
```json
{
"vue/v-on-handler-style": ["error",
["method", "inline-function"], // ["method", "inline-function"] | ["method", "inline"] | "inline-function" | "inline"
["method", "inline-function"], // ["method", "inline-function"] | ["method", "inline"] | ["inline", "inline-function"] | "inline-function" | "inline"
{
"ignoreIncludesComment": false
}
@@ -58,10 +58,12 @@ This rule aims to enforce a consistent style in `v-on` event handlers:
- First option ... Specifies the name of an allowed style. Default is `["method", "inline-function"]`.
- `["method", "inline-function"]` ... Allow handlers by method binding. e.g. `v-on:click="handler"`. Allow inline functions where method handlers cannot be used. e.g. `v-on:click="() => handler(listItem)"`.
- `["method", "inline"]` ... Allow handlers by method binding. e.g. `v-on:click="handler"`. Allow inline handlers where method handlers cannot be used. e.g. `v-on:click="handler(listItem)"`.
- `["inline", "inline-function"]` ... Allow inline handlers. e.g. `v-on:click="handler()"`. Allow inline functions if they have at least 1 argument. e.g. `v-on:click="(arg1, arg2) => handler(arg1, arg2)"`.
- `"inline-function"` ... Allow inline functions. e.g. `v-on:click="() => handler()"`
- `"inline"` ... Allow inline handlers. e.g. `v-on:click="handler()"`
- Second option
- `ignoreIncludesComment` ... If `true`, do not report inline handlers or inline functions containing comments, even if the preferred style is `"method"`. Default is `false`.
- `allowInlineFuncSingleArg` ... Used in conjunction with `["method", "inline-function"]` or `["inline", "inline-function"]`. If `true`, allow inline functions with a single argument. Default is `false`.

### `["method", "inline-function"]` (Default)

@@ -121,6 +123,68 @@ This rule aims to enforce a consistent style in `v-on` event handlers:

</eslint-code-block>

### `["inline", "inline-function"]`

<eslint-code-block fix :rules="{'vue/v-on-handler-style': ['error', ['inline', 'inline-function']]}">

```vue
<template>
<!-- ✓ GOOD -->
<button v-on:click="count++" />
<button v-on:click="handler()" />
<button v-on:click="handler($event)" />
<button v-on:click="(arg1, arg2) => handler(arg1, arg2)" />
<template v-for="e in list">
<button v-on:click="handler(e)" />
<button v-on:click="handler($event, e)" />
<button v-on:click="(arg1, arg2) => handler(arg1, arg2, e)" />
</template>
<!-- ✗ BAD -->
<button v-on:click="() => count++" />
<button v-on:click="handler" />
<button v-on:click="() => handler()" />
<button v-on:click="(arg) => handler(arg)" />
<template v-for="e in list">
<button v-on:click="() => handler(e)" />
<button v-on:click="(arg) => handler(arg, e)" />
</template>
</template>
```

</eslint-code-block>

### `["inline", "inline-function"]` with `allowInlineFuncSingleArg: true`

<eslint-code-block fix :rules="{'vue/v-on-handler-style': ['error', ['inline', 'inline-function'], { allowInlineFuncSingleArg: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<button v-on:click="count++" />
<button v-on:click="handler()" />
<button v-on:click="handler($event)" />
<button v-on:click="(arg) => handler(arg)" />
<button v-on:click="(arg1, arg2) => handler(arg1, arg2)" />
<template v-for="e in list">
<button v-on:click="handler(e)" />
<button v-on:click="handler($event, e)" />
<button v-on:click="(arg) => handler(arg, e)" />
<button v-on:click="(arg1, arg2) => handler(arg1, arg2, e)" />
</template>
<!-- ✗ BAD -->
<button v-on:click="() => count++" />
<button v-on:click="handler" />
<button v-on:click="() => handler()" />
<template v-for="e in list">
<button v-on:click="() => handler(e)" />
</template>
</template>
```

</eslint-code-block>

### `"inline-function"`

<eslint-code-block fix :rules="{'vue/v-on-handler-style': ['error', 'inline-function']}">
113 changes: 85 additions & 28 deletions lib/rules/v-on-handler-style.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ const utils = require('../utils')
* @typedef {'method' | 'inline' | 'inline-function'} HandlerKind
* @typedef {object} ObjectOption
* @property {boolean} [ignoreIncludesComment]
* @property {boolean} [allowInlineFuncSingleArg]
*/

/**
@@ -33,8 +34,9 @@ function parseOptions(context) {

const option = options[1] || {}
const ignoreIncludesComment = !!option.ignoreIncludesComment
const allowInlineFuncSingleArg = option.allowInlineFuncSingleArg === true

return { allows, ignoreIncludesComment }
return { allows, ignoreIncludesComment, allowInlineFuncSingleArg }
}

/**
@@ -112,33 +114,73 @@ module.exports = {
url: 'https://eslint.vuejs.org/rules/v-on-handler-style.html'
},
fixable: 'code',
schema: [
{
oneOf: [
{ enum: ['inline', 'inline-function'] },
{
type: 'array',
items: [
{ const: 'method' },
{ enum: ['inline', 'inline-function'] }
],
uniqueItems: true,
additionalItems: false,
minItems: 2,
maxItems: 2
}
]
},
{
type: 'object',
properties: {
ignoreIncludesComment: {
type: 'boolean'
}
schema: {
anyOf: [
// `inline`, `inline-function` or `['method', 'inline']`
{
type: 'array',
items: [
{
anyOf: [
{ enum: ['inline', 'inline-function'] },
{
type: 'array',
items: [{ const: 'method' }, { const: 'inline' }],
uniqueItems: true,
additionalItems: false,
minItems: 2,
maxItems: 2
}
]
},
{
type: 'object',
properties: {
ignoreIncludesComment: {
type: 'boolean'
}
},
additionalProperties: false
}
],
additionalItems: false,
minItems: 1,
maxItems: 2
},
additionalProperties: false
}
],
// `['method', 'inline-function']` or `['inline', 'inline-function']`
{
type: 'array',
items: [
{
type: 'array',
items: [
{ enum: ['method', 'inline'] },
{ const: 'inline-function' }
],
uniqueItems: true,
additionalItems: false,
minItems: 2,
maxItems: 2
},
{
type: 'object',
properties: {
ignoreIncludesComment: {
type: 'boolean'
},
allowInlineFuncSingleArg: {
type: 'boolean'
}
},
additionalProperties: false
}
],
additionalItems: false,
minItems: 0,
maxItems: 2
}
]
},
messages: {
preferMethodOverInline:
'Prefer method handler over inline handler in v-on.',
@@ -162,7 +204,8 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
const { allows, ignoreIncludesComment } = parseOptions(context)
const { allows, ignoreIncludesComment, allowInlineFuncSingleArg } =
parseOptions(context)

/** @type {Set<VElement>} */
const upperElements = new Set()
@@ -530,6 +573,20 @@ module.exports = {
if (allows[0] === 'inline-function') {
return
}

if (allows[1] === 'inline-function') {
if (expression.params.length > 1) {
return
}

if (
expression.params.length === 1 &&
allowInlineFuncSingleArg
) {
return
}
}

for (const allow of allows) {
if (verifyForInlineFunction(expression, allow)) {
return
196 changes: 196 additions & 0 deletions tests/lib/rules/v-on-handler-style.js
Original file line number Diff line number Diff line change
@@ -71,6 +71,41 @@ tester.run('v-on-handler-style', rule, {
{
filename: 'test.vue',
code: '<template><button :click="foo()" /></template>'
},
{
filename: 'test.vue',
code: `<template>
<button @click="value++" />
<button @click="foo()" />
<button @click="foo($event)" />
<button @click="(a, b) => foo(a, b)" />
<template v-for="e in list">
<button @click="foo(e)" />
<button @click="foo($event, e)" />
<button @click="(a, b) => foo(a, b, e)" />
</template>
</template>`,
options: [['inline', 'inline-function']]
},
{
filename: 'test.vue',
code: `<template>
<button @click="value++" />
<button @click="foo()" />
<button @click="foo($event)" />
<button @click="(evt) => foo(evt)" />
<button @click="(a, b) => foo(a, b)" />
<template v-for="e in list">
<button @click="foo(e)" />
<button @click="foo($event, e)" />
<button @click="(evt) => foo(evt, e)" />
<button @click="(a, b) => foo(a, b, e)" />
</template>
</template>`,
options: [
['inline', 'inline-function'],
{ allowInlineFuncSingleArg: true }
]
}
],
invalid: [
@@ -191,6 +226,30 @@ tester.run('v-on-handler-style', rule, {
}
]
},
{
filename: 'test.vue',
code: `<template>
<div @click="() => foo()" />
<div @click="(event) => foo(event)" />
</template>`,
output: `<template>
<div @click="foo" />
<div @click="foo" />
</template>`,
options: [['method', 'inline-function']],
errors: [
{
message: 'Prefer method handler over inline function in v-on.',
line: 2,
column: 22
},
{
message: 'Prefer method handler over inline function in v-on.',
line: 3,
column: 22
}
]
},
{
filename: 'test.vue',
code: `<template>
@@ -1136,6 +1195,143 @@ tester.run('v-on-handler-style', rule, {
column: 25
}
]
},
// ['inline', 'inline-function']
{
filename: 'test.vue',
code: `<template>
<button @click="() => value++" />
<button @click="foo" />
<button @click="() => foo()" />
<button @click="() => foo($event)" />
<button @click="(event) => foo(event)" />
<template v-for="e in list">
<button @click="(event) => foo(event, e)" />
</template>
</template>`,
output: `<template>
<button @click="value++" />
<button @click="foo" />
<button @click="foo()" />
<button @click="foo($event)" />
<button @click="(event) => foo(event)" />
<template v-for="e in list">
<button @click="(event) => foo(event, e)" />
</template>
</template>`,
options: [['inline', 'inline-function']],
errors: [
{
message: 'Prefer inline handler over inline function in v-on.',
line: 2,
column: 25
},
{
message: 'Prefer inline handler over method handler in v-on.',
line: 3,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 4,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 5,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 6,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 8,
column: 27
}
]
},
// ['inline', 'inline-function'] + allowInlineFuncSingleArg
{
filename: 'test.vue',
code: `<template>
<button @click="() => value++" />
<button @click="foo" />
<button @click="() => foo()" />
<button @click="() => foo($event)" />
<button @click="(event) => foo(event)" />
<template v-for="e in list">
<button @click="() => foo(e)" />
<button @click="(event) => foo(event, e)" />
</template>
</template>`,
output: `<template>
<button @click="value++" />
<button @click="foo" />
<button @click="foo()" />
<button @click="foo($event)" />
<button @click="(event) => foo(event)" />
<template v-for="e in list">
<button @click="foo(e)" />
<button @click="(event) => foo(event, e)" />
</template>
</template>`,
options: [
['inline', 'inline-function'],
{ allowInlineFuncSingleArg: true }
],
errors: [
{
message: 'Prefer inline handler over inline function in v-on.',
line: 2,
column: 25
},
{
message: 'Prefer inline handler over method handler in v-on.',
line: 3,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 4,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 5,
column: 25
},
{
message: 'Prefer inline handler over inline function in v-on.',
line: 8,
column: 27
}
]
},
// ['method', 'inline-function'] + allowInlineFuncSingleArg
{
filename: 'test.vue',
code: `<template>
<div @click="() => foo()" />
<div @click="(event) => foo(event)" />
</template>`,
output: `<template>
<div @click="foo" />
<div @click="(event) => foo(event)" />
</template>`,
options: [
['method', 'inline-function'],
{ allowInlineFuncSingleArg: true }
],
errors: [
{
message: 'Prefer method handler over inline function in v-on.',
line: 2,
column: 22
}
]
}
]
})