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

This commit is contained in:
Evan You 2023-04-13 11:21:09 +08:00
parent 51773d5d1d
commit 3f779ddbf8
2 changed files with 79 additions and 12 deletions

View File

@ -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', () => { describe('errors', () => {
test('error on computed keys', () => { test('error on computed keys', () => {
expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow( expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow(

View File

@ -1,4 +1,5 @@
import { import {
Identifier,
Node, Node,
Statement, Statement,
TSCallSignatureDeclaration, TSCallSignatureDeclaration,
@ -8,6 +9,7 @@ import {
TSMappedType, TSMappedType,
TSMethodSignature, TSMethodSignature,
TSPropertySignature, TSPropertySignature,
TSQualifiedName,
TSType, TSType,
TSTypeAnnotation, TSTypeAnnotation,
TSTypeElement, TSTypeElement,
@ -62,6 +64,34 @@ function innerResolveTypeElements(
case 'TSFunctionType': { case 'TSFunctionType': {
return { props: {}, calls: [node] } 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 'TSExpressionWithTypeArguments': // referenced by interface extends
case 'TSTypeReference': { case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node) 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( function typeElementsToMap(
@ -342,8 +364,15 @@ function getReferenceName(
if (ref.type === 'Identifier') { if (ref.type === 'Identifier') {
return ref.name return ref.name
} else { } else {
// TODO qualified name, e.g. Foo.Bar return qualifiedNameToPath(ref)
return [] }
}
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) { switch (node.type) {
case 'TSInterfaceDeclaration': case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration': 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 break
}
case 'TSTypeAliasDeclaration': case 'TSTypeAliasDeclaration':
types[node.id.name] = node.typeAnnotation types[node.id.name] = node.typeAnnotation
break break
@ -542,6 +574,17 @@ export function inferRuntimeType(
case 'TSSymbolKeyword': case 'TSSymbolKeyword':
return ['Symbol'] 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: default:
return [UNKNOWN_TYPE] // no runtime check return [UNKNOWN_TYPE] // no runtime check
} }