Skip to content

Commit 3e66445

Browse files
authored
Add support for stricter type-literal option in vue/define-emits-declaration (#2315)
1 parent b3129f9 commit 3e66445

File tree

3 files changed

+176
-10
lines changed

3 files changed

+176
-10
lines changed

docs/rules/define-emits-declaration.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ title: vue/define-emits-declaration
55
description: enforce declaration style of `defineEmits`
66
since: v9.5.0
77
---
8+
89
# vue/define-emits-declaration
910

1011
> enforce declaration style of `defineEmits`
1112
1213
## :book: Rule Details
1314

14-
This rule enforces `defineEmits` typing style which you should use `type-based` or `runtime` declaration.
15+
This rule enforces `defineEmits` typing style which you should use `type-based`, strict `type-literal`
16+
(introduced in Vue 3.3), or `runtime` declaration.
1517

1618
This rule only works in setup script and `lang="ts"`.
1719

@@ -25,6 +27,12 @@ const emit = defineEmits<{
2527
(e: 'update', value: string): void
2628
}>()
2729
30+
/* ✓ GOOD */
31+
const emit = defineEmits<{
32+
change: [id: number]
33+
update: [value: string]
34+
}>()
35+
2836
/* ✗ BAD */
2937
const emit = defineEmits({
3038
change: (id) => typeof id == 'number',
@@ -41,10 +49,11 @@ const emit = defineEmits(['change', 'update'])
4149
## :wrench: Options
4250

4351
```json
44-
"vue/define-emits-declaration": ["error", "type-based" | "runtime"]
52+
"vue/define-emits-declaration": ["error", "type-based" | "type-literal" | "runtime"]
4553
```
4654

47-
- `type-based` (default) enforces type-based declaration
55+
- `type-based` (default) enforces type based declaration
56+
- `type-literal` enforces strict "type literal" type based declaration
4857
- `runtime` enforces runtime declaration
4958

5059
### `runtime`
@@ -72,6 +81,37 @@ const emit = defineEmits(['change', 'update'])
7281

7382
</eslint-code-block>
7483

84+
### `type-literal`
85+
86+
<eslint-code-block :rules="{'vue/define-emits-declaration': ['error', 'type-literal']}">
87+
88+
```vue
89+
<script setup lang="ts">
90+
/* ✗ BAD */
91+
const emit = defineEmits(['change', 'update'])
92+
93+
/* ✗ BAD */
94+
const emit = defineEmits({
95+
change: (id) => typeof id == 'number',
96+
update: (value) => typeof value == 'string'
97+
})
98+
99+
/* ✗ BAD */
100+
const emit = defineEmits<{
101+
(e: 'change', id: number): void
102+
(e: 'update', value: string): void
103+
}>()
104+
105+
/* ✓ GOOD */
106+
const emit = defineEmits<{
107+
change: [id: number]
108+
update: [value: string]
109+
}>()
110+
</script>
111+
```
112+
113+
</eslint-code-block>
114+
75115
## :couple: Related Rules
76116

77117
- [vue/define-props-declaration](./define-props-declaration.md)

lib/rules/define-emits-declaration.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
const utils = require('../utils')
88

9+
/**
10+
* @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
11+
*
12+
*/
13+
914
module.exports = {
1015
meta: {
1116
type: 'suggestion',
@@ -17,12 +22,14 @@ module.exports = {
1722
fixable: null,
1823
schema: [
1924
{
20-
enum: ['type-based', 'runtime']
25+
enum: ['type-based', 'type-literal', 'runtime']
2126
}
2227
],
2328
messages: {
24-
hasArg: 'Use type-based declaration instead of runtime declaration.',
25-
hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
29+
hasArg: 'Use type based declaration instead of runtime declaration.',
30+
hasTypeArg: 'Use runtime declaration instead of type based declaration.',
31+
hasTypeCallArg:
32+
'Use new type literal declaration instead of the old call signature declaration.'
2633
}
2734
},
2835
/** @param {RuleContext} context */
@@ -46,6 +53,28 @@ module.exports = {
4653
break
4754
}
4855

56+
case 'type-literal': {
57+
if (node.arguments.length > 0) {
58+
context.report({
59+
node,
60+
messageId: 'hasArg'
61+
})
62+
return
63+
}
64+
65+
const typeArguments = node.typeArguments || node.typeParameters
66+
const param = /** @type {TSTypeLiteral} */ (typeArguments.params[0])
67+
for (const memberNode of param.members) {
68+
if (memberNode.type !== 'TSPropertySignature') {
69+
context.report({
70+
node: memberNode,
71+
messageId: 'hasTypeCallArg'
72+
})
73+
}
74+
}
75+
break
76+
}
77+
4978
case 'runtime': {
5079
const typeArguments = node.typeArguments || node.typeParameters
5180
if (typeArguments && typeArguments.params.length > 0) {

tests/lib/rules/define-emits-declaration.js

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,36 @@ tester.run('define-emits-declaration', rule, {
6363
`,
6464
options: ['runtime']
6565
},
66+
{
67+
filename: 'test.vue',
68+
code: `
69+
<script setup lang="ts">
70+
const emit = defineEmits<{
71+
change: [id: number]
72+
update: [value: string]
73+
}>()
74+
</script>
75+
`,
76+
options: ['type-based'],
77+
parserOptions: {
78+
parser: require.resolve('@typescript-eslint/parser')
79+
}
80+
},
81+
{
82+
filename: 'test.vue',
83+
code: `
84+
<script setup lang="ts">
85+
const emit = defineEmits<{
86+
change: [id: number]
87+
update: [value: string]
88+
}>()
89+
</script>
90+
`,
91+
options: ['type-literal'],
92+
parserOptions: {
93+
parser: require.resolve('@typescript-eslint/parser')
94+
}
95+
},
6696
{
6797
filename: 'test.vue',
6898
// ignore code without defineEmits
@@ -82,7 +112,7 @@ tester.run('define-emits-declaration', rule, {
82112
code: `
83113
<script lang="ts">
84114
import { PropType } from 'vue'
85-
115+
86116
export default {
87117
props: {
88118
kind: { type: String as PropType<'primary' | 'secondary'> },
@@ -106,7 +136,7 @@ tester.run('define-emits-declaration', rule, {
106136
`,
107137
errors: [
108138
{
109-
message: 'Use type-based declaration instead of runtime declaration.',
139+
message: 'Use type based declaration instead of runtime declaration.',
110140
line: 3
111141
}
112142
]
@@ -121,7 +151,25 @@ tester.run('define-emits-declaration', rule, {
121151
options: ['type-based'],
122152
errors: [
123153
{
124-
message: 'Use type-based declaration instead of runtime declaration.',
154+
message: 'Use type based declaration instead of runtime declaration.',
155+
line: 3
156+
}
157+
]
158+
},
159+
{
160+
filename: 'test.vue',
161+
code: `
162+
<script setup lang="ts">
163+
const emit = defineEmits(['change', 'update'])
164+
</script>
165+
`,
166+
options: ['type-literal'],
167+
parserOptions: {
168+
parser: require.resolve('@typescript-eslint/parser')
169+
},
170+
errors: [
171+
{
172+
message: 'Use type based declaration instead of runtime declaration.',
125173
line: 3
126174
}
127175
]
@@ -142,10 +190,59 @@ tester.run('define-emits-declaration', rule, {
142190
},
143191
errors: [
144192
{
145-
message: 'Use runtime declaration instead of type-based declaration.',
193+
message: 'Use runtime declaration instead of type based declaration.',
146194
line: 3
147195
}
148196
]
197+
},
198+
{
199+
filename: 'test.vue',
200+
code: `
201+
<script setup lang="ts">
202+
const emit = defineEmits<{
203+
(e: 'change', id: number): void
204+
(e: 'update', value: string): void
205+
}>()
206+
</script>
207+
`,
208+
options: ['type-literal'],
209+
parserOptions: {
210+
parser: require.resolve('@typescript-eslint/parser')
211+
},
212+
errors: [
213+
{
214+
message:
215+
'Use new type literal declaration instead of the old call signature declaration.',
216+
line: 4
217+
},
218+
{
219+
message:
220+
'Use new type literal declaration instead of the old call signature declaration.',
221+
line: 5
222+
}
223+
]
224+
},
225+
{
226+
filename: 'test.vue',
227+
code: `
228+
<script setup lang="ts">
229+
const emit = defineEmits<{
230+
'change': [id: number]
231+
(e: 'update', value: string): void
232+
}>()
233+
</script>
234+
`,
235+
options: ['type-literal'],
236+
parserOptions: {
237+
parser: require.resolve('@typescript-eslint/parser')
238+
},
239+
errors: [
240+
{
241+
message:
242+
'Use new type literal declaration instead of the old call signature declaration.',
243+
line: 5
244+
}
245+
]
149246
}
150247
]
151248
})

0 commit comments

Comments
 (0)