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

View File

@ -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
}