fix(compiler-sfc): support proper type arguments for defineEmit helper

fix #2874
This commit is contained in:
Evan You 2021-03-29 16:38:07 -04:00
parent 2793bc0918
commit bb8cdcad9f
4 changed files with 40 additions and 14 deletions

View File

@ -692,7 +692,7 @@ return { a, b, c, d, x }
}" }"
`; `;
exports[`SFC compile <script setup> with TypeScript defineEmit w/ type (union) 1`] = ` exports[`SFC compile <script setup> with TypeScript defineEmit w/ type (type literal w/ call signatures) 1`] = `
"import { defineComponent as _defineComponent } from 'vue' "import { defineComponent as _defineComponent } from 'vue'
@ -700,7 +700,7 @@ export default _defineComponent({
expose: [], expose: [],
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined, emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
setup(__props, { emit }: { setup(__props, { emit }: {
emit: (((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)), emit: ({(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}),
slots: any, slots: any,
attrs: any attrs: any
}) { }) {

View File

@ -532,6 +532,18 @@ const emit = defineEmit(['a', 'b'])
test('defineEmit w/ type (union)', () => { test('defineEmit w/ type (union)', () => {
const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)` const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
expect(() =>
compile(`
<script setup lang="ts">
import { defineEmit } from 'vue'
const emit = defineEmit<${type}>()
</script>
`)
).toThrow()
})
test('defineEmit w/ type (type literal w/ call signatures)', () => {
const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
const { content } = compile(` const { content } = compile(`
<script setup lang="ts"> <script setup lang="ts">
import { defineEmit } from 'vue' import { defineEmit } from 'vue'

View File

@ -20,8 +20,8 @@ import {
Statement, Statement,
Expression, Expression,
LabeledStatement, LabeledStatement,
TSUnionType, CallExpression,
CallExpression RestElement
} from '@babel/types' } from '@babel/types'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map' import { RawSourceMap } from 'source-map'
@ -184,7 +184,7 @@ export function compileScript(
let propsTypeDecl: TSTypeLiteral | undefined let propsTypeDecl: TSTypeLiteral | undefined
let propsIdentifier: string | undefined let propsIdentifier: string | undefined
let emitRuntimeDecl: Node | undefined let emitRuntimeDecl: Node | undefined
let emitTypeDecl: TSFunctionType | TSUnionType | undefined let emitTypeDecl: TSFunctionType | TSTypeLiteral | undefined
let emitIdentifier: string | undefined let emitIdentifier: string | undefined
let hasAwait = false let hasAwait = false
let hasInlinedSsrRenderFn = false let hasInlinedSsrRenderFn = false
@ -300,13 +300,13 @@ export function compileScript(
const typeArg = node.typeParameters.params[0] const typeArg = node.typeParameters.params[0]
if ( if (
typeArg.type === 'TSFunctionType' || typeArg.type === 'TSFunctionType' ||
typeArg.type === 'TSUnionType' typeArg.type === 'TSTypeLiteral'
) { ) {
emitTypeDecl = typeArg emitTypeDecl = typeArg
} else { } else {
error( error(
`type argument passed to ${DEFINE_EMIT}() must be a function type ` + `type argument passed to ${DEFINE_EMIT}() must be a function type ` +
`or a union of function types.`, `or a literal type with call signatures.`,
typeArg typeArg
) )
} }
@ -1304,20 +1304,25 @@ function toRuntimeTypeString(types: string[]) {
} }
function extractRuntimeEmits( function extractRuntimeEmits(
node: TSFunctionType | TSUnionType, node: TSFunctionType | TSTypeLiteral,
emits: Set<string> emits: Set<string>
) { ) {
if (node.type === 'TSUnionType') { if (node.type === 'TSTypeLiteral') {
for (let t of node.types) { for (let t of node.members) {
if (t.type === 'TSParenthesizedType') t = t.typeAnnotation if (t.type === 'TSCallSignatureDeclaration') {
if (t.type === 'TSFunctionType') { extractEventNames(t.parameters[0], emits)
extractRuntimeEmits(t, emits)
} }
} }
return return
} else {
extractEventNames(node.parameters[0], emits)
} }
}
const eventName = node.parameters[0] function extractEventNames(
eventName: Identifier | RestElement,
emits: Set<string>
) {
if ( if (
eventName.type === 'Identifier' && eventName.type === 'Identifier' &&
eventName.typeAnnotation && eventName.typeAnnotation &&

View File

@ -56,6 +56,15 @@ describe('defineEmit w/ type declaration', () => {
emit() emit()
// @ts-expect-error // @ts-expect-error
emit('bar') emit('bar')
type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
const emit2 = defineEmit<Emits>()
emit2('foo')
emit2('bar')
emit2('baz', 123)
// @ts-expect-error
emit2('baz')
}) })
describe('defineEmit w/ runtime declaration', () => { describe('defineEmit w/ runtime declaration', () => {