type Import = Pick<ImportBinding, 'source' | 'imported'>
-type ScopeTypeNode = Node & {
- // scope types always has ownerScope attached
+interface WithScope {
_ownerScope: TypeScope
}
+// scope types always has ownerScope attached
+type ScopeTypeNode = Node &
+ WithScope & { _ns?: TSModuleDeclaration & WithScope }
+
export interface TypeScope {
filename: string
source: string
exportedTypes: Record<string, ScopeTypeNode>
}
-export interface WithScope {
+export interface MaybeWithScope {
_ownerScope?: TypeScope
}
*/
export function resolveTypeElements(
ctx: TypeResolveContext,
- node: Node & WithScope & { _resolvedElements?: ResolvedElements },
+ node: Node & MaybeWithScope & { _resolvedElements?: ResolvedElements },
scope?: TypeScope
): ResolvedElements {
if (node._resolvedElements) {
const res: ResolvedElements = { props: {} }
for (const e of elements) {
if (e.type === 'TSPropertySignature' || e.type === 'TSMethodSignature') {
- ;(e as WithScope)._ownerScope = scope
+ ;(e as MaybeWithScope)._ownerScope = scope
const name = getId(e.key)
if (name && !e.computed) {
res.props[name] = e as ResolvedElements['props'][string]
function resolveInterfaceMembers(
ctx: TypeResolveContext,
- node: TSInterfaceDeclaration & WithScope,
+ node: TSInterfaceDeclaration & MaybeWithScope,
scope: TypeScope
): ResolvedElements {
const base = typeElementsToMap(ctx, node.body.body, node._ownerScope)
ctx: TypeResolveContext,
node: TSIndexedAccessType,
scope: TypeScope
-): (TSType & WithScope)[] {
+): (TSType & MaybeWithScope)[] {
if (node.indexType.type === 'TSNumberKeyword') {
return resolveArrayElementType(ctx, node.objectType, scope)
}
for (const key of keys) {
const targetType = resolved.props[key]?.typeAnnotation?.typeAnnotation
if (targetType) {
- ;(targetType as TSType & WithScope)._ownerScope =
+ ;(targetType as TSType & MaybeWithScope)._ownerScope =
resolved.props[key]._ownerScope
types.push(targetType)
}
}
}
} else {
- const ns = innerResolveTypeReference(
- ctx,
- scope,
- name[0],
- node,
- onlyExported
- )
- if (ns && ns.type === 'TSModuleDeclaration') {
- const childScope = moduleDeclToScope(ns, scope)
- return innerResolveTypeReference(
- ctx,
- childScope,
- name.length > 2 ? name.slice(1) : name[name.length - 1],
- node,
- !ns.declare
- )
+ let ns = innerResolveTypeReference(ctx, scope, name[0], node, onlyExported)
+ if (ns) {
+ if (ns.type !== 'TSModuleDeclaration') {
+ // namespace merged with other types, attached as _ns
+ ns = ns._ns
+ }
+ if (ns) {
+ const childScope = moduleDeclToScope(ns, ns._ownerScope || scope)
+ return innerResolveTypeReference(
+ ctx,
+ childScope,
+ name.length > 2 ? name.slice(1) : name[name.length - 1],
+ node,
+ !ns.declare
+ )
+ }
}
}
}
exportedTypes: Object.create(null)
}
recordTypes(body, scope, asGlobal)
-
fileToScopeCache.set(filename, scope)
return scope
}
}
const scope: TypeScope = {
...parentScope,
+ imports: Object.create(parentScope.imports),
+ // TODO this seems wrong
types: Object.create(parentScope.types),
- imports: Object.create(parentScope.imports)
+ exportedTypes: Object.create(null)
+ }
+
+ if (node.body.type === 'TSModuleDeclaration') {
+ const decl = node.body as TSModuleDeclaration & WithScope
+ decl._ownerScope = scope
+ const id = getId(decl.id)
+ scope.types[id] = scope.exportedTypes[id] = decl
+ } else {
+ recordTypes(node.body.body, scope)
}
- recordTypes((node.body as TSModuleBlock).body, scope)
+
return (node._resolvedChildScope = scope)
}
}
}
for (const key of Object.keys(types)) {
- types[key]._ownerScope = scope
+ const node = types[key]
+ node._ownerScope = scope
+ if (node._ns) node._ns._ownerScope = scope
}
}
switch (node.type) {
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration':
- case 'TSModuleDeclaration':
- case 'ClassDeclaration': {
- const id = node.id.type === 'Identifier' ? node.id.name : node.id.value
- types[id] = node
+ case 'TSModuleDeclaration': {
+ const id = getId(node.id)
+ let existing = types[id]
+ if (existing) {
+ if (node.type === 'TSModuleDeclaration') {
+ if (existing.type === 'TSModuleDeclaration') {
+ mergeNamespaces(existing as typeof node, node)
+ } else {
+ attachNamespace(existing, node)
+ }
+ break
+ }
+ if (existing.type === 'TSModuleDeclaration') {
+ // replace and attach namespace
+ types[id] = node
+ attachNamespace(node, existing)
+ break
+ }
+
+ if (existing.type !== node.type) {
+ // type-level error
+ break
+ }
+ if (node.type === 'TSInterfaceDeclaration') {
+ ;(existing as typeof node).body.body.push(...node.body.body)
+ } else {
+ ;(existing as typeof node).members.push(...node.members)
+ }
+ } else {
+ types[id] = node
+ }
break
}
+ case 'ClassDeclaration':
+ types[getId(node.id)] = node
+ break
case 'TSTypeAliasDeclaration':
types[node.id.name] = node.typeAnnotation
break
}
}
+function mergeNamespaces(to: TSModuleDeclaration, from: TSModuleDeclaration) {
+ const toBody = to.body
+ const fromBody = from.body
+ if (toBody.type === 'TSModuleDeclaration') {
+ if (fromBody.type === 'TSModuleDeclaration') {
+ // both decl
+ mergeNamespaces(toBody, fromBody)
+ } else {
+ // to: decl -> from: block
+ fromBody.body.push({
+ type: 'ExportNamedDeclaration',
+ declaration: toBody,
+ exportKind: 'type',
+ specifiers: []
+ })
+ }
+ } else if (fromBody.type === 'TSModuleDeclaration') {
+ // to: block <- from: decl
+ toBody.body.push({
+ type: 'ExportNamedDeclaration',
+ declaration: fromBody,
+ exportKind: 'type',
+ specifiers: []
+ })
+ } else {
+ // both block
+ toBody.body.push(...fromBody.body)
+ }
+}
+
+function attachNamespace(
+ to: Node & { _ns?: TSModuleDeclaration },
+ ns: TSModuleDeclaration
+) {
+ if (!to._ns) {
+ to._ns = ns
+ } else {
+ mergeNamespaces(to._ns, ns)
+ }
+}
+
export function recordImports(body: Statement[]) {
const imports: TypeScope['imports'] = Object.create(null)
for (const s of body) {
export function inferRuntimeType(
ctx: TypeResolveContext,
- node: Node & WithScope,
+ node: Node & MaybeWithScope,
scope = node._ownerScope || ctxToScope(ctx)
): string[] {
switch (node.type) {
}
case 'TSTypeReference':
+ const resolved = resolveTypeReference(ctx, node, scope)
+ if (resolved) {
+ return inferRuntimeType(ctx, resolved, resolved._ownerScope)
+ }
if (node.typeName.type === 'Identifier') {
- const resolved = resolveTypeReference(ctx, node, scope)
- if (resolved) {
- return inferRuntimeType(ctx, resolved, resolved._ownerScope)
- }
switch (node.typeName.name) {
case 'Array':
case 'Function':