import {
Identifier,
- Node,
+ Node as _Node,
Statement,
TSCallSignatureDeclaration,
TSEnumDeclaration,
TSFunctionType,
TSMappedType,
TSMethodSignature,
+ TSModuleBlock,
+ TSModuleDeclaration,
TSPropertySignature,
TSQualifiedName,
TSType,
export interface TypeScope {
filename: string
- body: Statement[]
imports: Record<string, ImportBinding>
types: Record<string, Node>
+ parent?: TypeScope
+}
+
+interface WithScope {
+ _ownerScope?: TypeScope
}
interface ResolvedElements {
- props: Record<string, TSPropertySignature | TSMethodSignature>
+ props: Record<string, (TSPropertySignature | TSMethodSignature) & WithScope>
calls?: (TSCallSignatureDeclaration | TSFunctionType)[]
}
+type Node = _Node &
+ WithScope & {
+ _resolvedElements?: ResolvedElements
+ }
+
/**
* Resolve arbitrary type node to a list of type elements that can be then
* mapped to runtime props or emits.
*/
export function resolveTypeElements(
ctx: ScriptCompileContext,
- node: Node & { _resolvedElements?: ResolvedElements }
+ node: Node
): ResolvedElements {
if (node._resolvedElements) {
return node._resolvedElements
): ResolvedElements {
switch (node.type) {
case 'TSTypeLiteral':
- return typeElementsToMap(ctx, node.members)
+ return typeElementsToMap(ctx, node.members, node._ownerScope)
case 'TSInterfaceDeclaration':
return resolveInterfaceMembers(ctx, node)
case 'TSTypeAliasDeclaration':
function typeElementsToMap(
ctx: ScriptCompileContext,
- elements: TSTypeElement[]
+ elements: TSTypeElement[],
+ scope = ctxToScope(ctx)
): ResolvedElements {
const res: ResolvedElements = { props: {} }
for (const e of elements) {
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
+ ;(e as Node)._ownerScope = scope
const name =
e.key.type === 'Identifier'
? e.key.name
function resolveInterfaceMembers(
ctx: ScriptCompileContext,
- node: TSInterfaceDeclaration
+ node: TSInterfaceDeclaration & WithScope
): ResolvedElements {
- const base = typeElementsToMap(ctx, node.body.body)
+ const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
if (node.extends) {
for (const ext of node.extends) {
const { props } = resolveTypeElements(ctx, ext)
function resolveTypeReference(
ctx: ScriptCompileContext,
- node: TSTypeReference | TSExpressionWithTypeArguments,
- scope = getRootScope(ctx)
+ node: (TSTypeReference | TSExpressionWithTypeArguments) & {
+ _resolvedReference?: Node
+ },
+ scope = ctxToScope(ctx)
): Node | undefined {
+ if (node._resolvedReference) {
+ return node._resolvedReference
+ }
const name = getReferenceName(node)
+ return (node._resolvedReference = innerResolveTypeReference(scope, name))
+}
+
+function innerResolveTypeReference(
+ scope: TypeScope,
+ name: string | string[]
+): Node | undefined {
if (typeof name === 'string') {
if (scope.imports[name]) {
// TODO external import
return scope.types[name]
}
} else {
- // TODO qualified name, e.g. Foo.Bar
- // return resolveTypeReference()
+ const ns = innerResolveTypeReference(scope, name[0])
+ if (ns && ns.type === 'TSModuleDeclaration') {
+ const childScope = moduleDeclToScope(ns, scope)
+ return innerResolveTypeReference(
+ childScope,
+ name.length > 2 ? name.slice(1) : name[name.length - 1]
+ )
+ }
}
}
}
}
-function getRootScope(ctx: ScriptCompileContext): TypeScope {
+function ctxToScope(ctx: ScriptCompileContext): TypeScope {
if (ctx.scope) {
return ctx.scope
}
return (ctx.scope = {
filename: ctx.descriptor.filename,
imports: ctx.userImports,
- types: recordTypes(body),
- body
+ types: recordTypes(body)
})
}
-function recordTypes(body: Statement[]) {
- const types: Record<string, Node> = Object.create(null)
+function moduleDeclToScope(
+ node: TSModuleDeclaration & { _resolvedChildScope?: TypeScope },
+ parent: TypeScope
+): TypeScope {
+ if (node._resolvedChildScope) {
+ return node._resolvedChildScope
+ }
+ const types: TypeScope['types'] = Object.create(parent.types)
+ const scope: TypeScope = {
+ filename: parent.filename,
+ imports: Object.create(parent.imports),
+ types: recordTypes((node.body as TSModuleBlock).body, types),
+ parent
+ }
+ for (const key of Object.keys(types)) {
+ types[key]._ownerScope = scope
+ }
+ return (node._resolvedChildScope = scope)
+}
+
+function recordTypes(
+ body: Statement[],
+ types: Record<string, Node> = Object.create(null)
+) {
for (const s of body) {
recordType(s, types)
}
types[node.id.name] = node.typeAnnotation
break
case 'ExportNamedDeclaration': {
- if (node.exportKind === 'type') {
- recordType(node.declaration!, types)
+ if (node.declaration) {
+ recordType(node.declaration, types)
}
break
}
export function inferRuntimeType(
ctx: ScriptCompileContext,
node: Node,
- scope = getRootScope(ctx)
+ scope = node._ownerScope || ctxToScope(ctx)
): string[] {
switch (node.type) {
case 'TSStringKeyword':
}
case 'TSPropertySignature':
if (node.typeAnnotation) {
- return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation)
+ return inferRuntimeType(ctx, node.typeAnnotation.typeAnnotation, scope)
}
case 'TSMethodSignature':
case 'TSFunctionType':