Skip to content

Commit

Permalink
fix(api): implement max recursion depth
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsdev committed Oct 8, 2022
1 parent 6fcfe62 commit 10c621e
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 170 deletions.
7 changes: 7 additions & 0 deletions packages/api/src/config.ts
@@ -0,0 +1,7 @@
// namespace Defaults {
// export const MAX_DEPTH = 6
// }

export class APIConfig {
public maxDepth: number = 6
}
1 change: 1 addition & 0 deletions packages/api/src/index.ts
@@ -1,3 +1,4 @@
export { APIConfig } from './config'
export { getSymbolType, multilineTypeToString } from "./util"
export { recursivelyExpandType } from "./merge"
export { generateTypeTree, getTypeInfoChildren } from "./tree"
Expand Down
129 changes: 77 additions & 52 deletions packages/api/src/merge.ts
@@ -1,69 +1,94 @@
import ts from "typescript"
import { APIConfig } from "./config"
import { createUnionType, createIntersectionType, createObjectType, TSSymbol, createSymbol, getSymbolType, SymbolName, ObjectType, getSignaturesOfType, getIndexInfos, getIntersectionTypesFlat } from "./util"

// TODO: need to add max depth
export function recursivelyExpandType(typeChecker: ts.TypeChecker, type: ts.Type, config?: APIConfig) {
config ??= new APIConfig()

export function recursivelyExpandType(typeChecker: ts.TypeChecker, type: ts.Type) {
return _recursivelyExpandType(typeChecker, [type], new WeakMap())
return _recursivelyExpandType(typeChecker, [type], {
seen: new WeakMap(),
maxDepth: config.maxDepth,
depth: 0,
})
}

function _recursivelyExpandType(typeChecker: ts.TypeChecker, types: ts.Type[], seen: WeakMap<ts.Type, ts.Type>): ts.Type {
if(types.length === 1 && seen.has(types[0])) {
return seen.get(types[0])!
}
type RecursiveExpandContext = {
seen: WeakMap<ts.Type, ts.Type>,
depth: number,
maxDepth: number
}

function _recursivelyExpandType(typeChecker: ts.TypeChecker, types: ts.Type[], ctx: RecursiveExpandContext): ts.Type {
const { seen } = ctx

ctx.depth++
const res = expandType()
ctx.depth--

const objectTypes: ts.ObjectType[] = []
const otherTypes: ts.Type[] = []
return res

function pushType(type: ts.Type) {
if(type.flags & ts.TypeFlags.Intersection) {
getIntersectionTypesFlat(type).forEach(pushType)
} else if(type.flags & ts.TypeFlags.Union) {
const newType = createUnionType(typeChecker)
otherTypes.push(newType)
seen.set(type, newType)
function expandType() {
if(types.length > 0 && ctx.depth > ctx.maxDepth) {
return types[0]
}

const unionTypeMembers = (type as ts.UnionType).types.map(t => _recursivelyExpandType(typeChecker, [t], seen))
newType.types = unionTypeMembers
} else if(type.flags & ts.TypeFlags.Object) {
if(getSignaturesOfType(typeChecker, type).length > 0) {
// function type
otherTypes.push(type)
} else if(getIndexInfos(typeChecker, type).length > 0) {
// mapped type
if(types.length === 1 && seen.has(types[0])) {
return seen.get(types[0])!
}

const objectTypes: ts.ObjectType[] = []
const otherTypes: ts.Type[] = []

function pushType(type: ts.Type) {
if(type.flags & ts.TypeFlags.Intersection) {
getIntersectionTypesFlat(type).forEach(pushType)
} else if(type.flags & ts.TypeFlags.Union) {
const newType = createUnionType(typeChecker)
otherTypes.push(newType)
seen.set(type, newType)

const unionTypeMembers = (type as ts.UnionType).types.map(t => _recursivelyExpandType(typeChecker, [t], ctx))
newType.types = unionTypeMembers
} else if(type.flags & ts.TypeFlags.Object) {
if(getSignaturesOfType(typeChecker, type).length > 0) {
// function type
otherTypes.push(type)
} else if(getIndexInfos(typeChecker, type).length > 0) {
// mapped type
otherTypes.push(type)
} else { // TODO: array types
objectTypes.push(type as ts.ObjectType)
}
} else {
otherTypes.push(type)
} else { // TODO: array types
objectTypes.push(type as ts.ObjectType)
}
}

types.forEach(pushType)

if(otherTypes.length === 1 && objectTypes.length === 0) {
const newType = cloneTypeWithoutAlias(otherTypes[0])
seen.set(otherTypes[0], newType)

return newType
} else if(otherTypes.length === 0 && objectTypes.length > 0) {
const newType = createAnonymousObjectType()
if(types.length === 1) seen.set(types[0], newType)

recursiveMergeObjectIntersection(objectTypes, newType)

return newType
} else {
otherTypes.push(type)
const newType = createIntersectionType(typeChecker)
if(types.length === 1) seen.set(types[0], newType)

const objectType = recursiveMergeObjectIntersection(objectTypes)
newType.types = [...(objectType ? [objectType] : []), ...otherTypes]

return newType
}
}

types.forEach(pushType)

if(otherTypes.length === 1 && objectTypes.length === 0) {
const newType = cloneTypeWithoutAlias(otherTypes[0])
seen.set(otherTypes[0], newType)

return newType
} else if(otherTypes.length === 0 && objectTypes.length > 0) {
const newType = createAnonymousObjectType()
if(types.length === 1) seen.set(types[0], newType)

recursiveMergeObjectIntersection(objectTypes, newType)

return newType
} else {
const newType = createIntersectionType(typeChecker)
if(types.length === 1) seen.set(types[0], newType)

const objectType = recursiveMergeObjectIntersection(objectTypes)
newType.types = [...(objectType ? [objectType] : []), ...otherTypes]

return newType
}

function createAnonymousObjectType() {
return createObjectType(typeChecker, ts.ObjectFlags.Anonymous)
}
Expand Down Expand Up @@ -103,7 +128,7 @@ function _recursivelyExpandType(typeChecker: ts.TypeChecker, types: ts.Type[], s

const types = symbols.map(s => getSymbolType(typeChecker, s))

propertySymbol.type = _recursivelyExpandType(typeChecker, types, seen)
propertySymbol.type = _recursivelyExpandType(typeChecker, types, ctx)

return propertySymbol
}
Expand Down

0 comments on commit 10c621e

Please sign in to comment.