diff --git a/src/handlers/typebox/base-type-handler.ts b/src/handlers/typebox/base-type-handler.ts index c9eaf39..3228da8 100644 --- a/src/handlers/typebox/base-type-handler.ts +++ b/src/handlers/typebox/base-type-handler.ts @@ -1,6 +1,7 @@ +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { Node, ts } from 'ts-morph' export abstract class BaseTypeHandler { abstract canHandle(node: Node): boolean - abstract handle(node: Node): ts.Expression + abstract handle(node: Node, context: TypeBoxContext): ts.Expression } diff --git a/src/handlers/typebox/collection/array-type-handler.ts b/src/handlers/typebox/collection/array-type-handler.ts index cf6f41c..843a6e7 100644 --- a/src/handlers/typebox/collection/array-type-handler.ts +++ b/src/handlers/typebox/collection/array-type-handler.ts @@ -1,6 +1,7 @@ import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { ArrayTypeNode, Node, ts } from 'ts-morph' export class ArrayTypeHandler extends CollectionBaseHandler { @@ -8,9 +9,9 @@ export class ArrayTypeHandler extends CollectionBaseHandler { return Node.isArrayTypeNode(node) } - handle(node: ArrayTypeNode): ts.Expression { - const typeboxType = getTypeBoxType(node.getElementTypeNode()) + handle(node: ArrayTypeNode, context: TypeBoxContext): ts.Expression { + const typeboxType = getTypeBoxType(node.getElementTypeNode(), context) - return makeTypeCall('Array', [typeboxType]) + return GenericTypeUtils.makeTypeCall('Array', [typeboxType]) } } diff --git a/src/handlers/typebox/collection/collection-base-handler.ts b/src/handlers/typebox/collection/collection-base-handler.ts index ad5c621..cb9ac99 100644 --- a/src/handlers/typebox/collection/collection-base-handler.ts +++ b/src/handlers/typebox/collection/collection-base-handler.ts @@ -1,13 +1,18 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { Node, ts } from 'ts-morph' export abstract class CollectionBaseHandler extends BaseTypeHandler { - protected processTypeCollection(nodes: Node[], typeBoxFunction: string): ts.Expression { - const typeBoxTypes = nodes.map((node) => getTypeBoxType(node)) + protected processTypeCollection( + nodes: Node[], + typeBoxFunction: string, + context: TypeBoxContext, + ): ts.Expression { + const typeBoxTypes = nodes.map((node) => getTypeBoxType(node, context)) const arrayLiteral = ts.factory.createArrayLiteralExpression(typeBoxTypes) - return makeTypeCall(typeBoxFunction, [arrayLiteral]) + return GenericTypeUtils.makeTypeCall(typeBoxFunction, [arrayLiteral]) } } diff --git a/src/handlers/typebox/collection/intersection-type-handler.ts b/src/handlers/typebox/collection/intersection-type-handler.ts index ac23ce0..be28890 100644 --- a/src/handlers/typebox/collection/intersection-type-handler.ts +++ b/src/handlers/typebox/collection/intersection-type-handler.ts @@ -1,4 +1,5 @@ import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { IntersectionTypeNode, Node, ts } from 'ts-morph' export class IntersectionTypeHandler extends CollectionBaseHandler { @@ -6,7 +7,7 @@ export class IntersectionTypeHandler extends CollectionBaseHandler { return Node.isIntersectionTypeNode(node) } - handle(node: IntersectionTypeNode): ts.Expression { - return this.processTypeCollection(node.getTypeNodes(), 'Intersect') + handle(node: IntersectionTypeNode, context: TypeBoxContext): ts.Expression { + return this.processTypeCollection(node.getTypeNodes(), 'Intersect', context) } } diff --git a/src/handlers/typebox/collection/tuple-type-handler.ts b/src/handlers/typebox/collection/tuple-type-handler.ts index ac981ca..4f15164 100644 --- a/src/handlers/typebox/collection/tuple-type-handler.ts +++ b/src/handlers/typebox/collection/tuple-type-handler.ts @@ -1,4 +1,5 @@ import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { Node, ts, TupleTypeNode } from 'ts-morph' export class TupleTypeHandler extends CollectionBaseHandler { @@ -6,7 +7,8 @@ export class TupleTypeHandler extends CollectionBaseHandler { return Node.isTupleTypeNode(node) } - handle(node: TupleTypeNode): ts.Expression { - return this.processTypeCollection(node.getElements(), 'Tuple') + handle(node: TupleTypeNode, context: TypeBoxContext): ts.Expression { + const elements = node.getElements() + return this.processTypeCollection(elements, 'Tuple', context) } } diff --git a/src/handlers/typebox/collection/union-type-handler.ts b/src/handlers/typebox/collection/union-type-handler.ts index d070c42..c8ae592 100644 --- a/src/handlers/typebox/collection/union-type-handler.ts +++ b/src/handlers/typebox/collection/union-type-handler.ts @@ -1,4 +1,5 @@ import { CollectionBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/collection/collection-base-handler' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { Node, ts, UnionTypeNode } from 'ts-morph' export class UnionTypeHandler extends CollectionBaseHandler { @@ -6,7 +7,8 @@ export class UnionTypeHandler extends CollectionBaseHandler { return Node.isUnionTypeNode(node) } - handle(node: UnionTypeNode): ts.Expression { - return this.processTypeCollection(node.getTypeNodes(), 'Union') + handle(node: UnionTypeNode, context: TypeBoxContext): ts.Expression { + const types = node.getTypeNodes() + return this.processTypeCollection(types, 'Union', context) } } diff --git a/src/handlers/typebox/date-type-handler.ts b/src/handlers/typebox/date-type-handler.ts index 94f11ff..fa7f677 100644 --- a/src/handlers/typebox/date-type-handler.ts +++ b/src/handlers/typebox/date-type-handler.ts @@ -1,5 +1,5 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts, TypeReferenceNode } from 'ts-morph' export class DateTypeHandler extends BaseTypeHandler { @@ -10,6 +10,6 @@ export class DateTypeHandler extends BaseTypeHandler { } handle(): ts.Expression { - return makeTypeCall('Date') + return GenericTypeUtils.makeTypeCall('Date') } } diff --git a/src/handlers/typebox/function-type-handler.ts b/src/handlers/typebox/function-type-handler.ts index 99f4da1..42fb683 100644 --- a/src/handlers/typebox/function-type-handler.ts +++ b/src/handlers/typebox/function-type-handler.ts @@ -1,6 +1,7 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { FunctionTypeNode, Node, ts } from 'ts-morph' export class FunctionTypeHandler extends BaseTypeHandler { @@ -8,28 +9,28 @@ export class FunctionTypeHandler extends BaseTypeHandler { return Node.isFunctionTypeNode(node) } - handle(node: FunctionTypeNode): ts.Expression { + handle(node: FunctionTypeNode, context: TypeBoxContext): ts.Expression { const parameters = node.getParameters() const returnType = node.getReturnTypeNode() // Convert parameters to TypeBox types const parameterTypes = parameters.map((param) => { const paramTypeNode = param.getTypeNode() - const paramType = getTypeBoxType(paramTypeNode) + const paramType = getTypeBoxType(paramTypeNode, context) // Check if parameter is optional if (param.hasQuestionToken()) { - return makeTypeCall('Optional', [paramType]) + return GenericTypeUtils.makeTypeCall('Optional', [paramType]) } return paramType }) // Convert return type to TypeBox type - const returnTypeBox = getTypeBoxType(returnType) + const returnTypeBox = getTypeBoxType(returnType, context) // Create TypeBox Function call with parameters array and return type - return makeTypeCall('Function', [ + return GenericTypeUtils.makeTypeCall('Function', [ ts.factory.createArrayLiteralExpression(parameterTypes), returnTypeBox, ]) diff --git a/src/handlers/typebox/indexed-access-type-handler.ts b/src/handlers/typebox/indexed-access-type-handler.ts index e11669a..bc773dd 100644 --- a/src/handlers/typebox/indexed-access-type-handler.ts +++ b/src/handlers/typebox/indexed-access-type-handler.ts @@ -1,6 +1,7 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { IndexedAccessTypeNode, Node, ts, TypeNode } from 'ts-morph' export class IndexedAccessTypeHandler extends BaseTypeHandler { @@ -8,7 +9,7 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler { return node.isKind(ts.SyntaxKind.IndexedAccessType) } - handle(node: IndexedAccessTypeNode): ts.Expression { + handle(node: IndexedAccessTypeNode, context: TypeBoxContext): ts.Expression { const objectType = node.getObjectTypeNode() const indexType = node.getIndexTypeNode() @@ -17,18 +18,19 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler { objectType.isKind(ts.SyntaxKind.TypeQuery) && indexType.isKind(ts.SyntaxKind.NumberKeyword) ) { - return this.handleTypeofArrayAccess(objectType, node) + return this.handleTypeofArrayAccess(objectType, node, context) } - const typeboxObjectType = getTypeBoxType(objectType) - const typeboxIndexType = getTypeBoxType(indexType) + const typeboxObjectType = getTypeBoxType(objectType, context) + const typeboxIndexType = getTypeBoxType(indexType, context) - return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType]) + return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType]) } private handleTypeofArrayAccess( typeQuery: Node, indexedAccessType: IndexedAccessTypeNode, + context: TypeBoxContext, ): ts.Expression { const typeQueryNode = typeQuery.asKindOrThrow(ts.SyntaxKind.TypeQuery) const exprName = typeQueryNode.getExprName() @@ -54,10 +56,10 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler { } // Fallback to default Index behavior - const typeboxObjectType = getTypeBoxType(typeQuery) - const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode()) + const typeboxObjectType = getTypeBoxType(typeQuery, context) + const typeboxIndexType = getTypeBoxType(indexedAccessType.getIndexTypeNode(), context) - return makeTypeCall('Index', [typeboxObjectType, typeboxIndexType]) + return GenericTypeUtils.makeTypeCall('Index', [typeboxObjectType, typeboxIndexType]) } private extractTupleUnion(typeNode: TypeNode | undefined): ts.Expression | null { @@ -86,14 +88,18 @@ export class IndexedAccessTypeHandler extends BaseTypeHandler { if (literal.isKind(ts.SyntaxKind.StringLiteral)) { const stringLiteral = literal.asKindOrThrow(ts.SyntaxKind.StringLiteral) const value = stringLiteral.getLiteralValue() - literalTypes.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(value)])) + literalTypes.push( + GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(value)]), + ) } } } // Return union of literal types if we found any if (literalTypes.length > 0) { - return makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literalTypes)]) + return GenericTypeUtils.makeTypeCall('Union', [ + ts.factory.createArrayLiteralExpression(literalTypes), + ]) } } diff --git a/src/handlers/typebox/keyof-typeof-handler.ts b/src/handlers/typebox/keyof-typeof-handler.ts index e0be21f..cde3b03 100644 --- a/src/handlers/typebox/keyof-typeof-handler.ts +++ b/src/handlers/typebox/keyof-typeof-handler.ts @@ -1,5 +1,5 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts, TypeOperatorTypeNode, VariableDeclaration } from 'ts-morph' export class KeyOfTypeofHandler extends BaseTypeHandler { @@ -13,13 +13,13 @@ export class KeyOfTypeofHandler extends BaseTypeHandler { handle(node: TypeOperatorTypeNode): ts.Expression { const typeQuery = node.getTypeNode() - if (!Node.isTypeQuery(typeQuery)) return makeTypeCall('Any') + if (!Node.isTypeQuery(typeQuery)) return GenericTypeUtils.makeTypeCall('Any') const exprName = typeQuery.getExprName() - if (!Node.isIdentifier(exprName)) return makeTypeCall('Any') + if (!Node.isIdentifier(exprName)) return GenericTypeUtils.makeTypeCall('Any') const keys = this.getObjectKeys(exprName) - return keys.length > 0 ? this.createUnion(keys) : makeTypeCall('Any') + return keys.length > 0 ? this.createUnion(keys) : GenericTypeUtils.makeTypeCall('Any') } private getObjectKeys(node: Node): string[] { @@ -80,11 +80,11 @@ export class KeyOfTypeofHandler extends BaseTypeHandler { ? ts.factory.createNumericLiteral(num) : ts.factory.createStringLiteral(key) - return makeTypeCall('Literal', [literal]) + return GenericTypeUtils.makeTypeCall('Literal', [literal]) }) return literals.length === 1 ? literals[0]! - : makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)]) + : GenericTypeUtils.makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(literals)]) } } diff --git a/src/handlers/typebox/literal-type-handler.ts b/src/handlers/typebox/literal-type-handler.ts index a556e0a..776a9b3 100644 --- a/src/handlers/typebox/literal-type-handler.ts +++ b/src/handlers/typebox/literal-type-handler.ts @@ -1,5 +1,5 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { LiteralTypeNode, Node, SyntaxKind, ts } from 'ts-morph' export class LiteralTypeHandler extends BaseTypeHandler { @@ -12,19 +12,21 @@ export class LiteralTypeHandler extends BaseTypeHandler { switch (literal.getKind()) { case SyntaxKind.StringLiteral: - return makeTypeCall('Literal', [ + return GenericTypeUtils.makeTypeCall('Literal', [ ts.factory.createStringLiteral(literal.getText().slice(1, -1)), ]) case SyntaxKind.NumericLiteral: - return makeTypeCall('Literal', [ts.factory.createNumericLiteral(literal.getText())]) + return GenericTypeUtils.makeTypeCall('Literal', [ + ts.factory.createNumericLiteral(literal.getText()), + ]) case SyntaxKind.TrueKeyword: - return makeTypeCall('Literal', [ts.factory.createTrue()]) + return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createTrue()]) case SyntaxKind.FalseKeyword: - return makeTypeCall('Literal', [ts.factory.createFalse()]) + return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createFalse()]) case SyntaxKind.NullKeyword: - return makeTypeCall('Null') + return GenericTypeUtils.makeTypeCall('Null') default: - return makeTypeCall('Any') + return GenericTypeUtils.makeTypeCall('Any') } } } diff --git a/src/handlers/typebox/object/interface-type-handler.ts b/src/handlers/typebox/object/interface-type-handler.ts index 0e59caa..39670fa 100644 --- a/src/handlers/typebox/object/interface-type-handler.ts +++ b/src/handlers/typebox/object/interface-type-handler.ts @@ -1,5 +1,6 @@ import { ObjectLikeBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/object/object-like-base-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { HeritageClause, InterfaceDeclaration, Node, ts } from 'ts-morph' export class InterfaceTypeHandler extends ObjectLikeBaseHandler { @@ -7,9 +8,11 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler { return Node.isInterfaceDeclaration(node) } - handle(node: InterfaceDeclaration): ts.Expression { + handle(node: InterfaceDeclaration, context: TypeBoxContext): ts.Expression { const heritageClauses = node.getHeritageClauses() - const baseObjectType = this.createObjectType(this.processProperties(node.getProperties())) + const baseObjectType = this.createObjectType( + this.processProperties(node.getProperties(), context), + ) if (heritageClauses.length === 0) return baseObjectType @@ -20,7 +23,7 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler { const allTypes = [...extendedTypes, baseObjectType] const expression = ts.factory.createArrayLiteralExpression(allTypes, true) - return makeTypeCall('Composite', [expression]) + return GenericTypeUtils.makeTypeCall('Composite', [expression]) } private parseGenericTypeCall(typeText: string): ts.Expression | null { @@ -42,11 +45,11 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler { // Convert common TypeScript types to TypeBox calls switch (typeArg) { case 'number': - return makeTypeCall('Number') + return GenericTypeUtils.makeTypeCall('Number') case 'string': - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') case 'boolean': - return makeTypeCall('Boolean') + return GenericTypeUtils.makeTypeCall('Boolean') default: // For other types, assume it's a reference return ts.factory.createIdentifier(typeArg) diff --git a/src/handlers/typebox/object/object-like-base-handler.ts b/src/handlers/typebox/object/object-like-base-handler.ts index bba91b3..f46737c 100644 --- a/src/handlers/typebox/object/object-like-base-handler.ts +++ b/src/handlers/typebox/object/object-like-base-handler.ts @@ -1,11 +1,15 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { isValidIdentifier } from '@daxserver/validation-schema-codegen/utils/identifier-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { Node, PropertySignature, ts } from 'ts-morph' export abstract class ObjectLikeBaseHandler extends BaseTypeHandler { - protected processProperties(properties: PropertySignature[]): ts.PropertyAssignment[] { + protected processProperties( + properties: PropertySignature[], + context?: TypeBoxContext, + ): ts.PropertyAssignment[] { const propertyAssignments: ts.PropertyAssignment[] = [] for (const prop of properties) { @@ -13,7 +17,7 @@ export abstract class ObjectLikeBaseHandler extends BaseTypeHandler { if (!propTypeNode) continue const outputNameNode = this.extractPropertyNameInfo(prop) - const valueExpr = getTypeBoxType(propTypeNode) + const valueExpr = getTypeBoxType(propTypeNode, context) const isAlreadyOptional = ts.isCallExpression(valueExpr) && ts.isPropertyAccessExpression(valueExpr.expression) && @@ -21,7 +25,7 @@ export abstract class ObjectLikeBaseHandler extends BaseTypeHandler { const maybeOptional = prop.hasQuestionToken() && !isAlreadyOptional - ? makeTypeCall('Optional', [valueExpr]) + ? GenericTypeUtils.makeTypeCall('Optional', [valueExpr]) : valueExpr propertyAssignments.push(ts.factory.createPropertyAssignment(outputNameNode, maybeOptional)) @@ -33,7 +37,7 @@ export abstract class ObjectLikeBaseHandler extends BaseTypeHandler { protected createObjectType(properties: ts.PropertyAssignment[]): ts.Expression { const objectLiteral = ts.factory.createObjectLiteralExpression(properties, true) - return makeTypeCall('Object', [objectLiteral]) + return GenericTypeUtils.makeTypeCall('Object', [objectLiteral]) } private extractPropertyNameInfo(prop: PropertySignature): ts.PropertyName { diff --git a/src/handlers/typebox/object/object-type-handler.ts b/src/handlers/typebox/object/object-type-handler.ts index 0c3ea21..eab39e2 100644 --- a/src/handlers/typebox/object/object-type-handler.ts +++ b/src/handlers/typebox/object/object-type-handler.ts @@ -1,4 +1,5 @@ import { ObjectLikeBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/object/object-like-base-handler' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { Node, ts, TypeLiteralNode } from 'ts-morph' export class ObjectTypeHandler extends ObjectLikeBaseHandler { @@ -6,7 +7,8 @@ export class ObjectTypeHandler extends ObjectLikeBaseHandler { return Node.isTypeLiteral(node) } - handle(node: TypeLiteralNode): ts.Expression { - return this.createObjectType(this.processProperties(node.getProperties())) + handle(node: TypeLiteralNode, context: TypeBoxContext): ts.Expression { + const properties = node.getProperties() + return this.createObjectType(this.processProperties(properties, context)) } } diff --git a/src/handlers/typebox/reference/omit-type-handler.ts b/src/handlers/typebox/reference/omit-type-handler.ts index f56bc7d..e1010d1 100644 --- a/src/handlers/typebox/reference/omit-type-handler.ts +++ b/src/handlers/typebox/reference/omit-type-handler.ts @@ -1,26 +1,29 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { createTypeBoxKeys, extractStringKeys, } from '@daxserver/validation-schema-codegen/utils/key-extraction-utils' -import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { + getTypeBoxType, + type TypeBoxContext, +} from '@daxserver/validation-schema-codegen/utils/typebox-call' +import { ts, TypeReferenceNode } from 'ts-morph' export class OmitTypeHandler extends TypeReferenceBaseHandler { - protected supportedTypeNames = ['Omit'] - protected expectedArgumentCount = 2 + protected readonly supportedTypeNames = ['Omit'] + protected readonly expectedArgumentCount = 2 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [objectType, keysType] = this.extractTypeArguments(typeRef) - if (!keysType) return makeTypeCall('Any') + if (!keysType) return GenericTypeUtils.makeTypeCall('Any') - const typeboxObjectType = getTypeBoxType(objectType) + const typeboxObjectType = getTypeBoxType(objectType, context) const omitKeys = extractStringKeys(keysType) const typeboxKeys = createTypeBoxKeys(omitKeys) - return makeTypeCall('Omit', [typeboxObjectType, typeboxKeys]) + return GenericTypeUtils.makeTypeCall('Omit', [typeboxObjectType, typeboxKeys]) } } diff --git a/src/handlers/typebox/reference/partial-type-handler.ts b/src/handlers/typebox/reference/partial-type-handler.ts index ad38392..070e145 100644 --- a/src/handlers/typebox/reference/partial-type-handler.ts +++ b/src/handlers/typebox/reference/partial-type-handler.ts @@ -1,18 +1,21 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' -import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import { + getTypeBoxType, + type TypeBoxContext, +} from '@daxserver/validation-schema-codegen/utils/typebox-call' +import { ts, TypeReferenceNode } from 'ts-morph' export class PartialTypeHandler extends TypeReferenceBaseHandler { protected readonly supportedTypeNames = ['Partial'] protected readonly expectedArgumentCount = 1 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [innerType] = this.extractTypeArguments(typeRef) - const typeBoxType = getTypeBoxType(innerType) + const typeBoxType = getTypeBoxType(innerType, context) - return makeTypeCall('Partial', [typeBoxType]) + return GenericTypeUtils.makeTypeCall('Partial', [typeBoxType]) } } diff --git a/src/handlers/typebox/reference/pick-type-handler.ts b/src/handlers/typebox/reference/pick-type-handler.ts index b71f586..89e2c7f 100644 --- a/src/handlers/typebox/reference/pick-type-handler.ts +++ b/src/handlers/typebox/reference/pick-type-handler.ts @@ -1,26 +1,29 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { createTypeBoxKeys, extractStringKeys, } from '@daxserver/validation-schema-codegen/utils/key-extraction-utils' -import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { + getTypeBoxType, + type TypeBoxContext, +} from '@daxserver/validation-schema-codegen/utils/typebox-call' +import { ts, TypeReferenceNode } from 'ts-morph' export class PickTypeHandler extends TypeReferenceBaseHandler { - protected supportedTypeNames = ['Pick'] - protected expectedArgumentCount = 2 + protected readonly supportedTypeNames = ['Pick'] + protected readonly expectedArgumentCount = 2 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [objectType, keysType] = this.extractTypeArguments(typeRef) - if (!keysType) return makeTypeCall('Any') + if (!keysType) return GenericTypeUtils.makeTypeCall('Any') - const typeboxObjectType = getTypeBoxType(objectType) + const typeboxObjectType = getTypeBoxType(objectType, context) const pickKeys = extractStringKeys(keysType) const typeboxKeys = createTypeBoxKeys(pickKeys) - return makeTypeCall('Pick', [typeboxObjectType, typeboxKeys]) + return GenericTypeUtils.makeTypeCall('Pick', [typeboxObjectType, typeboxKeys]) } } diff --git a/src/handlers/typebox/reference/readonly-type-handler.ts b/src/handlers/typebox/reference/readonly-type-handler.ts index e5b237b..4cb0356 100644 --- a/src/handlers/typebox/reference/readonly-type-handler.ts +++ b/src/handlers/typebox/reference/readonly-type-handler.ts @@ -1,16 +1,19 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' -import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import { + getTypeBoxType, + type TypeBoxContext, +} from '@daxserver/validation-schema-codegen/utils/typebox-call' +import { ts, TypeReferenceNode } from 'ts-morph' export class ReadonlyTypeHandler extends TypeReferenceBaseHandler { protected readonly supportedTypeNames = ['Readonly'] protected readonly expectedArgumentCount = 1 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [innerType] = this.extractTypeArguments(typeRef) - return makeTypeCall('Readonly', [getTypeBoxType(innerType)]) + return GenericTypeUtils.makeTypeCall('Readonly', [getTypeBoxType(innerType, context)]) } } diff --git a/src/handlers/typebox/reference/record-type-handler.ts b/src/handlers/typebox/reference/record-type-handler.ts index b814b40..a429a55 100644 --- a/src/handlers/typebox/reference/record-type-handler.ts +++ b/src/handlers/typebox/reference/record-type-handler.ts @@ -1,19 +1,57 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { Node, ts, TypeReferenceNode } from 'ts-morph' export class RecordTypeHandler extends TypeReferenceBaseHandler { protected readonly supportedTypeNames = ['Record'] protected readonly expectedArgumentCount = 2 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [keyType, valueType] = this.extractTypeArguments(typeRef) - const typeBoxKeyType = getTypeBoxType(keyType) - const typeBoxValueType = getTypeBoxType(valueType) + // Check if keyType is a type reference that might be chunked + if (Node.isTypeReference(keyType)) { + const typeName = keyType.getTypeName() + if (Node.isIdentifier(typeName)) { + const originalTypeName = typeName.getText() + const qualifiedName = resolverStore.resolveQualifiedName(originalTypeName) - return makeTypeCall('Record', [typeBoxKeyType, typeBoxValueType]) + // Check if the node exists in the graph before accessing it + if (qualifiedName && context.nodeGraph.hasNode(qualifiedName)) { + const referencedNode = context.nodeGraph.getNode(qualifiedName) + + // Check if this is a chunked union type + if ( + referencedNode && + referencedNode.chunkReferences && + referencedNode.chunkReferences.length > 0 + ) { + // Generate Type.Intersect of Type.Record for each chunk + const chunkRecords = referencedNode.chunkReferences.map( + (chunkQualifiedName: string) => { + const chunkNode = context.nodeGraph.getNode(chunkQualifiedName) + const chunkOriginalName = chunkNode?.originalName || chunkQualifiedName + const chunkIdentifier = ts.factory.createIdentifier(chunkOriginalName) + const typeBoxValueType = getTypeBoxType(valueType, context) + return GenericTypeUtils.makeTypeCall('Record', [chunkIdentifier, typeBoxValueType]) + }, + ) + + return GenericTypeUtils.makeTypeCall('Intersect', [ + ts.factory.createArrayLiteralExpression(chunkRecords), + ]) + } + } + } + } + + const typeBoxKeyType = getTypeBoxType(keyType, context) + const typeBoxValueType = getTypeBoxType(valueType, context) + + return GenericTypeUtils.makeTypeCall('Record', [typeBoxKeyType, typeBoxValueType]) } } diff --git a/src/handlers/typebox/reference/required-type-handler.ts b/src/handlers/typebox/reference/required-type-handler.ts index 17e2529..f7d3409 100644 --- a/src/handlers/typebox/reference/required-type-handler.ts +++ b/src/handlers/typebox/reference/required-type-handler.ts @@ -1,16 +1,17 @@ import { TypeReferenceBaseHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/reference/type-reference-base-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' -import { Node, ts } from 'ts-morph' +import { ts, TypeReferenceNode } from 'ts-morph' export class RequiredTypeHandler extends TypeReferenceBaseHandler { protected readonly supportedTypeNames = ['Required'] protected readonly expectedArgumentCount = 1 - handle(node: Node): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const typeRef = this.validateTypeReference(node) const [innerType] = this.extractTypeArguments(typeRef) - return makeTypeCall('Required', [getTypeBoxType(innerType)]) + return GenericTypeUtils.makeTypeCall('Required', [getTypeBoxType(innerType, context)]) } } diff --git a/src/handlers/typebox/simple-type-handler.ts b/src/handlers/typebox/simple-type-handler.ts index 403e896..09d6407 100644 --- a/src/handlers/typebox/simple-type-handler.ts +++ b/src/handlers/typebox/simple-type-handler.ts @@ -1,6 +1,6 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { isAnySyntaxKind } from '@daxserver/validation-schema-codegen/utils/node-type-utils' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { Node, SyntaxKind, ts } from 'ts-morph' export const TypeBoxType = 'Type' @@ -34,6 +34,6 @@ export class SimpleTypeHandler extends BaseTypeHandler { } handle(node: Node): ts.Expression { - return makeTypeCall(kindToTypeBox[node.getKind() as SimpleKind]) + return GenericTypeUtils.makeTypeCall(kindToTypeBox[node.getKind() as SimpleKind]) } } diff --git a/src/handlers/typebox/template-literal-type-handler.ts b/src/handlers/typebox/template-literal-type-handler.ts index e3857e1..e22437f 100644 --- a/src/handlers/typebox/template-literal-type-handler.ts +++ b/src/handlers/typebox/template-literal-type-handler.ts @@ -1,6 +1,6 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' import { TemplateLiteralTypeProcessor } from '@daxserver/validation-schema-codegen/handlers/typebox/template-literal-type-processor' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, TemplateLiteralTypeNode, ts } from 'ts-morph' export class TemplateLiteralTypeHandler extends BaseTypeHandler { @@ -16,7 +16,9 @@ export class TemplateLiteralTypeHandler extends BaseTypeHandler { const headCompilerNode = head.compilerNode as ts.TemplateHead const headText = headCompilerNode.text if (headText) { - parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)])) + parts.push( + GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(headText)]), + ) } // Process template spans (substitutions + following literal parts) @@ -32,16 +34,20 @@ export class TemplateLiteralTypeHandler extends BaseTypeHandler { // Add the literal part after the substitution const literalText = compilerNode.literal.text if (literalText) { - parts.push(makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)])) + parts.push( + GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(literalText)]), + ) } } // If no parts were found, fallback to a simple string if (parts.length === 0) { - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') } // Return TemplateLiteral with array of parts - return makeTypeCall('TemplateLiteral', [ts.factory.createArrayLiteralExpression(parts)]) + return GenericTypeUtils.makeTypeCall('TemplateLiteral', [ + ts.factory.createArrayLiteralExpression(parts), + ]) } } diff --git a/src/handlers/typebox/template-literal-type-processor.ts b/src/handlers/typebox/template-literal-type-processor.ts index fbf4cca..8577138 100644 --- a/src/handlers/typebox/template-literal-type-processor.ts +++ b/src/handlers/typebox/template-literal-type-processor.ts @@ -1,4 +1,4 @@ -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { ts } from 'ts-morph' /** @@ -11,15 +11,15 @@ export class TemplateLiteralTypeProcessor { static processType(node: ts.TypeNode): ts.Expression { switch (node.kind) { case ts.SyntaxKind.StringKeyword: - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') case ts.SyntaxKind.NumberKeyword: - return makeTypeCall('Number') + return GenericTypeUtils.makeTypeCall('Number') case ts.SyntaxKind.LiteralType: return this.processLiteralType(node as ts.LiteralTypeNode) case ts.SyntaxKind.UnionType: return this.processUnionType(node as ts.UnionTypeNode) default: - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') } } @@ -28,15 +28,19 @@ export class TemplateLiteralTypeProcessor { */ private static processLiteralType(literalType: ts.LiteralTypeNode): ts.Expression { if (ts.isStringLiteral(literalType.literal)) { - return makeTypeCall('Literal', [ts.factory.createStringLiteral(literalType.literal.text)]) + return GenericTypeUtils.makeTypeCall('Literal', [ + ts.factory.createStringLiteral(literalType.literal.text), + ]) } if (ts.isNumericLiteral(literalType.literal)) { - return makeTypeCall('Literal', [ts.factory.createNumericLiteral(literalType.literal.text)]) + return GenericTypeUtils.makeTypeCall('Literal', [ + ts.factory.createNumericLiteral(literalType.literal.text), + ]) } // Fallback for other literals (boolean, etc.) - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') } /** @@ -47,12 +51,16 @@ export class TemplateLiteralTypeProcessor { if (t.kind === ts.SyntaxKind.LiteralType) { const literalType = t as ts.LiteralTypeNode if (ts.isStringLiteral(literalType.literal)) { - return makeTypeCall('Literal', [ts.factory.createStringLiteral(literalType.literal.text)]) + return GenericTypeUtils.makeTypeCall('Literal', [ + ts.factory.createStringLiteral(literalType.literal.text), + ]) } } - return makeTypeCall('String') // fallback + return GenericTypeUtils.makeTypeCall('String') // fallback }) - return makeTypeCall('Union', [ts.factory.createArrayLiteralExpression(unionParts)]) + return GenericTypeUtils.makeTypeCall('Union', [ + ts.factory.createArrayLiteralExpression(unionParts), + ]) } } diff --git a/src/handlers/typebox/type-query-handler.ts b/src/handlers/typebox/type-query-handler.ts index ed7e6f7..30907d7 100644 --- a/src/handlers/typebox/type-query-handler.ts +++ b/src/handlers/typebox/type-query-handler.ts @@ -1,5 +1,5 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts, TypeQueryNode } from 'ts-morph' export class TypeQueryHandler extends BaseTypeHandler { @@ -24,6 +24,6 @@ export class TypeQueryHandler extends BaseTypeHandler { return ts.factory.createIdentifier(fullName.replace('.', '_')) } - return makeTypeCall('Any') + return GenericTypeUtils.makeTypeCall('Any') } } diff --git a/src/handlers/typebox/type-reference-handler.ts b/src/handlers/typebox/type-reference-handler.ts index 1ec1db8..af8375c 100644 --- a/src/handlers/typebox/type-reference-handler.ts +++ b/src/handlers/typebox/type-reference-handler.ts @@ -1,7 +1,8 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { Node, ts, TypeReferenceNode } from 'ts-morph' export class TypeReferenceHandler extends BaseTypeHandler { @@ -9,7 +10,7 @@ export class TypeReferenceHandler extends BaseTypeHandler { return Node.isTypeReference(node) } - handle(node: TypeReferenceNode): ts.Expression { + handle(node: TypeReferenceNode, context: TypeBoxContext): ts.Expression { const referencedType = node.getTypeName() const typeArguments = node.getTypeArguments() @@ -21,7 +22,7 @@ export class TypeReferenceHandler extends BaseTypeHandler { // If there are type arguments, create a function call if (typeArguments.length > 0) { - const typeBoxArgs = typeArguments.map((arg) => getTypeBoxType(arg)) + const typeBoxArgs = typeArguments.map((arg) => getTypeBoxType(arg, context)) return ts.factory.createCallExpression( ts.factory.createIdentifier(typeName), @@ -34,6 +35,6 @@ export class TypeReferenceHandler extends BaseTypeHandler { return ts.factory.createIdentifier(typeName) } - return makeTypeCall('Any') + return GenericTypeUtils.makeTypeCall('Any') } } diff --git a/src/handlers/typebox/type/type-operator-base-handler.ts b/src/handlers/typebox/type/type-operator-base-handler.ts index 9badf93..88922f4 100644 --- a/src/handlers/typebox/type/type-operator-base-handler.ts +++ b/src/handlers/typebox/type/type-operator-base-handler.ts @@ -1,7 +1,8 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { isTypeOperatorWithOperator } from '@daxserver/validation-schema-codegen/utils/node-type-utils' +import type { TypeBoxContext } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { Node, SyntaxKind, ts, TypeOperatorTypeNode } from 'ts-morph' /** @@ -16,10 +17,10 @@ export abstract class TypeOperatorBaseHandler extends BaseTypeHandler { return isTypeOperatorWithOperator(node, this.operatorKind) } - handle(node: TypeOperatorTypeNode): ts.Expression { + handle(node: TypeOperatorTypeNode, context: TypeBoxContext): ts.Expression { const innerType = node.getTypeNode() - const typeboxType = getTypeBoxType(innerType) + const typeboxType = getTypeBoxType(innerType, context) - return makeTypeCall(this.typeBoxMethod, [typeboxType]) + return GenericTypeUtils.makeTypeCall(this.typeBoxMethod, [typeboxType]) } } diff --git a/src/handlers/typebox/typeof-type-handler.ts b/src/handlers/typebox/typeof-type-handler.ts index 16a32eb..53a6601 100644 --- a/src/handlers/typebox/typeof-type-handler.ts +++ b/src/handlers/typebox/typeof-type-handler.ts @@ -1,5 +1,5 @@ import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-handler' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts } from 'ts-morph' export class TypeofTypeHandler extends BaseTypeHandler { @@ -10,6 +10,6 @@ export class TypeofTypeHandler extends BaseTypeHandler { handle(): ts.Expression { // TypeQuery represents 'typeof' expressions in TypeScript // For TypeBox, we'll return a String type as typeof returns string literals - return makeTypeCall('String') + return GenericTypeUtils.makeTypeCall('String') } } diff --git a/src/index.ts b/src/index.ts index 7881e14..13916a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,10 +45,15 @@ const createOutputFile = (hasGenericInterfaces: boolean) => { return newSourceFile } -const printSortedNodes = (sortedTraversedNodes: TraversedNode[], newSourceFile: SourceFile) => { +const printSortedNodes = ( + sortedTraversedNodes: TraversedNode[], + newSourceFile: SourceFile, + dependencyTraversal: DependencyTraversal, +) => { const printer = new TypeBoxPrinter({ newSourceFile, printer: ts.createPrinter(), + nodeGraph: dependencyTraversal.getNodeGraph(), }) // Process nodes in topological order @@ -94,7 +99,7 @@ export const generateCode = (options: InputOptions): string => { const newSourceFile = createOutputFile(hasGenericInterfaces) // Print sorted nodes to output - const result = printSortedNodes(traversedNodes, newSourceFile) + const result = printSortedNodes(traversedNodes, newSourceFile, dependencyTraversal) return result } diff --git a/src/parsers/base-parser.ts b/src/parsers/base-parser.ts index c477365..b72f977 100644 --- a/src/parsers/base-parser.ts +++ b/src/parsers/base-parser.ts @@ -1,20 +1,24 @@ +import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' import { Node, SourceFile, ts } from 'ts-morph' export interface BaseParserOptions { newSourceFile: SourceFile printer: ts.Printer processedTypes: Set + nodeGraph: NodeGraph } export abstract class BaseParser { protected newSourceFile: SourceFile protected printer: ts.Printer protected processedTypes: Set + protected nodeGraph: NodeGraph constructor(options: BaseParserOptions) { this.newSourceFile = options.newSourceFile this.printer = options.printer this.processedTypes = options.processedTypes + this.nodeGraph = options.nodeGraph } abstract parse(node: Node): void diff --git a/src/parsers/parse-chunks.ts b/src/parsers/parse-chunks.ts new file mode 100644 index 0000000..4729152 --- /dev/null +++ b/src/parsers/parse-chunks.ts @@ -0,0 +1,97 @@ +import { + BaseParser, + type BaseParserOptions, +} from '@daxserver/validation-schema-codegen/parsers/base-parser' +import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' +import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' +import { SourceFile, ts } from 'ts-morph' + +export interface ChunkParserOptions extends BaseParserOptions { + newSourceFile: SourceFile + printer: ts.Printer + processedTypes: Set +} + +export class ChunkParser extends BaseParser { + constructor(options: ChunkParserOptions) { + super(options) + } + + parse(): void { + // This method is required by BaseParser but not used for chunk parsing + throw new Error('Use parseChunk(traversedNode, aliasName) instead') + } + + parseChunk(traversedNode: TraversedNode, aliasName?: string): void { + const typeName = aliasName || traversedNode.originalName + + if (this.processedTypes.has(typeName)) return + this.processedTypes.add(typeName) + + this.parseChunkNode(traversedNode, typeName) + } + + private parseChunkNode(traversedNode: TraversedNode, typeName: string): void { + // If this is an individual chunk node (type === 'chunk'), process it as a regular union + if (traversedNode.type === 'chunk') { + const typeboxType = this.printer.printNode( + ts.EmitHint.Expression, + getTypeBoxType(traversedNode.node, { nodeGraph: this.nodeGraph }), + this.newSourceFile.compilerNode, + ) + + // Chunk nodes should only be const variables, not exported and without static type aliases + GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, typeName, typeboxType, false) + + return + } + + // If this is a main type with chunk references, create union of chunk identifiers + if (!traversedNode.chunkReferences || traversedNode.chunkReferences.length === 0) { + // Fallback to empty union if no chunk references + const unionCall = GenericTypeUtils.makeTypeCall('Union', []) + const typeboxType = this.printer.printNode( + ts.EmitHint.Expression, + unionCall, + this.newSourceFile.compilerNode, + ) + + GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, typeName, typeboxType) + GenericTypeUtils.addStaticTypeAlias( + this.newSourceFile, + typeName, + this.newSourceFile.compilerNode, + this.printer, + ) + + return + } + + // Convert chunk references to identifier references + const chunkIdentifiers = traversedNode.chunkReferences.map((qualifiedName) => { + const chunkNode = this.nodeGraph.getNode(qualifiedName) + if (!chunkNode) { + throw new Error(`Chunk node not found: ${qualifiedName}`) + } + // Use the original name of the chunk as an identifier + return ts.factory.createIdentifier(chunkNode.originalName) + }) + const arrayLiteral = ts.factory.createArrayLiteralExpression(chunkIdentifiers) + const unionCall = GenericTypeUtils.makeTypeCall('Union', [arrayLiteral]) + + const typeboxType = this.printer.printNode( + ts.EmitHint.Expression, + unionCall, + this.newSourceFile.compilerNode, + ) + + GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, typeName, typeboxType) + GenericTypeUtils.addStaticTypeAlias( + this.newSourceFile, + typeName, + this.newSourceFile.compilerNode, + this.printer, + ) + } +} diff --git a/src/parsers/parse-enums.ts b/src/parsers/parse-enums.ts index 72313e4..ae8c846 100644 --- a/src/parsers/parse-enums.ts +++ b/src/parsers/parse-enums.ts @@ -1,5 +1,5 @@ import { BaseParser } from '@daxserver/validation-schema-codegen/parsers/base-parser' -import { addStaticTypeAlias } from '@daxserver/validation-schema-codegen/utils/add-static-type-alias' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { EnumDeclaration, VariableDeclarationKind } from 'ts-morph' export class EnumParser extends BaseParser { @@ -30,7 +30,7 @@ export class EnumParser extends BaseParser { ], }) - addStaticTypeAlias( + GenericTypeUtils.addStaticTypeAlias( this.newSourceFile, schemaName, this.newSourceFile.compilerNode, diff --git a/src/parsers/parse-function-declarations.ts b/src/parsers/parse-function-declarations.ts index 6d9654d..8ea208d 100644 --- a/src/parsers/parse-function-declarations.ts +++ b/src/parsers/parse-function-declarations.ts @@ -1,7 +1,6 @@ import { BaseParser } from '@daxserver/validation-schema-codegen/parsers/base-parser' -import { addStaticTypeAlias } from '@daxserver/validation-schema-codegen/utils/add-static-type-alias' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' import { FunctionDeclaration, ts, VariableDeclarationKind } from 'ts-morph' export class FunctionDeclarationParser extends BaseParser { @@ -21,7 +20,9 @@ export class FunctionDeclarationParser extends BaseParser { // Convert parameters to TypeBox types const parameterTypes = parameters.map((param) => { const paramTypeNode = param.getTypeNode() - const paramType = paramTypeNode ? getTypeBoxType(paramTypeNode) : makeTypeCall('Any') + const paramType = paramTypeNode + ? getTypeBoxType(paramTypeNode, { nodeGraph: this.nodeGraph }) + : GenericTypeUtils.makeTypeCall('Any') // Check if parameter is optional or required if (!param.hasQuestionToken()) { @@ -40,7 +41,9 @@ export class FunctionDeclarationParser extends BaseParser { }) // Convert return type to TypeBox type - const returnTypeBox = returnType ? getTypeBoxType(returnType) : makeTypeCall('Any') + const returnTypeBox = returnType + ? getTypeBoxType(returnType, { nodeGraph: this.nodeGraph }) + : GenericTypeUtils.makeTypeCall('Any') // Create TypeBox Function call with parameters array and return type const typeboxExpression = ts.factory.createCallExpression( @@ -69,7 +72,7 @@ export class FunctionDeclarationParser extends BaseParser { ], }) - addStaticTypeAlias( + GenericTypeUtils.addStaticTypeAlias( this.newSourceFile, functionName, this.newSourceFile.compilerNode, diff --git a/src/parsers/parse-interfaces.ts b/src/parsers/parse-interfaces.ts index fd2e4dd..bff16d0 100644 --- a/src/parsers/parse-interfaces.ts +++ b/src/parsers/parse-interfaces.ts @@ -1,5 +1,4 @@ import { BaseParser } from '@daxserver/validation-schema-codegen/parsers/base-parser' -import { addStaticTypeAlias } from '@daxserver/validation-schema-codegen/utils/add-static-type-alias' import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' import { InterfaceDeclaration, ts } from 'ts-morph' @@ -23,7 +22,7 @@ export class InterfaceParser extends BaseParser { private parseRegularInterface(interfaceDecl: InterfaceDeclaration, interfaceName: string): void { // Generate TypeBox type definition - const typeboxTypeNode = getTypeBoxType(interfaceDecl) + const typeboxTypeNode = getTypeBoxType(interfaceDecl, { nodeGraph: this.nodeGraph }) const typeboxType = this.printer.printNode( ts.EmitHint.Expression, typeboxTypeNode, @@ -31,8 +30,7 @@ export class InterfaceParser extends BaseParser { ) GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, interfaceName, typeboxType) - - addStaticTypeAlias( + GenericTypeUtils.addStaticTypeAlias( this.newSourceFile, interfaceName, this.newSourceFile.compilerNode, @@ -44,7 +42,7 @@ export class InterfaceParser extends BaseParser { const typeParameters = interfaceDecl.getTypeParameters() // Generate TypeBox function definition using the same flow as type aliases - const typeboxTypeNode = getTypeBoxType(interfaceDecl) + const typeboxTypeNode = getTypeBoxType(interfaceDecl, { nodeGraph: this.nodeGraph }) // Create the function expression using shared utilities (mirrors type-alias flow) const functionExpression = GenericTypeUtils.createGenericArrowFunction( diff --git a/src/parsers/parse-type-aliases.ts b/src/parsers/parse-type-aliases.ts index 6db4657..f298600 100644 --- a/src/parsers/parse-type-aliases.ts +++ b/src/parsers/parse-type-aliases.ts @@ -1,8 +1,7 @@ import { BaseParser } from '@daxserver/validation-schema-codegen/parsers/base-parser' -import { addStaticTypeAlias } from '@daxserver/validation-schema-codegen/utils/add-static-type-alias' import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { getTypeBoxType } from '@daxserver/validation-schema-codegen/utils/typebox-call' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' + import { ts, TypeAliasDeclaration } from 'ts-morph' export class TypeAliasParser extends BaseParser { @@ -24,7 +23,9 @@ export class TypeAliasParser extends BaseParser { private parseRegularTypeAlias(typeAlias: TypeAliasDeclaration, typeName: string): void { const typeNode = typeAlias.getTypeNode() - const typeboxTypeNode = typeNode ? getTypeBoxType(typeNode) : makeTypeCall('Any') + const typeboxTypeNode = typeNode + ? getTypeBoxType(typeNode, { nodeGraph: this.nodeGraph }) + : GenericTypeUtils.makeTypeCall('Any') const typeboxType = this.printer.printNode( ts.EmitHint.Expression, typeboxTypeNode, @@ -32,8 +33,12 @@ export class TypeAliasParser extends BaseParser { ) GenericTypeUtils.addTypeBoxVariableStatement(this.newSourceFile, typeName, typeboxType) - - addStaticTypeAlias(this.newSourceFile, typeName, this.newSourceFile.compilerNode, this.printer) + GenericTypeUtils.addStaticTypeAlias( + this.newSourceFile, + typeName, + this.newSourceFile.compilerNode, + this.printer, + ) } private parseGenericTypeAlias(typeAlias: TypeAliasDeclaration, typeName: string): void { @@ -41,7 +46,9 @@ export class TypeAliasParser extends BaseParser { // Generate TypeBox function definition const typeNode = typeAlias.getTypeNode() - const typeboxTypeNode = typeNode ? getTypeBoxType(typeNode) : makeTypeCall('Any') + const typeboxTypeNode = typeNode + ? getTypeBoxType(typeNode, { nodeGraph: this.nodeGraph }) + : GenericTypeUtils.makeTypeCall('Any') // Create the function expression using shared utilities const functionExpression = GenericTypeUtils.createGenericArrowFunction( diff --git a/src/printer/typebox-printer.ts b/src/printer/typebox-printer.ts index 23f530f..bfc99f0 100644 --- a/src/printer/typebox-printer.ts +++ b/src/printer/typebox-printer.ts @@ -1,3 +1,4 @@ +import { ChunkParser } from '@daxserver/validation-schema-codegen/parsers/parse-chunks' import { EnumParser } from '@daxserver/validation-schema-codegen/parsers/parse-enums' import { FunctionDeclarationParser } from '@daxserver/validation-schema-codegen/parsers/parse-function-declarations' import { InterfaceParser } from '@daxserver/validation-schema-codegen/parsers/parse-interfaces' @@ -5,39 +6,63 @@ import { TypeAliasParser } from '@daxserver/validation-schema-codegen/parsers/pa import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types' import { Node, SourceFile, ts } from 'ts-morph' +import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' + export interface PrinterOptions { newSourceFile: SourceFile printer: ts.Printer + nodeGraph: NodeGraph } export class TypeBoxPrinter { private readonly newSourceFile: SourceFile private readonly printer: ts.Printer + private readonly nodeGraph: NodeGraph private readonly processedTypes = new Set() private readonly typeAliasParser: TypeAliasParser private readonly interfaceParser: InterfaceParser private readonly enumParser: EnumParser private readonly functionParser: FunctionDeclarationParser + private readonly chunkParser: ChunkParser constructor(options: PrinterOptions) { this.newSourceFile = options.newSourceFile this.printer = options.printer + this.nodeGraph = options.nodeGraph // Initialize parsers with the same configuration const parserOptions = { newSourceFile: this.newSourceFile, printer: this.printer, processedTypes: this.processedTypes, + nodeGraph: this.nodeGraph, } this.typeAliasParser = new TypeAliasParser(parserOptions) this.interfaceParser = new InterfaceParser(parserOptions) this.enumParser = new EnumParser(parserOptions) this.functionParser = new FunctionDeclarationParser(parserOptions) + this.chunkParser = new ChunkParser(parserOptions) } printNode(traversedNode: TraversedNode): void { - const { node, aliasName } = traversedNode + const { node, aliasName, type } = traversedNode + + // Handle chunk nodes first + if (type === 'chunk') { + this.chunkParser.parseChunk(traversedNode, aliasName) + return + } + + // Handle type aliases with chunk references using chunk parser + if ( + type === 'typeAlias' && + traversedNode.chunkReferences && + traversedNode.chunkReferences.length > 0 + ) { + this.chunkParser.parseChunk(traversedNode, aliasName) + return + } switch (true) { case Node.isTypeAliasDeclaration(node): diff --git a/src/traverse/dependency-traversal.ts b/src/traverse/dependency-traversal.ts index 9d7cf37..806d797 100644 --- a/src/traverse/dependency-traversal.ts +++ b/src/traverse/dependency-traversal.ts @@ -68,4 +68,8 @@ export class DependencyTraversal { async visualizeGraph(options: VisualizationOptions = {}): Promise { return GraphVisualizer.generateVisualization(this.nodeGraph, options) } + + getNodeGraph(): NodeGraph { + return this.nodeGraph + } } diff --git a/src/traverse/local-type-collector.ts b/src/traverse/local-type-collector.ts index 701bb30..b880f53 100644 --- a/src/traverse/local-type-collector.ts +++ b/src/traverse/local-type-collector.ts @@ -1,6 +1,82 @@ import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' +import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types' import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' -import { SourceFile } from 'ts-morph' +import { Node, SourceFile } from 'ts-morph' + +const CHUNK_SIZE = 20 + +const shouldChunkUnion = (typeNode: Node): boolean => { + if (!Node.isUnionTypeNode(typeNode)) { + return false + } + return typeNode.getTypeNodes().length >= CHUNK_SIZE +} + +const createChunkNodes = ( + node: Node, + parentTypeName: string, + nodeGraph: NodeGraph, + maincodeNodeIds: Set, + requiredNodeIds: Set, + sourceFile: SourceFile, +): string[] => { + if (!Node.isUnionTypeNode(node)) { + return [] + } + + const typeNodes = node.getTypeNodes() + const chunks: Node[][] = [] + + // Create chunks of 20 items each + for (let i = 0; i < typeNodes.length; i += CHUNK_SIZE) { + chunks.push(typeNodes.slice(i, i + CHUNK_SIZE)) + } + + const chunkReferences: string[] = [] + + // Create chunk nodes + for (let i = 0; i < chunks.length; i++) { + const chunkName = `${parentTypeName}_Chunk${i + 1}` + const chunkQualifiedName = resolverStore.generateQualifiedName(chunkName, sourceFile) + + chunkReferences.push(chunkQualifiedName) + maincodeNodeIds.add(chunkQualifiedName) + requiredNodeIds.add(chunkQualifiedName) + + // Create a new union node with only the chunk's type nodes + const chunkTypeNodes = chunks[i]! + + // Create a synthetic union node for this chunk + const project = node.getProject() + const tempSourceFile = project.createSourceFile( + `__temp_chunk_${i}.ts`, + `type TempChunk = ${chunkTypeNodes.map((node) => node.getText()).join(' | ')}`, + ) + const tempTypeAlias = tempSourceFile.getTypeAliases()[0]! + const chunkTypeNode = tempTypeAlias.getTypeNode()! + + const chunkTraversedNode: TraversedNode = { + node: chunkTypeNode, // Use the chunk-specific union node + type: 'chunk', + originalName: chunkName, + qualifiedName: chunkQualifiedName, + isImported: false, + isMainCode: true, + isChunk: true, + chunkReferences: [], // Chunk nodes don't need references to other chunks + } + + nodeGraph.addTypeNode(chunkQualifiedName, chunkTraversedNode) + + // Add to ResolverStore + resolverStore.addTypeMapping({ + originalName: chunkName, + sourceFile, + }) + } + + return chunkReferences +} export const addLocalTypes = ( sourceFile: SourceFile, @@ -42,14 +118,40 @@ export const addLocalTypes = ( const qualifiedName = resolverStore.generateQualifiedName(typeName, typeAlias.getSourceFile()) maincodeNodeIds.add(qualifiedName) requiredNodeIds.add(qualifiedName) - nodeGraph.addTypeNode(qualifiedName, { - node: typeAlias, - type: 'typeAlias', - originalName: typeName, - qualifiedName, - isImported: false, - isMainCode: true, - }) + + // Check if this type alias contains a large union that needs chunking + const typeNode = typeAlias.getTypeNode() + if (typeNode && shouldChunkUnion(typeNode)) { + // Create chunk nodes for large union + const chunkReferences = createChunkNodes( + typeNode, + typeName, + nodeGraph, + maincodeNodeIds, + requiredNodeIds, + sourceFile, + ) + + // Add the main type as a regular type alias that will be handled by chunk parser + nodeGraph.addTypeNode(qualifiedName, { + node: typeAlias, + type: 'typeAlias', + originalName: typeName, + qualifiedName, + isImported: false, + isMainCode: true, + chunkReferences: chunkReferences, + }) + } else { + nodeGraph.addTypeNode(qualifiedName, { + node: typeAlias, + type: 'typeAlias', + originalName: typeName, + qualifiedName, + isImported: false, + isMainCode: true, + }) + } // Add to ResolverStore during traversal resolverStore.addTypeMapping({ diff --git a/src/traverse/types.ts b/src/traverse/types.ts index 596e568..f6db874 100644 --- a/src/traverse/types.ts +++ b/src/traverse/types.ts @@ -2,10 +2,13 @@ import type { Node } from 'ts-morph' export interface TraversedNode { node: Node - type: 'interface' | 'typeAlias' | 'enum' | 'function' + type: 'interface' | 'typeAlias' | 'enum' | 'function' | 'chunk' originalName: string qualifiedName: string isImported: boolean isMainCode: boolean aliasName?: string // The alias name used in import statements (e.g., 'UserType' for 'import { User as UserType }') + // Chunk-specific properties + isChunk?: boolean + chunkReferences?: string[] } diff --git a/src/utils/add-static-type-alias.ts b/src/utils/add-static-type-alias.ts deleted file mode 100644 index 27432ac..0000000 --- a/src/utils/add-static-type-alias.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { SourceFile, ts } from 'ts-morph' - -export const addStaticTypeAlias = ( - newSourceFile: SourceFile, - name: string, - compilerNode: ts.SourceFile, - printer: ts.Printer, -) => { - const staticTypeNode = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Static'), [ - ts.factory.createTypeQueryNode(ts.factory.createIdentifier(name)), - ]) - - const staticType = printer.printNode(ts.EmitHint.Unspecified, staticTypeNode, compilerNode) - - newSourceFile.addTypeAlias({ - isExported: true, - name, - type: staticType, - }) -} diff --git a/src/utils/generic-type-utils.ts b/src/utils/generic-type-utils.ts index 0547981..61626d8 100644 --- a/src/utils/generic-type-utils.ts +++ b/src/utils/generic-type-utils.ts @@ -11,9 +11,10 @@ export class GenericTypeUtils { newSourceFile: SourceFile, name: string, initializer: string, + isExported: boolean = true, ): void { newSourceFile.addVariableStatement({ - isExported: true, + isExported, declarationKind: VariableDeclarationKind.Const, declarations: [ { @@ -146,4 +147,35 @@ export class GenericTypeUtils { type: staticType, }) } + + static addStaticTypeAlias = ( + newSourceFile: SourceFile, + name: string, + compilerNode: ts.SourceFile, + printer: ts.Printer, + ) => { + const staticTypeNode = ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('Static'), + [ts.factory.createTypeQueryNode(ts.factory.createIdentifier(name))], + ) + + const staticType = printer.printNode(ts.EmitHint.Unspecified, staticTypeNode, compilerNode) + + newSourceFile.addTypeAlias({ + isExported: true, + name, + type: staticType, + }) + } + + static makeTypeCall(method: string, args: ts.Expression[] = []) { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('Type'), + ts.factory.createIdentifier(method), + ), + undefined, + args, + ) + } } diff --git a/src/utils/key-extraction-utils.ts b/src/utils/key-extraction-utils.ts index 94d896f..acca77f 100644 --- a/src/utils/key-extraction-utils.ts +++ b/src/utils/key-extraction-utils.ts @@ -1,4 +1,4 @@ -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts } from 'ts-morph' /** @@ -33,12 +33,14 @@ export const extractStringKeys = (keysType: Node): string[] => { */ export const createTypeBoxKeys = (keys: string[]): ts.Expression => { if (keys.length === 1) { - return makeTypeCall('Literal', [ts.factory.createStringLiteral(keys[0]!)]) + return GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(keys[0]!)]) } - return makeTypeCall('Union', [ + return GenericTypeUtils.makeTypeCall('Union', [ ts.factory.createArrayLiteralExpression( - keys.map((k) => makeTypeCall('Literal', [ts.factory.createStringLiteral(k)])), + keys.map((k) => + GenericTypeUtils.makeTypeCall('Literal', [ts.factory.createStringLiteral(k)]), + ), ), ]) } diff --git a/src/utils/typebox-call.ts b/src/utils/typebox-call.ts index d4e558d..34eb957 100644 --- a/src/utils/typebox-call.ts +++ b/src/utils/typebox-call.ts @@ -1,15 +1,20 @@ import { TypeBoxTypeHandlers } from '@daxserver/validation-schema-codegen/handlers/typebox/typebox-type-handlers' -import { makeTypeCall } from '@daxserver/validation-schema-codegen/utils/typebox-codegen-utils' +import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' +import { GenericTypeUtils } from '@daxserver/validation-schema-codegen/utils/generic-type-utils' import { Node, ts } from 'ts-morph' let handlers: TypeBoxTypeHandlers | null = null -export const getTypeBoxType = (node?: Node): ts.Expression => { - if (!node) return makeTypeCall('Any') +export interface TypeBoxContext { + nodeGraph: NodeGraph +} + +export const getTypeBoxType = (node?: Node, context?: TypeBoxContext): ts.Expression => { + if (!node || !context) return GenericTypeUtils.makeTypeCall('Any') if (!handlers) { handlers = new TypeBoxTypeHandlers() } - return handlers.getHandler(node).handle(node) + return handlers.getHandler(node).handle(node, context) } diff --git a/src/utils/typebox-codegen-utils.ts b/src/utils/typebox-codegen-utils.ts deleted file mode 100644 index 38c65e9..0000000 --- a/src/utils/typebox-codegen-utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ts } from 'ts-morph' - -export const makeTypeCall = (method: string, args: ts.Expression[] = []) => { - return ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Type'), - ts.factory.createIdentifier(method), - ), - undefined, - args, - ) -} diff --git a/tests/integration/deep-instantiation-integration.test.ts b/tests/integration/deep-instantiation-integration.test.ts new file mode 100644 index 0000000..59e3f66 --- /dev/null +++ b/tests/integration/deep-instantiation-integration.test.ts @@ -0,0 +1,361 @@ +import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils' +import { beforeEach, describe, expect, test } from 'bun:test' +import { Project } from 'ts-morph' + +describe('Deep Instantiation Integration', () => { + let project: Project + + beforeEach(() => { + project = new Project() + }) + + test('should generate code without deep Instantiation', () => { + const sourceFile = createSourceFile( + project, + ` + type A = 'aa' | 'ab' | 'ac' | 'ad' | 'ae' | 'af' | 'ag' | 'ah' | 'ai' | 'aj' + type B = Record + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier( + ` + export const A = Type.Union([ + Type.Literal('aa'), + Type.Literal('ab'), + Type.Literal('ac'), + Type.Literal('ad'), + Type.Literal('ae'), + Type.Literal('af'), + Type.Literal('ag'), + Type.Literal('ah'), + Type.Literal('ai'), + Type.Literal('aj'), + ]); + + export type A = Static + + export const B = Type.Record(A, Type.String()) + + export type B = Static + `, + ), + ) + }) + + test('should generate code with deep instantiation 1', () => { + const sourceFile = createSourceFile( + project, + ` + type A = 'aa' | 'ab' | 'ac' | 'ad' | 'ae' | 'af' | 'ag' | 'ah' | 'ai' | 'aj' + | 'ak' | 'al' | 'am' | 'an' | 'ao' | 'ap' | 'aq' | 'ar' | 'as' | 'at' + | 'au' | 'av' | 'aw' | 'ax' | 'ay' | 'az' | 'ba' | 'bb' | 'bc' | 'bd' + | 'be' | 'bf' | 'bg' | 'bh' | 'bi' | 'bj' | 'bk' | 'bl' | 'bm' | 'bn' + | 'bo' | 'bp' | 'bq' | 'br' | 'bs' | 'bt' | 'bu' | 'bv' | 'bw' | 'bx' + | 'by' | 'bz' | 'ca' | 'cb' | 'cc' | 'cd' | 'ce' | 'cf' | 'cg' | 'ch' + | 'ci' | 'cj' | 'ck' | 'cl' | 'cm' | 'cn' | 'co' | 'cp' | 'cq' | 'cr' + | 'cs' | 'ct' | 'cu' | 'cv' | 'cw' | 'cx' | 'cy' | 'cz' + + type B = Partial> + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier( + ` + const A_Chunk1 = Type.Union([ + Type.Literal('aa'), + Type.Literal('ab'), + Type.Literal('ac'), + Type.Literal('ad'), + Type.Literal('ae'), + Type.Literal('af'), + Type.Literal('ag'), + Type.Literal('ah'), + Type.Literal('ai'), + Type.Literal('aj'), + Type.Literal('ak'), + Type.Literal('al'), + Type.Literal('am'), + Type.Literal('an'), + Type.Literal('ao'), + Type.Literal('ap'), + Type.Literal('aq'), + Type.Literal('ar'), + Type.Literal('as'), + Type.Literal('at'), + ]); + const A_Chunk2 = Type.Union([ + Type.Literal('au'), + Type.Literal('av'), + Type.Literal('aw'), + Type.Literal('ax'), + Type.Literal('ay'), + Type.Literal('az'), + Type.Literal('ba'), + Type.Literal('bb'), + Type.Literal('bc'), + Type.Literal('bd'), + Type.Literal('be'), + Type.Literal('bf'), + Type.Literal('bg'), + Type.Literal('bh'), + Type.Literal('bi'), + Type.Literal('bj'), + Type.Literal('bk'), + Type.Literal('bl'), + Type.Literal('bm'), + Type.Literal('bn'), + ]); + const A_Chunk3 = Type.Union([ + Type.Literal('bo'), + Type.Literal('bp'), + Type.Literal('bq'), + Type.Literal('br'), + Type.Literal('bs'), + Type.Literal('bt'), + Type.Literal('bu'), + Type.Literal('bv'), + Type.Literal('bw'), + Type.Literal('bx'), + Type.Literal('by'), + Type.Literal('bz'), + Type.Literal('ca'), + Type.Literal('cb'), + Type.Literal('cc'), + Type.Literal('cd'), + Type.Literal('ce'), + Type.Literal('cf'), + Type.Literal('cg'), + Type.Literal('ch'), + ]); + const A_Chunk4 = Type.Union([ + Type.Literal('ci'), + Type.Literal('cj'), + Type.Literal('ck'), + Type.Literal('cl'), + Type.Literal('cm'), + Type.Literal('cn'), + Type.Literal('co'), + Type.Literal('cp'), + Type.Literal('cq'), + Type.Literal('cr'), + Type.Literal('cs'), + Type.Literal('ct'), + Type.Literal('cu'), + Type.Literal('cv'), + Type.Literal('cw'), + Type.Literal('cx'), + Type.Literal('cy'), + Type.Literal('cz'), + ]); + export const A = Type.Union([ + A_Chunk1, + A_Chunk2, + A_Chunk3, + A_Chunk4, + ]); + + export type A = Static; + + export const B = Type.Partial( + Type.Intersect([ + Type.Record(A_Chunk1, Type.String()), + Type.Record(A_Chunk2, Type.String()), + Type.Record(A_Chunk3, Type.String()), + Type.Record(A_Chunk4, Type.String()), + ]), + ); + + export type B = Static; + `, + ), + ) + }) + + test('should generate code with deep instantiation 2', () => { + const sourceFile = createSourceFile( + project, + ` + type A = 'aa' | 'ab' | 'ac' | 'ad' | 'ae' | 'af' | 'ag' | 'ah' | 'ai' | 'aj' + | 'ak' | 'al' | 'am' | 'an' | 'ao' | 'ap' | 'aq' | 'ar' | 'as' | 'at' + | 'au' | 'av' | 'aw' | 'ax' | 'ay' | 'az' | 'ba' | 'bb' | 'bc' | 'bd' + | 'be' | 'bf' | 'bg' | 'bh' | 'bi' | 'bj' | 'bk' | 'bl' | 'bm' | 'bn' + | 'bo' | 'bp' | 'bq' | 'br' | 'bs' | 'bt' | 'bu' | 'bv' | 'bw' | 'bx' + | 'by' | 'bz' | 'ca' | 'cb' | 'cc' | 'cd' | 'ce' | 'cf' | 'cg' | 'ch' + | 'ci' | 'cj' | 'ck' | 'cl' | 'cm' | 'cn' | 'co' | 'cp' | 'cq' | 'cr' + | 'cs' | 'ct' | 'cu' | 'cv' | 'cw' | 'cx' | 'cy' | 'cz' | number + + type B = Record + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier( + ` + const A_Chunk1 = Type.Union([ + Type.Literal('aa'), + Type.Literal('ab'), + Type.Literal('ac'), + Type.Literal('ad'), + Type.Literal('ae'), + Type.Literal('af'), + Type.Literal('ag'), + Type.Literal('ah'), + Type.Literal('ai'), + Type.Literal('aj'), + Type.Literal('ak'), + Type.Literal('al'), + Type.Literal('am'), + Type.Literal('an'), + Type.Literal('ao'), + Type.Literal('ap'), + Type.Literal('aq'), + Type.Literal('ar'), + Type.Literal('as'), + Type.Literal('at'), + ]); + const A_Chunk2 = Type.Union([ + Type.Literal('au'), + Type.Literal('av'), + Type.Literal('aw'), + Type.Literal('ax'), + Type.Literal('ay'), + Type.Literal('az'), + Type.Literal('ba'), + Type.Literal('bb'), + Type.Literal('bc'), + Type.Literal('bd'), + Type.Literal('be'), + Type.Literal('bf'), + Type.Literal('bg'), + Type.Literal('bh'), + Type.Literal('bi'), + Type.Literal('bj'), + Type.Literal('bk'), + Type.Literal('bl'), + Type.Literal('bm'), + Type.Literal('bn'), + ]); + const A_Chunk3 = Type.Union([ + Type.Literal('bo'), + Type.Literal('bp'), + Type.Literal('bq'), + Type.Literal('br'), + Type.Literal('bs'), + Type.Literal('bt'), + Type.Literal('bu'), + Type.Literal('bv'), + Type.Literal('bw'), + Type.Literal('bx'), + Type.Literal('by'), + Type.Literal('bz'), + Type.Literal('ca'), + Type.Literal('cb'), + Type.Literal('cc'), + Type.Literal('cd'), + Type.Literal('ce'), + Type.Literal('cf'), + Type.Literal('cg'), + Type.Literal('ch'), + ]); + const A_Chunk4 = Type.Union([ + Type.Literal('ci'), + Type.Literal('cj'), + Type.Literal('ck'), + Type.Literal('cl'), + Type.Literal('cm'), + Type.Literal('cn'), + Type.Literal('co'), + Type.Literal('cp'), + Type.Literal('cq'), + Type.Literal('cr'), + Type.Literal('cs'), + Type.Literal('ct'), + Type.Literal('cu'), + Type.Literal('cv'), + Type.Literal('cw'), + Type.Literal('cx'), + Type.Literal('cy'), + Type.Literal('cz'), + Type.Number(), + ]); + export const A = Type.Union([ + A_Chunk1, + A_Chunk2, + A_Chunk3, + A_Chunk4, + ]); + + export type A = Static; + + export const B = Type.Intersect([ + Type.Record(A_Chunk1, Type.String()), + Type.Record(A_Chunk2, Type.String()), + Type.Record(A_Chunk3, Type.String()), + Type.Record(A_Chunk4, Type.String()), + ]); + + export type B = Static; + `, + ), + ) + }) + + test('should generate code with deep instantiation 3', () => { + const sourceFile = createSourceFile( + project, + ` + type A = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 + | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 + | string + + type B = Record + `, + ) + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier( + ` + const A_Chunk1 = Type.Union([ + Type.Literal(1), + Type.Literal(2), + Type.Literal(3), + Type.Literal(4), + Type.Literal(5), + Type.Literal(6), + Type.Literal(7), + Type.Literal(8), + Type.Literal(9), + Type.Literal(10), + Type.Literal(11), + Type.Literal(12), + Type.Literal(13), + Type.Literal(14), + Type.Literal(15), + Type.Literal(16), + Type.Literal(17), + Type.Literal(18), + Type.Literal(19), + Type.Literal(20), + ]); + const A_Chunk2 = Type.String(); + export const A = Type.Union([ + A_Chunk1, + A_Chunk2, + ]); + + export type A = Static; + + export const B = Type.Intersect([ + Type.Record(A_Chunk1, Type.String()), + Type.Record(A_Chunk2, Type.String()), + ]); + + export type B = Static; + `, + ), + ) + }) +}) diff --git a/tsconfig.build.json b/tsconfig.build.json index 71db777..6f58b8b 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -13,14 +13,6 @@ "allowImportingTsExtensions": false, "verbatimModuleSyntax": false }, - "include": [ - "src/**/*" - ], - "exclude": [ - "tests/**/*", - "**/*.test.ts", - "**/*.spec.ts", - "dist", - "node_modules" - ] + "include": ["src/**/*"], + "exclude": ["tests/**/*", "**/*.test.ts", "**/*.spec.ts", "dist", "node_modules"] }