Skip to content

Commit 4eb500c

Browse files
committed
Add support for new defineEmits type syntax in vue/require-explicit-emits rule
1 parent e1747fc commit 4eb500c

File tree

3 files changed

+110
-13
lines changed

3 files changed

+110
-13
lines changed

lib/utils/ts-ast-utils.js

Lines changed: 33 additions & 12 deletions
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

Lines changed: 66 additions & 0 deletions
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,47 @@ 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+
suggestions: []
1932+
}
1933+
]
1934+
},
1935+
{
1936+
// new syntax in Vue 3.3
1937+
filename: 'test.vue',
1938+
code: `
1939+
<script setup lang="ts">
1940+
type Emits = {foo: []}
1941+
const emit = defineEmits<Emits>()
1942+
emit('foo')
1943+
emit('bar')
1944+
</script>
1945+
`,
1946+
parserOptions: { parser: require.resolve('@typescript-eslint/parser') },
1947+
errors: [
1948+
{
1949+
message:
1950+
'The "bar" event has been triggered but not declared on `defineEmits`.',
1951+
line: 6,
1952+
suggestions: []
1953+
}
1954+
]
18891955
}
18901956
]
18911957
})

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,23 @@ export type ComponentUnknownEmit = {
152152
node: Expression | SpreadElement | null
153153
}
154154

155-
export type ComponentTypeEmit = {
155+
export type ComponentTypeEmitCallSignature = {
156156
type: 'type'
157157
key: TSLiteralType
158158
emitName: string
159159
value: null
160160
node: TSCallSignatureDeclaration | TSFunctionType
161161
}
162+
export type ComponentTypeEmitPropertySignature = {
163+
type: 'type'
164+
key: Identifier | Literal
165+
emitName: string
166+
value: null
167+
node: TSPropertySignature | TSMethodSignature
168+
}
169+
export type ComponentTypeEmit =
170+
| ComponentTypeEmitCallSignature
171+
| ComponentTypeEmitPropertySignature
162172

163173
export type ComponentEmit =
164174
| ComponentArrayEmit

0 commit comments

Comments
 (0)