Skip to content

Commit 8ed65c9

Browse files
authored
Add support for new defineEmits type syntax to vue/require-explicit-emits rule (#2152)
1 parent 012e2df commit 8ed65c9

File tree

3 files changed

+108
-13
lines changed

3 files changed

+108
-13
lines changed

lib/utils/ts-ast-utils.js

+33-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { findVariable } = require('@eslint-community/eslint-utils')
1010
/**
1111
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
1212
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
13+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
1314
*/
1415

1516
module.exports = {
@@ -147,22 +148,42 @@ function* extractRuntimeProps(context, node) {
147148
/**
148149
* @see https://github.com/vuejs/vue-next/blob/348c3b01e56383ffa70b180d1376fdf4ac12e274/packages/compiler-sfc/src/compileScript.ts#L1632
149150
* @param {TSTypeLiteral | TSInterfaceBody | TSFunctionType} node
150-
* @returns {IterableIterator<ComponentTypeEmit>}
151+
* @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
151152
*/
152153
function* extractRuntimeEmits(node) {
153-
if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
154-
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
155-
for (const t of members) {
156-
if (t.type === 'TSCallSignatureDeclaration') {
157-
yield* extractEventNames(
158-
t.params[0],
159-
/** @type {TSCallSignatureDeclaration} */ (t)
160-
)
154+
if (node.type === 'TSFunctionType') {
155+
yield* extractEventNames(node.params[0], node)
156+
return
157+
}
158+
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
159+
for (const member of members) {
160+
if (member.type === 'TSCallSignatureDeclaration') {
161+
yield* extractEventNames(
162+
member.params[0],
163+
/** @type {TSCallSignatureDeclaration} */ (member)
164+
)
165+
} else if (
166+
member.type === 'TSPropertySignature' ||
167+
member.type === 'TSMethodSignature'
168+
) {
169+
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
170+
yield {
171+
type: 'unknown',
172+
node: member.key
173+
}
174+
continue
175+
}
176+
yield {
177+
type: 'type',
178+
key: /** @type {Identifier | Literal} */ (member.key),
179+
emitName:
180+
member.key.type === 'Identifier'
181+
? member.key.name
182+
: `${member.key.value}`,
183+
value: null,
184+
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
161185
}
162186
}
163-
return
164-
} else {
165-
yield* extractEventNames(node.params[0], node)
166187
}
167188
}
168189

tests/lib/rules/require-explicit-emits.js

+64
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,31 @@ tester.run('require-explicit-emits', rule, {
593593
</script>
594594
`,
595595
options: [{ allowProps: true }]
596+
},
597+
{
598+
// new syntax in Vue 3.3
599+
filename: 'test.vue',
600+
code: `
601+
<script setup lang="ts">
602+
const emit = defineEmits<{foo: [], bar:[number]}>()
603+
emit('foo')
604+
emit('bar', 42)
605+
</script>
606+
`,
607+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
608+
},
609+
{
610+
// new syntax in Vue 3.3
611+
filename: 'test.vue',
612+
code: `
613+
<script setup lang="ts">
614+
type Emits = {foo: [], bar:[number]}
615+
const emit = defineEmits<Emits>()
616+
emit('foo')
617+
emit('bar', 42)
618+
</script>
619+
`,
620+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
596621
}
597622
],
598623
invalid: [
@@ -1886,6 +1911,45 @@ emits: {'foo': null}
18861911
]
18871912
}
18881913
]
1914+
},
1915+
{
1916+
// new syntax in Vue 3.3
1917+
filename: 'test.vue',
1918+
code: `
1919+
<script setup lang="ts">
1920+
const emit = defineEmits<{foo: []}>()
1921+
emit('foo')
1922+
emit('bar')
1923+
</script>
1924+
`,
1925+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
1926+
errors: [
1927+
{
1928+
message:
1929+
'The "bar" event has been triggered but not declared on `defineEmits`.',
1930+
line: 5
1931+
}
1932+
]
1933+
},
1934+
{
1935+
// new syntax in Vue 3.3
1936+
filename: 'test.vue',
1937+
code: `
1938+
<script setup lang="ts">
1939+
type Emits = {foo: []}
1940+
const emit = defineEmits<Emits>()
1941+
emit('foo')
1942+
emit('bar')
1943+
</script>
1944+
`,
1945+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
1946+
errors: [
1947+
{
1948+
message:
1949+
'The "bar" event has been triggered but not declared on `defineEmits`.',
1950+
line: 6
1951+
}
1952+
]
18891953
}
18901954
]
18911955
})

typings/eslint-plugin-vue/util-types/utils.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,23 @@ export type ComponentUnknownEmit = {
156156
node: Expression | SpreadElement | null
157157
}
158158

159-
export type ComponentTypeEmit = {
159+
export type ComponentTypeEmitCallSignature = {
160160
type: 'type'
161161
key: TSLiteralType
162162
emitName: string
163163
value: null
164164
node: TSCallSignatureDeclaration | TSFunctionType
165165
}
166+
export type ComponentTypeEmitPropertySignature = {
167+
type: 'type'
168+
key: Identifier | Literal
169+
emitName: string
170+
value: null
171+
node: TSPropertySignature | TSMethodSignature
172+
}
173+
export type ComponentTypeEmit =
174+
| ComponentTypeEmitCallSignature
175+
| ComponentTypeEmitPropertySignature
166176

167177
export type ComponentEmit =
168178
| ComponentArrayEmit

0 commit comments

Comments
 (0)