Skip to content

Commit d82bf0d

Browse files
committed
feat: add vue/no-direct-composable-in-event-handler rule
1 parent 2b5273c commit d82bf0d

7 files changed

+238
-0
lines changed

docs/rules/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
6868
| [vue/no-deprecated-v-on-native-modifier] | disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+) | | :three::warning: |
6969
| [vue/no-deprecated-v-on-number-modifiers] | disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
7070
| [vue/no-deprecated-vue-config-keycodes] | disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+) | | :three::warning: |
71+
| [vue/no-direct-composable-in-event-handler] | disallow direct composable usage in event handler | | :three::hammer: |
7172
| [vue/no-dupe-keys] | disallow duplication of field names | | :three::two::warning: |
7273
| [vue/no-dupe-v-else-if] | disallow duplicate conditions in `v-if` / `v-else-if` chains | | :three::two::warning: |
7374
| [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
460461
[vue/no-deprecated-v-on-native-modifier]: ./no-deprecated-v-on-native-modifier.md
461462
[vue/no-deprecated-v-on-number-modifiers]: ./no-deprecated-v-on-number-modifiers.md
462463
[vue/no-deprecated-vue-config-keycodes]: ./no-deprecated-vue-config-keycodes.md
464+
[vue/no-direct-composable-in-event-handler]: ./no-direct-composable-in-event-handler.md
463465
[vue/no-dupe-keys]: ./no-dupe-keys.md
464466
[vue/no-dupe-v-else-if]: ./no-dupe-v-else-if.md
465467
[vue/no-duplicate-attr-inheritance]: ./no-duplicate-attr-inheritance.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-direct-composable-in-event-handler
5+
description: disallow direct composable usage in event handler
6+
since: v10.1.0
7+
---
8+
9+
# vue/no-direct-composable-in-event-handler
10+
11+
> disallow direct composable usage in event handler
12+
13+
- :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"]`.
14+
15+
This rule prevents directly calling a composable function in an event handler.
16+
17+
## :book: Rule Details
18+
19+
This rule prevents directly calling a composable function in an event handler. If something starts with `use`, it is considered a composable function.
20+
21+
<eslint-code-block :rules="{'vue/no-direct-composable-in-event-handler': ['error']}">
22+
23+
```vue
24+
<template>
25+
<!-- ✗ BAD -->
26+
<button @click="useFoo">Click me</button>
27+
</template>
28+
29+
<script setup>
30+
function useFoo() {}
31+
</script>
32+
```
33+
34+
</eslint-code-block>
35+
36+
## :rocket: Version
37+
38+
This rule was introduced in eslint-plugin-vue v10.1.0
39+
40+
## :mag: Implementation
41+
42+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-direct-composable-in-event-handler.js)
43+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-direct-composable-in-event-handler.js)

