]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): enhance type resolution with support for generic type parameters
authordaiwei <daiwei521@126.com>
Fri, 14 Feb 2025 03:23:07 +0000 (11:23 +0800)
committerdaiwei <daiwei521@126.com>
Fri, 14 Feb 2025 03:23:07 +0000 (11:23 +0800)
packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
packages/compiler-sfc/src/script/resolveType.ts

index 816d74bc64905a4daf1e7aa30147d261d48de4b4..cf96d5858fb5841cffc6434c05bace2e3e7afe67 100644 (file)
@@ -521,7 +521,7 @@ describe('resolveType', () => {
 
     expect(props).toStrictEqual({
       foo: ['Symbol', 'String', 'Number'],
-      bar: [UNKNOWN_TYPE],
+      bar: ['String', 'Number'],
     })
   })
 
@@ -779,6 +779,28 @@ describe('resolveType', () => {
       })
     })
 
+    test('generic with union type', () => {
+      expect(
+        resolve(`
+        type Wrapped<T> = T | symbol | number
+        defineProps<{foo?: Wrapped<boolean>}>()
+      `).props,
+      ).toStrictEqual({
+        foo: ['Boolean', 'Symbol', 'Number'],
+      })
+    })
+
+    test('generic type /w intersection', () => {
+      expect(
+        resolve(`
+        type Wrapped<T> = T & symbol & number
+        defineProps<{foo?: Wrapped<boolean>}>()
+      `).props,
+      ).toStrictEqual({
+        foo: ['Boolean', 'Symbol', 'Number'],
+      })
+    })
+
     test('generic from external-file', () => {
       const files = {
         '/foo.ts': 'export type P<T> = { foo: T }',
index 6bb647f11ff2ac418827e85ac1e1cdb7ce3b2b59..38726896b99d2c955c7da9cc3faa23e907b8b18a 100644 (file)
@@ -1466,6 +1466,7 @@ export function inferRuntimeType(
   node: Node & MaybeWithScope,
   scope: TypeScope = node._ownerScope || ctxToScope(ctx),
   isKeyOf = false,
+  typeParameters: Record<string, Node> | undefined = undefined,
 ): string[] {
   try {
     switch (node.type) {
@@ -1554,10 +1555,39 @@ export function inferRuntimeType(
       case 'TSTypeReference': {
         const resolved = resolveTypeReference(ctx, node, scope)
         if (resolved) {
-          return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
+          if (!node.typeParameters) {
+            return inferRuntimeType(
+              ctx,
+              resolved,
+              resolved._ownerScope,
+              isKeyOf,
+            )
+          } else if (resolved.type === 'TSTypeAliasDeclaration') {
+            const typeParams = Object.create(null)
+            if (resolved.typeParameters) {
+              resolved.typeParameters.params.forEach((p, i) => {
+                typeParams![p.name] = node.typeParameters!.params[i]
+              })
+            }
+            return inferRuntimeType(
+              ctx,
+              resolved.typeAnnotation,
+              resolved._ownerScope,
+              isKeyOf,
+              typeParams,
+            )
+          }
         }
-
         if (node.typeName.type === 'Identifier') {
+          if (typeParameters && typeParameters[node.typeName.name]) {
+            return inferRuntimeType(
+              ctx,
+              typeParameters[node.typeName.name],
+              scope,
+              isKeyOf,
+              typeParameters,
+            )
+          }
           if (isKeyOf) {
             switch (node.typeName.name) {
               case 'String':
@@ -1690,11 +1720,15 @@ export function inferRuntimeType(
         return inferRuntimeType(ctx, node.typeAnnotation, scope)
 
       case 'TSUnionType':
-        return flattenTypes(ctx, node.types, scope, isKeyOf)
+        return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
       case 'TSIntersectionType': {
-        return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
-          t => t !== UNKNOWN_TYPE,
-        )
+        return flattenTypes(
+          ctx,
+          node.types,
+          scope,
+          isKeyOf,
+          typeParameters,
+        ).filter(t => t !== UNKNOWN_TYPE)
       }
 
       case 'TSEnumDeclaration':
@@ -1765,14 +1799,17 @@ function flattenTypes(
   types: TSType[],
   scope: TypeScope,
   isKeyOf: boolean = false,
+  typeParameters: Record<string, Node> | undefined = undefined,
 ): string[] {
   if (types.length === 1) {
-    return inferRuntimeType(ctx, types[0], scope, isKeyOf)
+    return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
   }
   return [
     ...new Set(
       ([] as string[]).concat(
-        ...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
+        ...types.map(t =>
+          inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
+        ),
       ),
     ),
   ]