Skip to content

Commit

Permalink
Support Recursive Type Code Generation
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Mar 29, 2023
1 parent c5d5018 commit 31d0e8b
Showing 1 changed file with 17 additions and 4 deletions.
21 changes: 17 additions & 4 deletions codegen/typescript-to-typebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ import * as ts from 'typescript'

/** Generates TypeBox types from TypeScript code */
export namespace TypeScriptToTypeBox {
function isRecursiveType(decl: ts.InterfaceDeclaration | ts.TypeAliasDeclaration) {
function find(decl: ts.InterfaceDeclaration | ts.TypeAliasDeclaration, node: ts.Node): boolean {
return (ts.isTypeReferenceNode(node) && decl.name.getText() === node.getText()) || node.getChildren().some((node) => find(decl, node))
}
return ts.isTypeAliasDeclaration(decl) ? [decl.type].some((node) => find(decl, node)) : decl.members.some((node) => find(decl, node))
}
function isReadonlyProperty(node: ts.PropertySignature): boolean {
return node.modifiers !== undefined && node.modifiers.find((modifier) => modifier.getText() === 'readonly') !== undefined
}
Expand Down Expand Up @@ -110,6 +116,7 @@ export namespace TypeScriptToTypeBox {
}
function* InterfaceDeclaration(node: ts.InterfaceDeclaration): IterableIterator<string> {
useImports = true
const heritage = node.heritageClauses !== undefined ? node.heritageClauses.flatMap((node) => node.types.map((node) => node.getText())) : []
if (node.typeParameters) {
useGenerics = true
const exports = isExport(node) ? 'export ' : ''
Expand All @@ -118,13 +125,17 @@ export namespace TypeScriptToTypeBox {
const names = node.typeParameters.map((param) => `${Collect(param)}`).join(', ')
const members = node.members.map((member) => Collect(member)).join(',\n')
const staticDeclaration = `${exports}type ${node.name.getText()}<${constraints}> = Static<ReturnType<typeof ${node.name.getText()}<${names}>>>`
const typeDeclaration = `${exports}const ${node.name.getText()} = <${constraints}>(${parameters}) => Type.Object({\n${members}\n})`
const rawTypeExpression = isRecursiveType(node) ? `Type.Recursive(${node.name.getText()} => Type.Object({\n${members}\n}))` : `Type.Object({\n${members}\n})`
const typeExpression = heritage.length === 0 ? rawTypeExpression : `Type.Intersect([${heritage.join(', ')}, ${rawTypeExpression}])`
const typeDeclaration = `${exports}const ${node.name.getText()} = <${constraints}>(${parameters}) => ${typeExpression}`
yield `${staticDeclaration}\n${typeDeclaration}`
} else {
const exports = isExport(node) ? 'export ' : ''
const members = node.members.map((member) => Collect(member)).join(',\n')
const staticDeclaration = `${exports}type ${node.name.getText()} = Static<typeof ${node.name.getText()}>`
const typeDeclaration = `${exports}const ${node.name.getText()} = Type.Object({\n${members}\n})`
const rawTypeExpression = isRecursiveType(node) ? `Type.Recursive(${node.name.getText()} => Type.Object({\n${members}\n}))` : `Type.Object({\n${members}\n})`
const typeExpression = heritage.length === 0 ? rawTypeExpression : `Type.Intersect([${heritage.join(', ')}, ${rawTypeExpression}])`
const typeDeclaration = `${exports}const ${node.name.getText()} = ${typeExpression}`
yield `${staticDeclaration}\n${typeDeclaration}`
}
}
Expand All @@ -138,13 +149,15 @@ export namespace TypeScriptToTypeBox {
const names = node.typeParameters.map((param) => Collect(param)).join(', ')
const type = Collect(node.type)
const staticDeclaration = `${exports}type ${node.name.getText()}<${constraints}> = Static<ReturnType<typeof ${node.name.getText()}<${names}>>>`
const typeDeclaration = `${exports}const ${node.name.getText()} = <${constraints}>(${parameters}) => ${type}`
const typeDeclaration = isRecursiveType(node)
? `${exports}const ${node.name.getText()} = <${constraints}>(${parameters}) => Type.Recursive(${node.name.getText()} => ${type})`
: `${exports}const ${node.name.getText()} = <${constraints}>(${parameters}) => ${type}`
yield `${staticDeclaration}\n${typeDeclaration}`
} else {
const exports = isExport(node) ? 'export ' : ''
const type = Collect(node.type)
const staticDeclaration = `${exports}type ${node.name.getText()} = Static<typeof ${node.name.getText()}>`
const typeDeclaration = `${exports}const ${node.name.getText()} = ${type}`
const typeDeclaration = isRecursiveType(node) ? `${exports}const ${node.name.getText()} = Type.Recursive(${node.name.getText()} => ${type})` : `${exports}const ${node.name.getText()} = ${type}`
yield `${staticDeclaration}\n${typeDeclaration}`
}
}
Expand Down

0 comments on commit 31d0e8b

Please sign in to comment.