lib/configs/flat/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ module.exports = [
3737
'vue/no-deprecated-v-on-native-modifier': 'error',
3838
'vue/no-deprecated-v-on-number-modifiers': 'error',
3939
'vue/no-deprecated-vue-config-keycodes': 'error',
40+
'vue/no-direct-composable-in-event-handler': 'error',
4041
'vue/no-dupe-keys': 'error',
4142
'vue/no-dupe-v-else-if': 'error',
4243
'vue/no-duplicate-attributes': 'error',

lib/configs/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module.exports = {
3232
'vue/no-deprecated-v-on-native-modifier': 'error',
3333
'vue/no-deprecated-v-on-number-modifiers': 'error',
3434
'vue/no-deprecated-vue-config-keycodes': 'error',
35+
'vue/no-direct-composable-in-event-handler': 'error',
3536
'vue/no-dupe-keys': 'error',
3637
'vue/no-dupe-v-else-if': 'error',
3738
'vue/no-duplicate-attributes': 'error',

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const plugin = {
120120
'no-deprecated-v-on-native-modifier': require('./rules/no-deprecated-v-on-native-modifier'),
121121
'no-deprecated-v-on-number-modifiers': require('./rules/no-deprecated-v-on-number-modifiers'),
122122
'no-deprecated-vue-config-keycodes': require('./rules/no-deprecated-vue-config-keycodes'),
123+
'no-direct-composable-in-event-handler': require('./rules/no-direct-composable-in-event-handler'),
123124
'no-dupe-keys': require('./rules/no-dupe-keys'),
124125
'no-dupe-v-else-if': require('./rules/no-dupe-v-else-if'),
125126
'no-duplicate-attr-inheritance': require('./rules/no-duplicate-attr-inheritance'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @author Nils Haberkamp
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/**
10+
* Check if the given function name follows the composable naming convention (starts with 'use')
11+
* @param {string | null | undefined} name The function name
12+
* @returns {boolean} `true` if the function name starts with 'use'
13+
*/
14+
function isComposable(name) {
15+
return Boolean(name && name.startsWith('use'))
16+
}
17+
18+
module.exports = {
19+
meta: {
20+
type: 'suggestion',
21+
docs: {
22+
description: 'disallow direct composable usage in event handler',
23+
categories: ['vue3-essential'],
24+
url: 'https://eslint.vuejs.org/rules/no-direct-composable-in-event-handler.html'
25+
},
26+
fixable: null,
27+
schema: [],
28+
messages: {
29+
forbiddenComposableUsage:
30+
'Direct composable usage in event handler is not allowed.'
31+
}
32+
},
33+
/** @param {RuleContext} context */
34+
create(context) {
35+
return utils.defineTemplateBodyVisitor(context, {
36+
/** @param {VDirective} node */
37+
'VAttribute[directive=true][key.name.name="on"]'(node) {
38+
const eventHandler = node.value
39+
40+
if (!eventHandler || !eventHandler.expression) {
41+
return
42+
}
43+
44+
if (
45+
eventHandler.expression.type === 'Identifier' &&
46+
isComposable(eventHandler.expression.name)
47+
) {
48+
context.report({
49+
node,
50+
messageId: 'forbiddenComposableUsage',
51+
loc: {
52+
start: {
53+
line: node.loc.start.line,
54+
column: node.loc.start.column
55+
},
56+
end: {
57+
line: node.loc.end.line,
58+
column: node.loc.end.column
59+
}
60+
}
61+
})
62+
}
63+
}
64+
})
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @fileoverview Disallow direct composable usage in event handler.
3+
* @author Nils Haberkamp
4+
*/
5+
'use strict'
6+
7+
const rule = require('../../../lib/rules/no-direct-composable-in-event-handler')
8+
const RuleTester = require('../../eslint-compat').RuleTester
9+
10+
const ruleTester = new RuleTester({
11+
languageOptions: {
12+
ecmaVersion: 2018,
13+
sourceType: 'module'
14+
}
15+
})
16+
17+
ruleTester.run('no-direct-composable-in-event-handler', rule, {
18+
valid: [
19+
{
20+
filename: 'test.vue',
21+
code: `
22+
<template>
23+
<button @click="foo">Click me</button>
24+
</template>
25+
<script setup>
26+
function foo() {}
27+
</script>
28+
`,
29+
languageOptions: { parser: require('vue-eslint-parser') }
30+
},
31+
{
32+
filename: 'test.vue',
33+
code: `
34+
<template>
35+
<button @click="() => console.log('foo')">Click me</button>
36+
</template>`,
37+
languageOptions: { parser: require('vue-eslint-parser') }
38+
}
39+
],
40+
41+
invalid: [
42+
{
43+
filename: 'test.vue',
44+
code: `
45+
<template>
46+
<button @click="useFoo">Click me</button>
47+
</template>
48+
<script>
49+
export default {
50+
setup() {
51+
function useFoo() {}
52+
53+
return { useFoo }
54+
}
55+
}
56+
</script>
57+
`,
58+
languageOptions: { parser: require('vue-eslint-parser') },
59+
errors: [
60+
{
61+
message: 'Direct composable usage in event handler is not allowed.',
62+
line: 3
63+
}
64+
]
65+
},
66+
{
67+
filename: 'test.vue',
68+
code: `
69+
<template>
70+
<button @click="useFoo">Click me</button>
71+
</template>
72+
<script setup>
73+
function useFoo() {}
74+
</script>
75+
`,
76+
languageOptions: { parser: require('vue-eslint-parser') },
77+
errors: [
78+
{
79+
message: 'Direct composable usage in event handler is not allowed.',
80+
line: 3
81+
}
82+
]
83+
},
84+
{
85+
filename: 'test.vue',
86+
code: `
87+
<template>
88+
<button @keydown.enter="useFoo">Click me</button>
89+
</template>
90+
<script setup>
91+
function useFoo() {}
92+
</script>
93+
`,
94+
languageOptions: { parser: require('vue-eslint-parser') },
95+
errors: [
96+
{
97+
message: 'Direct composable usage in event handler is not allowed.',
98+
line: 3
99+
}
100+
]
101+
},
102+
{
103+
filename: 'test.vue',
104+
code: `
105+
<template>
106+
<button @click="useFoo">Click me</button>
107+
</template>
108+
<script setup lang="ts">
109+
function useFoo() {}
110+
</script>
111+
`,
112+
languageOptions: {
113+
parser: require('vue-eslint-parser'),
114+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
115+
},
116+
errors: [
117+
{
118+
message: 'Direct composable usage in event handler is not allowed.',
119+
line: 3
120+
}
121+
]
122+
}
123+
]
124+
})

0 commit comments

Comments
 (0)