From f099cbcf3764ee5507b2ca702f83d7a12513a474 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Sat, 8 Oct 2022 15:03:58 +0900 Subject: [PATCH 01/15] Add `vue/v-on-handler-style` rule and deprecated `vue/v-on-function-call` rule --- docs/rules/README.md | 3 +- docs/rules/v-on-function-call.md | 1 + docs/rules/v-on-handler-style.md | 153 +++++ lib/index.js | 1 + lib/rules/v-on-function-call.js | 4 +- lib/rules/v-on-handler-style.js | 527 +++++++++++++++ tests/lib/rules/v-on-handler-style.js | 913 ++++++++++++++++++++++++++ typings/eslint/index.d.ts | 10 +- 8 files changed, 1604 insertions(+), 8 deletions(-) create mode 100644 docs/rules/v-on-handler-style.md create mode 100644 lib/rules/v-on-handler-style.js create mode 100644 tests/lib/rules/v-on-handler-style.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 19c2565e4..94e0c77c8 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -264,7 +264,7 @@ For example: | [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components | | :hammer: | | [vue/static-class-names-order](./static-class-names-order.md) | enforce static class names order | :wrench: | :hammer: | | [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: | -| [vue/v-on-function-call](./v-on-function-call.md) | enforce or forbid parentheses after method calls without arguments in `v-on` directives | :wrench: | :hammer: | +| [vue/v-on-handler-style](./v-on-handler-style.md) | enforce writing style for handlers in `v-on` directives | :wrench: | :hammer: | @@ -324,6 +324,7 @@ The following rules extend the rules provided by ESLint itself and apply them to |:--------|:------------| | [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | [vue/valid-model-definition](./valid-model-definition.md) | | [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | (no replacement) | +| [vue/v-on-function-call](./v-on-function-call.md) | [vue/v-on-handler-style](./v-on-handler-style.md) | ## Removed diff --git a/docs/rules/v-on-function-call.md b/docs/rules/v-on-function-call.md index edd48f084..c80dafc60 100644 --- a/docs/rules/v-on-function-call.md +++ b/docs/rules/v-on-function-call.md @@ -9,6 +9,7 @@ since: v5.2.0 > enforce or forbid parentheses after method calls without arguments in `v-on` directives +- :warning: This rule was **deprecated** and replaced by [vue/v-on-handler-style](v-on-handler-style.md) rule. - :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 diff --git a/docs/rules/v-on-handler-style.md b/docs/rules/v-on-handler-style.md new file mode 100644 index 000000000..f99d6f495 --- /dev/null +++ b/docs/rules/v-on-handler-style.md @@ -0,0 +1,153 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/v-on-handler-style +description: enforce writing style for handlers in `v-on` directives +--- +# vue/v-on-handler-style + +> enforce writing style for handlers in `v-on` directives + +- :exclamation: ***This rule has not been released yet.*** +- :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 aims to enforce using method handlers on `v-on`, using inline handlers on `v-on`, or binding inline functions to `v-on`. + +```vue + + + + + + +``` + + + +```vue + +``` + + + +## :wrench: Options + +```json +{ + "vue/v-on-handler-style": ["error", + [ + "method", + "inline-function" + ], + { + "ignoreIncludesComment": false + } + ] +} +``` + +- First option ... An array of names of allowed styles. Default is `["method", "inline-function"]`. + - `"method"` ... Allow handlers by method binding. e.g. `v-on:click="handler"` + - `"inline"` ... Allow inline handlers. e.g. `v-on:click="handler()"` + Even if this is not specified, writing styles that cannot be converted to other allowed styles are allowed. + - `"inline-function"` ... Allow inline functions. e.g. `v-on:click="() => handler()"` + Even if this is not specified, writing styles that cannot be converted to other allowed styles are allowed. +- Second option + - `ignoreIncludesComment` ... If `true`, do not report expressions containing comments. This option takes effect if `"method"` is allowed. Default is `false`. + +### `["method"]` + + + +```vue + +``` + + + +### `["inline"]` + + + +```vue + +``` + + + +### `["inline-function"]` + + + +```vue + +``` + + + +### `["method"], { "ignoreIncludesComment": true }` + + + +```vue + +``` + + + +## :books: Further Reading + +- [Guide - Inline Handlers] +- [Guide - Method Handlers] + +[Guide - Inline Handlers]: https://vuejs.org/guide/essentials/event-handling.html#inline-handlers +[Guide - Method Handlers]: https://vuejs.org/guide/essentials/event-handling.html#method-handlers + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/v-on-handler-style.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/v-on-handler-style.js) diff --git a/lib/index.js b/lib/index.js index 645669c4c..6f5452cc8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -200,6 +200,7 @@ module.exports = { '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-handler-style': require('./rules/v-on-handler-style'), 'v-on-style': require('./rules/v-on-style'), 'v-slot-style': require('./rules/v-slot-style'), 'valid-attribute-name': require('./rules/valid-attribute-name'), diff --git a/lib/rules/v-on-function-call.js b/lib/rules/v-on-function-call.js index b72548daa..ea9744822 100644 --- a/lib/rules/v-on-function-call.js +++ b/lib/rules/v-on-function-call.js @@ -94,7 +94,9 @@ module.exports = { }, additionalProperties: false } - ] + ], + deprecated: true, + replacedBy: ['v-on-handler-style'] }, /** @param {RuleContext} context */ create(context) { diff --git a/lib/rules/v-on-handler-style.js b/lib/rules/v-on-handler-style.js new file mode 100644 index 000000000..01ee41ecc --- /dev/null +++ b/lib/rules/v-on-handler-style.js @@ -0,0 +1,527 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const utils = require('../utils') + +/** + * @typedef {'method' | 'inline' | 'inline-function'} HandlerKind + * @typedef {HandlerKind[]} HandlerKinds + * @typedef {object} ObjectOption + * @property {boolean} [ignoreIncludesComment] + */ + +/** + * @param {RuleContext} context + */ +function parseOptions(context) { + /** @type {[HandlerKinds | undefined, ObjectOption | undefined]} */ + const options = /** @type {any} */ (context.options) + /** @type {Set} */ + const allows = new Set() + if (options[0]) { + for (const o of options[0]) allows.add(o) + } else { + allows.add('method') + allows.add('inline-function') + } + + const option = options[1] || {} + const ignoreIncludesComment = !!option.ignoreIncludesComment + + return { allows: [...allows], ignoreIncludesComment } +} + +/** + * @param {Iterable} allows + */ +function getHandlerKindsPhrase(allows) { + /** @type {Record} */ + const map = { + method: 'method handler', + inline: 'inline handler', + 'inline-function': 'inline function' + } + + const allowsPhrase = [...allows].map((s) => map[s]) + switch (allowsPhrase.length) { + case 1: + return allowsPhrase[0] + default: + return `${allowsPhrase.slice(0, -1).join(', ')}, or ${ + allowsPhrase[allowsPhrase.length - 1] + }` + } +} + +/** + * Check whether the given token is a quote. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a quote. + */ +function isQuote(token) { + return ( + token != null && + token.type === 'Punctuator' && + (token.value === '"' || token.value === "'") + ) +} +/** + * Check whether the given node is an identifier call expression. e.g. `foo()` + * @param {Expression} node The node to check. + * @returns {node is CallExpression & {callee: Identifier}} + */ +function isIdentifierCallExpression(node) { + if (node.type !== 'CallExpression') { + return false + } + if (node.optional) { + // optional chaining + return false + } + const callee = node.callee + return callee.type === 'Identifier' +} + +/** + * Returns a call expression node if the given VOnExpression or BlockStatement consists + * of only a single identifier call expression. + * e.g. + * @click="foo()" + * @click="{ foo() }" + * @click="foo();;" + * @param {VOnExpression | BlockStatement} node + * @returns {CallExpression & {callee: Identifier} | null} + */ +function getIdentifierCallExpression(node) { + /** @type {ExpressionStatement} */ + let exprStatement + let body = node.body + while (true) { + const statements = body.filter((st) => st.type !== 'EmptyStatement') + if (statements.length !== 1) { + return null + } + const statement = statements[0] + if (statement.type === 'ExpressionStatement') { + exprStatement = statement + break + } + if (statement.type === 'BlockStatement') { + body = statement.body + continue + } + return null + } + const expression = exprStatement.expression + if (!isIdentifierCallExpression(expression)) { + return null + } + return expression +} + +module.exports = { + meta: { + hasSuggestions: true, + type: 'suggestion', + docs: { + description: 'enforce writing style for handlers in `v-on` directives', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/v-on-handler-style.html' + }, + fixable: 'code', + schema: [ + { + type: 'array', + items: { enum: ['method', 'inline', 'inline-function'] }, + uniqueItems: true, + additionalItems: false, + minItems: 1, + maxItems: 2 + }, + { + type: 'object', + properties: { + ignoreIncludesComment: { + type: 'boolean' + } + }, + additionalProperties: false + } + ], + messages: { + preferXOverInline: 'Prefer {{allows}} over inline handler in v-on.', + preferXOverInlineFunction: + 'Prefer {{allows}} over inline function in v-on.', + disallowMethodHandler: 'Method handlers are not allowed. Use {{allows}}.', + useMethodInsteadOfInline: 'Use method handler instead of inline handler.', + useInlineFunctionInsteadOfInline: + 'Use inline function instead of inline handler.', + useMethodInsteadOfInlineFunction: + 'Use method handler instead of inline function.', + useInlineInsteadOfInlineFunction: + 'Use inline handler instead of inline function.' + } + }, + /** @param {RuleContext} context */ + create(context) { + const { allows, ignoreIncludesComment } = parseOptions(context) + + /** @type {Map} */ + const methodArgumentCountMap = new Map() + /** @type {Identifier[]} */ + const $eventIdentifiers = [] + + /** + * @typedef {object} ReportResult + * @property {import('eslint').ReportDescriptorFix | null} fix + * @property {string} suggestMessageId + * @property {HandlerKind} kind + */ + + const VERIFY_INLINE_HANDLERS = { + method: verifyInlineHandlerCanBeReplacedWithMethodHandler, + 'inline-function': + verifyInlineHandlerCanBeReplacedWithInlineFunctionHandler, + inline: () => null + } + const VERIFY_INLINE_FUNCTION_HANDLERS = { + method: verifyInlineFunctionHandlerCanBeReplacedWithMethodHandler, + inline: verifyInlineFunctionHandlerCanBeReplacedWithInlineHandler, + 'inline-function': () => null + } + + /** + * Get token information for the given VExpressionContainer node. + * @param {VExpressionContainer} node + */ + function getVExpressionContainerTokenInfo(node) { + const tokenStore = context.parserServices.getTemplateBodyTokenStore() + const tokens = tokenStore.getTokens(node, { + includeComments: true + }) + const firstToken = tokens[0] + const lastToken = tokens[tokens.length - 1] + + const hasQuote = isQuote(firstToken) + /** + * Range without quotes. + * @type {Range} + */ + const range = hasQuote + ? [firstToken.range[1], lastToken.range[0]] + : [firstToken.range[0], lastToken.range[1]] + + return { + innerRange: range, + get hasComment() { + return tokens.some( + (token) => token.type === 'Block' || token.type === 'Line' + ) + }, + hasQuote + } + } + /** + * Check if `v-on:click="foo()"` can be converted to `v-on:click="foo"` and report if it can. + * @param {VOnExpression} node + * @returns {ReportResult & { kind: 'method' } | null} + */ + function verifyInlineHandlerCanBeReplacedWithMethodHandler(node) { + const expression = getIdentifierCallExpression(node) + if (!expression || expression.arguments.length > 0) { + return null + } + + const { innerRange, hasComment } = getVExpressionContainerTokenInfo( + node.parent + ) + + if (ignoreIncludesComment && hasComment) { + return null + } + + const argumentsCount = methodArgumentCountMap.get(expression.callee.name) + if (argumentsCount != null && argumentsCount > 0) { + // The behavior of target method can change given the arguments. + return null + } + + return { + kind: 'method', + suggestMessageId: 'useMethodInsteadOfInline', + fix: hasComment + ? null /* The statement contains comment and cannot be fixed. */ + : (fixer) => + fixer.replaceTextRange( + innerRange, + context.getSourceCode().getText(expression.callee) + ) + } + } + /** + * Check if `v-on:click="foo()"` can be converted to `v-on:click="()=>foo()"` and report if it can. + * @param {VOnExpression} node + * @returns {ReportResult & { kind: 'inline-function' } | null} + */ + function verifyInlineHandlerCanBeReplacedWithInlineFunctionHandler(node) { + const has$Event = $eventIdentifiers.some( + ({ range }) => node.range[0] <= range[0] && range[1] <= node.range[1] + ) + + const { innerRange, hasQuote } = getVExpressionContainerTokenInfo( + node.parent + ) + return { + kind: 'inline-function', + suggestMessageId: 'useInlineFunctionInsteadOfInline', + fix: + has$Event /* The statements contains $event and cannot be fixed. */ || + !hasQuote /* The statements is not enclosed in quotes and cannot be fixed. */ + ? null + : function* (fixer) { + yield fixer.insertTextBeforeRange(innerRange, '() => ') + const tokenStore = + context.parserServices.getTemplateBodyTokenStore() + const firstToken = tokenStore.getFirstToken(node) + const lastToken = tokenStore.getLastToken(node) + if (firstToken.value === '{' && lastToken.value === '}') return + if ( + lastToken.value !== ';' && + node.body.length === 1 && + node.body[0].type === 'ExpressionStatement' + ) { + // it is a single expression + return + } + yield fixer.insertTextBefore(firstToken, '{') + yield fixer.insertTextAfter(lastToken, '}') + } + } + } + + /** + * Check if `v-on:click="() => foo()"` can be converted to `v-on:click="foo"` and report if it can. + * @param {ArrowFunctionExpression | FunctionExpression} node + * @returns {ReportResult & { kind: 'method' } | null} + */ + function verifyInlineFunctionHandlerCanBeReplacedWithMethodHandler(node) { + /** @type {CallExpression & {callee: Identifier} } */ + let expression + if (node.body.type === 'BlockStatement') { + const callExpression = getIdentifierCallExpression(node.body) + if (!callExpression) { + return null + } + expression = callExpression + } else { + if (!isIdentifierCallExpression(node.body)) { + return null + } + expression = node.body + } + + if ( + node.params.length !== expression.arguments.length || + !node.params.every((param, index) => { + if (param.type !== 'Identifier') { + return false + } + const arg = expression.arguments[index] + if (!arg || arg.type !== 'Identifier') { + return false + } + return param.name === arg.name + }) + ) { + // It is not a call with the arguments given as is. + return null + } + + const { innerRange, hasComment } = getVExpressionContainerTokenInfo( + /** @type {VExpressionContainer} */ (node.parent) + ) + + if (ignoreIncludesComment && hasComment) { + return null + } + + const argumentsCount = methodArgumentCountMap.get(expression.callee.name) + if ( + argumentsCount != null && + argumentsCount !== expression.arguments.length + ) { + // The behavior of target method can change given the arguments. + return null + } + + return { + kind: 'method', + suggestMessageId: 'useMethodInsteadOfInlineFunction', + fix: hasComment + ? null /* The function contains comment and cannot be fixed. */ + : (fixer) => + fixer.replaceTextRange( + innerRange, + context.getSourceCode().getText(expression.callee) + ) + } + } + /** + * Check if `v-on:click="() => foo()"` can be converted to `v-on:click="foo()"` and report if it can. + * @param {ArrowFunctionExpression | FunctionExpression} node + * @returns {ReportResult & { kind: 'inline' } | null} + */ + function verifyInlineFunctionHandlerCanBeReplacedWithInlineHandler(node) { + if (node.params.length > 1) { + // Can't convert to inline handler if it has 2 or more parameters. + // If there is one parameter, it can be converted to an inline handler using $event. + return null + } + + return { + kind: 'inline', + suggestMessageId: 'useInlineInsteadOfInlineFunction', + fix: + node.params.length > 0 + ? null /* The function has parameters and cannot be fixed. */ + : (fixer) => { + let text = context.getSourceCode().getText(node.body) + if (node.body.type === 'BlockStatement') { + text = text.slice(1, -1) // strip braces + } + return fixer.replaceText(node, text) + } + } + } + + /** + * @param {Expression | VOnExpression} node + * @param {ReportResult[]} results + * @param {string} messageId + */ + function reportResults(node, results, messageId) { + if (results.length > 0) { + const fixableList = results.filter((r) => r.fix) + context.report({ + node, + messageId, + data: { + allows: getHandlerKindsPhrase(results.map((r) => r.kind)) + }, + fix: fixableList.length > 0 ? fixableList[0].fix : null, + suggest: + fixableList.length > 1 + ? fixableList.map((r) => ({ + messageId: r.suggestMessageId, + fix: r.fix + })) + : [] + }) + } + } + + return utils.defineTemplateBodyVisitor( + context, + { + /** @param {VExpressionContainer} node */ + "VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer.value:exit"( + node + ) { + const expression = node.expression + if (!expression) { + return + } + switch (expression.type) { + case 'VOnExpression': { + // e.g. v-on:click="foo()" + if (allows.includes('inline')) { + return + } + const results = allows + .map((allow) => VERIFY_INLINE_HANDLERS[allow](expression)) + .filter(utils.isDef) + + reportResults(expression, results, 'preferXOverInline') + + break + } + case 'Identifier': { + // e.g. v-on:click="foo" + if (allows.includes('method')) { + return + } + context.report({ + node, + messageId: 'disallowMethodHandler', + data: { + allows: getHandlerKindsPhrase(allows) + } + }) + + break + } + case 'ArrowFunctionExpression': + case 'FunctionExpression': { + // e.g. v-on:click="()=>foo()" + if (allows.includes('inline-function')) { + return + } + const results = allows + .map((allow) => + VERIFY_INLINE_FUNCTION_HANDLERS[allow](expression) + ) + .filter(utils.isDef) + + reportResults(expression, results, 'preferXOverInlineFunction') + + break + } + // No default + } + }, + ...(!allows.includes('inline') && allows.includes('inline-function') + ? // Collect $event identifiers to check for side effects + // when converting from `v-on:click="foo($event)"` to `v-on:click="()=>foo($event)"` . + { + 'Identifier[name="$event"]'(node) { + $eventIdentifiers.push(node) + } + } + : {}) + }, + allows.includes('method') + ? // Collect method definition with arguments information to check for side effects. + // when converting from `v-on:click="foo()"` to `v-on:click="foo"`, or + // converting from `v-on:click="() => foo()"` to `v-on:click="foo"`. + utils.defineVueVisitor(context, { + onVueObjectEnter(node) { + for (const method of utils.iterateProperties( + node, + new Set(['methods']) + )) { + if (method.type !== 'object') { + // This branch is usually not passed. + continue + } + const value = method.property.value + if ( + value.type === 'FunctionExpression' || + value.type === 'ArrowFunctionExpression' + ) { + methodArgumentCountMap.set( + method.name, + value.params.some((p) => p.type === 'RestElement') + ? Number.POSITIVE_INFINITY + : value.params.length + ) + } + } + } + }) + : {} + ) + } +} diff --git a/tests/lib/rules/v-on-handler-style.js b/tests/lib/rules/v-on-handler-style.js new file mode 100644 index 000000000..4d5c7d474 --- /dev/null +++ b/tests/lib/rules/v-on-handler-style.js @@ -0,0 +1,913 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/v-on-handler-style') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { ecmaVersion: 2020, sourceType: 'module' } +}) + +tester.run('v-on-handler-style', rule, { + valid: [ + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: ``, + options: [['method', 'inline-function']] + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: ``, + options: [['inline']] + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + // inline -> method + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ``, + options: [['method'], { ignoreIncludesComment: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']] + }, + // inline-function -> method + { + filename: 'test.vue', + code: ``, + options: [['method'], { ignoreIncludesComment: true }] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']] + }, + // inline-function -> inline + { + filename: 'test.vue', + code: ``, + options: [['inline']] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ``, + options: [['method', 'inline-function']], + output: ``, + errors: [ + { + message: + 'Prefer method handler, or inline function over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [ + { + desc: 'Use method handler instead of inline handler.', + output: `` + }, + { + desc: 'Use inline function instead of inline handler.', + output: `` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Prefer method handler, or inline function over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [ + { + desc: 'Use method handler instead of inline handler.', + output: `` + }, + { + desc: 'Use inline function instead of inline handler.', + output: `` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['inline']], + errors: [ + { + message: 'Method handlers are not allowed. Use inline handler.', + line: 2, + column: 24, + suggestions: [] + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 4, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']], + output: ``, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['inline-function']], + output: ``, + errors: [ + { + message: 'Method handlers are not allowed. Use inline function.', + line: 2, + column: 24, + suggestions: [] + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [] + } + ] + }, + // inline -> method + { + filename: 'test.vue', + code: '', + output: ``, + options: [['method']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 1, + column: 24, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']], + output: null, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']], + output: null, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 38, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 24, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + options: [['method']], + output: ` + `, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 6, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + options: [['method']], + output: ` + `, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 23, + suggestions: [] + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 22, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + options: [['method']], + output: ` + `, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 26, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']], + output: ` + + `, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 33, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [['method']], + output: ` + + `, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 33, + suggestions: [] + } + ] + }, + // inline -> inline-function + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 2, + column: 25, + suggestions: [] + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 3, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: '', + output: null, + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 24, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: '', + output: null, + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 23, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: [['inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27, + suggestions: [] + } + ] + }, + // method -> inline + { + filename: 'test.vue', + code: ``, + options: [['inline']], + output: null, + errors: [ + { + message: 'Method handlers are not allowed. Use inline handler.', + line: 2, + column: 24, + suggestions: [] + } + ] + }, + // inline-function -> method + { + filename: 'test.vue', + code: ``, + options: [['method']], + output: ``, + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 25, + suggestions: [] + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['method']], + output: null, + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + options: [['method']], + output: ` + `, + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + options: [['method']], + output: ` + `, + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + } + ] + }, + // inline-function -> inline + { + filename: 'test.vue', + code: ``, + options: [['inline']], + output: ``, + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 3, + column: 25, + suggestions: [] + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 4, + column: 25, + suggestions: [] + } + ] + }, + { + filename: 'test.vue', + code: ``, + options: [['inline']], + output: null, + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 2, + column: 25, + suggestions: [] + } + ] + }, + + // option order + { + filename: 'test.vue', + code: ``, + options: [['inline-function', 'method']], + output: ``, + errors: [ + { + message: + 'Prefer inline function, or method handler over inline handler in v-on.', + line: 2, + column: 25, + suggestions: [ + { + desc: 'Use inline function instead of inline handler.', + output: `` + }, + { + desc: 'Use method handler instead of inline handler.', + output: `` + } + ] + } + ] + } + ] +}) diff --git a/typings/eslint/index.d.ts b/typings/eslint/index.d.ts index 949d56f6d..7d85f665f 100644 --- a/typings/eslint/index.d.ts +++ b/typings/eslint/index.d.ts @@ -370,16 +370,14 @@ export namespace Linter { type LintMessage = ESLintLinter.LintMessage type LintOptions = ESLintLinter.LintOptions } - +export type ReportDescriptorFix = ( + fixer: Rule.RuleFixer +) => null | Rule.Fix | IterableIterator | Rule.Fix[] interface ReportDescriptorOptionsBase { data?: { [key: string]: string | number } - fix?: - | null - | (( - fixer: Rule.RuleFixer - ) => null | Rule.Fix | IterableIterator | Rule.Fix[]) + fix?: null | ReportDescriptorFix } interface SuggestionReportDescriptor1 extends ReportDescriptorOptionsBase { From 56fd2e9e9f8961ed1a6c8f315bff2a189090d746 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 12 Oct 2022 16:54:14 +0900 Subject: [PATCH 02/15] Update docs/rules/v-on-handler-style.md Co-authored-by: Flo Edelmann --- docs/rules/v-on-handler-style.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/v-on-handler-style.md b/docs/rules/v-on-handler-style.md index f99d6f495..5d075bbc4 100644 --- a/docs/rules/v-on-handler-style.md +++ b/docs/rules/v-on-handler-style.md @@ -13,7 +13,7 @@ description: enforce writing style for handlers in `v-on` directives ## :book: Rule Details -This rule aims to enforce using method handlers on `v-on`, using inline handlers on `v-on`, or binding inline functions to `v-on`. +This rule aims to enforce a consistent style in `v-on` event handlers: ```vue From f76cfad5e7becffe861e039244b8c2cacf618526 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 12 Oct 2022 16:54:51 +0900 Subject: [PATCH 03/15] Update docs/rules/v-on-handler-style.md Co-authored-by: Flo Edelmann --- docs/rules/v-on-handler-style.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/rules/v-on-handler-style.md b/docs/rules/v-on-handler-style.md index 5d075bbc4..697e3e878 100644 --- a/docs/rules/v-on-handler-style.md +++ b/docs/rules/v-on-handler-style.md @@ -16,12 +16,14 @@ description: enforce writing style for handlers in `v-on` directives This rule aims to enforce a consistent style in `v-on` event handlers: ```vue - - - - - - + +