Jelajahi Sumber

feat(compiler-sfc): support string indexed type in macros

Evan You 3 tahun lalu
induk
melakukan
3f779ddbf8

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

@@ -215,6 +215,30 @@ describe('resolveType', () => {
     })
   })
 
+  test('indexed access type', () => {
+    expect(
+      resolve(`
+    type T = { bar: number }
+    type S = { nested: { foo: T['bar'] }}
+    type Target = S['nested']
+    `).props
+    ).toStrictEqual({
+      foo: ['Number']
+    })
+  })
+
+  // test('namespace', () => {
+  //   expect(
+  //     resolve(`
+  //   type T = { foo: number, bar: string, baz: boolean }
+  //   type K = 'foo' | 'bar'
+  //   type Target = Omit<T, K>
+  //   `).props
+  //   ).toStrictEqual({
+  //     baz: ['Boolean']
+  //   })
+  // })
+
   describe('errors', () => {
     test('error on computed keys', () => {
       expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow(

+ 55 - 12
packages/compiler-sfc/src/script/resolveType.ts

@@ -1,4 +1,5 @@
 import {
+  Identifier,
   Node,
   Statement,
   TSCallSignatureDeclaration,
@@ -8,6 +9,7 @@ import {
   TSMappedType,
   TSMethodSignature,
   TSPropertySignature,
+  TSQualifiedName,
   TSType,
   TSTypeAnnotation,
   TSTypeElement,
@@ -62,6 +64,34 @@ function innerResolveTypeElements(
     case 'TSFunctionType': {
       return { props: {}, calls: [node] }
     }
+    case 'TSUnionType':
+    case 'TSIntersectionType':
+      return mergeElements(
+        node.types.map(t => resolveTypeElements(ctx, t)),
+        node.type
+      )
+    case 'TSMappedType':
+      return resolveMappedType(ctx, node)
+    case 'TSIndexedAccessType': {
+      if (
+        node.indexType.type === 'TSLiteralType' &&
+        node.indexType.literal.type === 'StringLiteral'
+      ) {
+        const resolved = resolveTypeElements(ctx, node.objectType)
+        const key = node.indexType.literal.value
+        const targetType = resolved.props[key].typeAnnotation
+        if (targetType) {
+          return resolveTypeElements(ctx, targetType.typeAnnotation)
+        } else {
+          break
+        }
+      } else {
+        ctx.error(
+          `Unsupported index type: ${node.indexType.type}`,
+          node.indexType
+        )
+      }
+    }
     case 'TSExpressionWithTypeArguments': // referenced by interface extends
     case 'TSTypeReference': {
       const resolved = resolveTypeReference(ctx, node)
@@ -82,16 +112,8 @@ function innerResolveTypeElements(
         )
       }
     }
-    case 'TSUnionType':
-    case 'TSIntersectionType':
-      return mergeElements(
-        node.types.map(t => resolveTypeElements(ctx, t)),
-        node.type
-      )
-    case 'TSMappedType':
-      return resolveMappedType(ctx, node)
   }
-  ctx.error(`Unsupported type in SFC macro: ${node.type}`, node)
+  ctx.error(`Unresolvable type in SFC macro: ${node.type}`, node)
 }
 
 function typeElementsToMap(
@@ -342,8 +364,15 @@ function getReferenceName(
   if (ref.type === 'Identifier') {
     return ref.name
   } else {
-    // TODO qualified name, e.g. Foo.Bar
-    return []
+    return qualifiedNameToPath(ref)
+  }
+}
+
+function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] {
+  if (node.type === 'Identifier') {
+    return [node.name]
+  } else {
+    return [...qualifiedNameToPath(node.left), node.right.name]
   }
 }
 
@@ -376,8 +405,11 @@ function recordType(node: Node, types: Record<string, Node>) {
   switch (node.type) {
     case 'TSInterfaceDeclaration':
     case 'TSEnumDeclaration':
-      types[node.id.name] = node
+    case 'TSModuleDeclaration': {
+      const id = node.id.type === 'Identifier' ? node.id.name : node.id.value
+      types[id] = node
       break
+    }
     case 'TSTypeAliasDeclaration':
       types[node.id.name] = node.typeAnnotation
       break
@@ -542,6 +574,17 @@ export function inferRuntimeType(
     case 'TSSymbolKeyword':
       return ['Symbol']
 
+    case 'TSIndexedAccessType': {
+      if (
+        node.indexType.type === 'TSLiteralType' &&
+        node.indexType.literal.type === 'StringLiteral'
+      ) {
+        const resolved = resolveTypeElements(ctx, node.objectType)
+        const key = node.indexType.literal.value
+        return inferRuntimeType(ctx, resolved.props[key])
+      }
+    }
+
     default:
       return [UNKNOWN_TYPE] // no runtime check
   }