浏览代码

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

fix #2874
Evan You 5 年之前
父节点
当前提交
bb8cdcad9f

+ 2 - 2
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

@@ -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'
 
       
@@ -700,7 +700,7 @@ export default _defineComponent({
   expose: [],
   emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
   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,
         attrs: any
       }) {

+ 12 - 0
packages/compiler-sfc/__tests__/compileScript.spec.ts

@@ -532,6 +532,18 @@ const emit = defineEmit(['a', 'b'])
 
     test('defineEmit w/ type (union)', () => {
       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(`
       <script setup lang="ts">
       import { defineEmit } from 'vue'

+ 17 - 12
packages/compiler-sfc/src/compileScript.ts

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

+ 9 - 0
test-dts/setupHelpers.test-d.ts

@@ -56,6 +56,15 @@ describe('defineEmit w/ type declaration', () => {
   emit()
   // @ts-expect-error
   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', () => {