diff --git a/docs/rules/index.md b/docs/rules/index.md index 55c5b96c9..2bef5d9f2 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -68,6 +68,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue | [vue/no-deprecated-v-on-native-modifier] | disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+) | | :three::warning: | | [vue/no-deprecated-v-on-number-modifiers] | disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+) | :wrench: | :three::warning: | | [vue/no-deprecated-vue-config-keycodes] | disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+) | | :three::warning: | +| [vue/no-direct-composable-in-event-handler] | disallow direct composable usage in event handler | | :three::hammer: | | [vue/no-dupe-keys] | disallow duplication of field names | | :three::two::warning: | | [vue/no-dupe-v-else-if] | disallow duplicate conditions in `v-if` / `v-else-if` chains | | :three::two::warning: | | [vue/no-duplicate-attributes] | disallow duplication of attributes | | :three::two::warning: | @@ -460,6 +461,7 @@ The following rules extend the rules provided by ESLint itself and apply them to [vue/no-deprecated-v-on-native-modifier]: ./no-deprecated-v-on-native-modifier.md [vue/no-deprecated-v-on-number-modifiers]: ./no-deprecated-v-on-number-modifiers.md [vue/no-deprecated-vue-config-keycodes]: ./no-deprecated-vue-config-keycodes.md +[vue/no-direct-composable-in-event-handler]: ./no-direct-composable-in-event-handler.md [vue/no-dupe-keys]: ./no-dupe-keys.md [vue/no-dupe-v-else-if]: ./no-dupe-v-else-if.md [vue/no-duplicate-attr-inheritance]: ./no-duplicate-attr-inheritance.md diff --git a/docs/rules/no-direct-composable-in-event-handler.md b/docs/rules/no-direct-composable-in-event-handler.md new file mode 100644 index 000000000..ae6027fa2 --- /dev/null +++ b/docs/rules/no-direct-composable-in-event-handler.md @@ -0,0 +1,43 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-direct-composable-in-event-handler +description: disallow direct composable usage in event handler +since: v10.1.0 +--- + +# vue/no-direct-composable-in-event-handler + +> disallow direct composable usage in event handler + +- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`. + +This rule prevents directly calling a composable function in an event handler. + +## :book: Rule Details + +This rule prevents directly calling a composable function in an event handler. If something starts with `use`, it is considered a composable function. + + + +```vue + + + +``` + + + +## :rocket: Version + +This rule was introduced in eslint-plugin-vue v10.1.0 + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-direct-composable-in-event-handler.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-direct-composable-in-event-handler.js) diff --git a/lib/configs/flat/vue3-essential.js b/lib/configs/flat/vue3-essential.js index ff8b5b4a6..36b11113f 100644 --- a/lib/configs/flat/vue3-essential.js +++ b/lib/configs/flat/vue3-essential.js @@ -37,6 +37,7 @@ module.exports = [ 'vue/no-deprecated-v-on-native-modifier': 'error', 'vue/no-deprecated-v-on-number-modifiers': 'error', 'vue/no-deprecated-vue-config-keycodes': 'error', + 'vue/no-direct-composable-in-event-handler': 'error', 'vue/no-dupe-keys': 'error', 'vue/no-dupe-v-else-if': 'error', 'vue/no-duplicate-attributes': 'error', diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js index 34b3229b1..73c610a9a 100644 --- a/lib/configs/vue3-essential.js +++ b/lib/configs/vue3-essential.js @@ -32,6 +32,7 @@ module.exports = { 'vue/no-deprecated-v-on-native-modifier': 'error', 'vue/no-deprecated-v-on-number-modifiers': 'error', 'vue/no-deprecated-vue-config-keycodes': 'error', + 'vue/no-direct-composable-in-event-handler': 'error', 'vue/no-dupe-keys': 'error', 'vue/no-dupe-v-else-if': 'error', 'vue/no-duplicate-attributes': 'error', diff --git a/lib/index.js b/lib/index.js index 834e5f28b..c45923d24 100644 --- a/lib/index.js +++ b/lib/index.js @@ -120,6 +120,7 @@ const plugin = { 'no-deprecated-v-on-native-modifier': require('./rules/no-deprecated-v-on-native-modifier'), 'no-deprecated-v-on-number-modifiers': require('./rules/no-deprecated-v-on-number-modifiers'), 'no-deprecated-vue-config-keycodes': require('./rules/no-deprecated-vue-config-keycodes'), + 'no-direct-composable-in-event-handler': require('./rules/no-direct-composable-in-event-handler'), 'no-dupe-keys': require('./rules/no-dupe-keys'), 'no-dupe-v-else-if': require('./rules/no-dupe-v-else-if'), 'no-duplicate-attr-inheritance': require('./rules/no-duplicate-attr-inheritance'), diff --git a/lib/rules/no-direct-composable-in-event-handler.js b/lib/rules/no-direct-composable-in-event-handler.js new file mode 100644 index 000000000..61f6d4f63 --- /dev/null +++ b/lib/rules/no-direct-composable-in-event-handler.js @@ -0,0 +1,66 @@ +/** + * @author Nils Haberkamp + * See LICENSE file in root directory for full license. + */ +'use strict' + +const utils = require('../utils') + +/** + * Check if the given function name follows the composable naming convention (starts with 'use') + * @param {string | null | undefined} name The function name + * @returns {boolean} `true` if the function name starts with 'use' + */ +function isComposable(name) { + return Boolean(name && name.startsWith('use')) +} + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'disallow direct composable usage in event handler', + categories: ['vue3-essential'], + url: 'https://eslint.vuejs.org/rules/no-direct-composable-in-event-handler.html' + }, + fixable: null, + schema: [], + messages: { + forbiddenComposableUsage: + 'Direct composable usage in event handler is not allowed.' + } + }, + /** @param {RuleContext} context */ + create(context) { + return utils.defineTemplateBodyVisitor(context, { + /** @param {VDirective} node */ + 'VAttribute[directive=true][key.name.name="on"]'(node) { + const eventHandler = node.value + + if (!eventHandler || !eventHandler.expression) { + return + } + + if ( + eventHandler.expression.type === 'Identifier' && + isComposable(eventHandler.expression.name) + ) { + context.report({ + node, + messageId: 'forbiddenComposableUsage', + loc: { + start: { + line: node.loc.start.line, + column: node.loc.start.column + }, + end: { + line: node.loc.end.line, + column: node.loc.end.column + } + } + }) + } + } + }) + } +} diff --git a/tests/lib/rules/no-direct-composable-in-event-handler.js b/tests/lib/rules/no-direct-composable-in-event-handler.js new file mode 100644 index 000000000..f3e173c3d --- /dev/null +++ b/tests/lib/rules/no-direct-composable-in-event-handler.js @@ -0,0 +1,124 @@ +/** + * @fileoverview Disallow direct composable usage in event handler. + * @author Nils Haberkamp + */ +'use strict' + +const rule = require('../../../lib/rules/no-direct-composable-in-event-handler') +const RuleTester = require('../../eslint-compat').RuleTester + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } +}) + +ruleTester.run('no-direct-composable-in-event-handler', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + + `, + languageOptions: { parser: require('vue-eslint-parser') } + }, + { + filename: 'test.vue', + code: ` + `, + languageOptions: { parser: require('vue-eslint-parser') } + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + + `, + languageOptions: { parser: require('vue-eslint-parser') }, + errors: [ + { + message: 'Direct composable usage in event handler is not allowed.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + languageOptions: { parser: require('vue-eslint-parser') }, + errors: [ + { + message: 'Direct composable usage in event handler is not allowed.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + languageOptions: { parser: require('vue-eslint-parser') }, + errors: [ + { + message: 'Direct composable usage in event handler is not allowed.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + message: 'Direct composable usage in event handler is not allowed.', + line: 3 + } + ] + } + ] +})