diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index 5d5a424f2..1485cfe83 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/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 + // `).props + // ).toStrictEqual({ + // baz: ['Boolean'] + // }) + // }) + describe('errors', () => { test('error on computed keys', () => { expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index ecd3838be..0b6969ad8 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/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) { 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 }