mirror of https://github.com/vuejs/core.git
feat(compiler-sfc): support string indexed type in macros
This commit is contained in:
parent
51773d5d1d
commit
3f779ddbf8
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue