diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index bda861c..575ec4c 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -33,7 +33,7 @@
- [TypeBoxTypeHandlers Optimizations](#typeboxtypehandlers-optimizations)
- [Performance Testing](#performance-testing)
- [Process Overview](#process-overview)
-- [Test-Driven Development (TDD) Approach](#test-driven-development-tdd-approach)
+- [Test-Driven Development](#test-driven-development)
- [TDD Cycle](#tdd-cycle)
- [Running Tests](#running-tests)
- [TDD Workflow for New Features](#tdd-workflow-for-new-features)
@@ -77,7 +77,7 @@ The code generation process includes sophisticated import resolution and depende
#### DependencyCollector
-The module implements a `DependencyCollector` class that:
+The module implements a `DependencyCollector` class that:
- **Traverses Import Chains**: Recursively follows import declarations to collect all type dependencies from external files
- **Builds Dependency Graph**: Creates a comprehensive map of type dependencies, tracking which types depend on which other types
@@ -113,11 +113,15 @@ The codebase provides comprehensive support for TypeScript interface inheritance
### Dependency-Ordered Processing
-Interfaces are processed in dependency order using a topological sort algorithm implemented in :
+The main codegen logic in implements sophisticated processing order management:
-1. **Dependency Analysis**: The `getInterfaceProcessingOrder` function analyzes all interfaces to identify inheritance relationships
-2. **Topological Sorting**: Interfaces are sorted to ensure base interfaces are processed before extended interfaces
-3. **Circular Dependency Detection**: The algorithm detects and handles circular inheritance scenarios gracefully
+1. **Dependency Analysis**: Uses `InterfaceTypeDependencyAnalyzer` to analyze complex relationships between interfaces and type aliases
+2. **Conditional Processing**: Handles three scenarios:
+ - Interfaces depending on type aliases only
+ - Type aliases depending on interfaces only
+ - Both dependencies present (three-phase processing)
+3. **Topological Sorting**: Ensures types are processed in correct dependency order to prevent "type not found" errors
+4. **Circular Dependency Detection**: The algorithm detects and handles circular inheritance scenarios gracefully
### TypeBox Composite Generation
@@ -126,11 +130,13 @@ Interface inheritance is implemented using TypeBox's `Type.Composite` functional
- **Base Interface Reference**: Extended interfaces reference their base interfaces by name as identifiers
- **Property Combination**: The `InterfaceTypeHandler` generates `Type.Composite([BaseInterface, Type.Object({...})])` for extended interfaces
- **Type Safety**: Generated code maintains full TypeScript type safety through proper static type aliases
+- **Generic Type Parameter Handling**: Uses `TSchema` as the constraint for TypeBox compatibility instead of preserving original TypeScript constraints
### Implementation Details
- **Heritage Clause Processing**: The processes `extends` clauses by extracting referenced type names
- **Identifier Generation**: Base interface references are converted to TypeScript identifiers rather than attempting recursive type resolution
+- **TypeBox Constraint Normalization**: Generic type parameters use `TSchema` constraints for TypeBox schema compatibility
- **Error Prevention**: The dependency ordering prevents "No handler found for type" errors that occur when extended interfaces are processed before their base interfaces
## Input Handling System
@@ -341,9 +347,9 @@ To ensure the dependency collection system performs efficiently under various sc
5. **Static Type Generation**: Alongside each TypeBox schema, a TypeScript `type` alias is generated using `Static` to provide compile-time type safety and seamless integration with existing TypeScript code.
6. **Output**: A new TypeScript file (as a string) containing the generated TypeBox schemas and static type aliases, ready to be written to disk or integrated into your application.
-## Test-Driven Development (TDD) Approach
+## Test-Driven Development
-This project follows a Test-Driven Development methodology to ensure code quality, maintainability, and reliability. The TDD workflow consists of three main phases:
+This project follows a Test-Driven Development (TDD) methodology to ensure code quality, maintainability, and reliability. The TDD workflow consists of three main phases:
### TDD Cycle
diff --git a/src/handlers/typebox/object/interface-type-handler.ts b/src/handlers/typebox/object/interface-type-handler.ts
index 91eae19..8603c57 100644
--- a/src/handlers/typebox/object/interface-type-handler.ts
+++ b/src/handlers/typebox/object/interface-type-handler.ts
@@ -69,10 +69,13 @@ export class InterfaceTypeHandler extends ObjectLikeBaseHandler {
const functionTypeParams = typeParameters.map((typeParam) => {
const paramName = typeParam.getName()
+ // Use TSchema as the constraint for TypeBox compatibility
+ const constraintNode = ts.factory.createTypeReferenceNode('TSchema', undefined)
+
return ts.factory.createTypeParameterDeclaration(
undefined,
ts.factory.createIdentifier(paramName),
- ts.factory.createTypeReferenceNode('TSchema', undefined),
+ constraintNode,
undefined,
)
})
diff --git a/src/handlers/typebox/type-reference-handler.ts b/src/handlers/typebox/type-reference-handler.ts
index e264b24..91d8a11 100644
--- a/src/handlers/typebox/type-reference-handler.ts
+++ b/src/handlers/typebox/type-reference-handler.ts
@@ -1,4 +1,5 @@
import { BaseTypeHandler } from '@daxserver/validation-schema-codegen/handlers/typebox/base-type-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, TypeReferenceNode } from 'ts-morph'
@@ -9,9 +10,23 @@ export class TypeReferenceHandler extends BaseTypeHandler {
handle(node: TypeReferenceNode): ts.Expression {
const referencedType = node.getTypeName()
+ const typeArguments = node.getTypeArguments()
if (Node.isIdentifier(referencedType)) {
const typeName = referencedType.getText()
+
+ // If there are type arguments, create a function call
+ if (typeArguments.length > 0) {
+ const typeBoxArgs = typeArguments.map((arg) => getTypeBoxType(arg))
+
+ return ts.factory.createCallExpression(
+ ts.factory.createIdentifier(typeName),
+ undefined,
+ typeBoxArgs,
+ )
+ }
+
+ // No type arguments, just return the identifier
return ts.factory.createIdentifier(typeName)
}
diff --git a/src/parsers/parse-interfaces.ts b/src/parsers/parse-interfaces.ts
index a6360a3..d2a2a9c 100644
--- a/src/parsers/parse-interfaces.ts
+++ b/src/parsers/parse-interfaces.ts
@@ -107,10 +107,13 @@ export class InterfaceParser extends BaseParser {
// Create type parameters for the type alias
const typeParamDeclarations = typeParameters.map((typeParam) => {
const paramName = typeParam.getName()
+ // Use TSchema as the constraint for TypeBox compatibility
+ const constraintNode = ts.factory.createTypeReferenceNode('TSchema', undefined)
+
return ts.factory.createTypeParameterDeclaration(
undefined,
ts.factory.createIdentifier(paramName),
- ts.factory.createTypeReferenceNode('TSchema', undefined),
+ constraintNode,
undefined,
)
})
diff --git a/src/traverse/ast-traversal.ts b/src/traverse/ast-traversal.ts
new file mode 100644
index 0000000..83213fe
--- /dev/null
+++ b/src/traverse/ast-traversal.ts
@@ -0,0 +1,111 @@
+import { InterfaceDeclaration, Node, TypeAliasDeclaration, TypeReferenceNode } from 'ts-morph'
+
+/**
+ * AST traversal patterns used in dependency analysis
+ */
+export class ASTTraversal {
+ private static cache = new Map()
+
+ /**
+ * Extract interface names referenced by a type alias
+ */
+ static extractInterfaceReferences(
+ typeAlias: TypeAliasDeclaration,
+ interfaces: Map,
+ ): string[] {
+ const typeNode = typeAlias.getTypeNode()
+ if (!typeNode) return []
+
+ const cacheKey = `interface_refs_${typeNode.getText()}`
+ const cached = ASTTraversal.cache.get(cacheKey)
+ if (cached) return cached
+
+ const references: string[] = []
+ const visited = new Set()
+
+ const traverse = (node: Node): void => {
+ if (visited.has(node)) return
+ visited.add(node)
+
+ // Handle type references
+ if (Node.isTypeReference(node)) {
+ const typeRefNode = node as TypeReferenceNode
+ const typeName = typeRefNode.getTypeName().getText()
+
+ if (interfaces.has(typeName)) {
+ references.push(typeName)
+ }
+ // Continue traversing to handle type arguments in generic instantiations
+ }
+
+ // Use forEachChild for better performance
+ node.forEachChild(traverse)
+ }
+
+ traverse(typeNode)
+
+ // Cache the result
+ ASTTraversal.cache.set(cacheKey, references)
+ return references
+ }
+
+ /**
+ * Extract type alias names referenced by an interface (e.g., in type parameter constraints)
+ */
+ static extractTypeAliasReferences(
+ interfaceDecl: InterfaceDeclaration,
+ typeAliases: Map,
+ ): string[] {
+ const cacheKey = `type_alias_refs_${interfaceDecl.getName()}_${interfaceDecl.getText()}`
+ const cached = ASTTraversal.cache.get(cacheKey)
+ if (cached) return cached
+
+ const references: string[] = []
+ const visited = new Set()
+
+ const traverse = (node: Node): void => {
+ if (visited.has(node)) return
+ visited.add(node)
+
+ // Handle type references
+ if (Node.isTypeReference(node)) {
+ const typeRefNode = node as TypeReferenceNode
+ const typeName = typeRefNode.getTypeName().getText()
+
+ if (typeAliases.has(typeName)) {
+ references.push(typeName)
+ }
+ return // No need to traverse children of type references
+ }
+
+ // Use forEachChild for better performance
+ node.forEachChild(traverse)
+ }
+
+ // Check type parameters for constraints
+ for (const typeParam of interfaceDecl.getTypeParameters()) {
+ const constraint = typeParam.getConstraint()
+ if (constraint) {
+ traverse(constraint)
+ }
+ }
+
+ // Check heritage clauses
+ for (const heritageClause of interfaceDecl.getHeritageClauses()) {
+ for (const typeNode of heritageClause.getTypeNodes()) {
+ traverse(typeNode)
+ }
+ }
+
+ // Cache the result
+ ASTTraversal.cache.set(cacheKey, references)
+ return references
+ }
+
+ /**
+ * Clear the internal cache
+ */
+ static clearCache(): void {
+ ASTTraversal.cache.clear()
+ }
+}
diff --git a/src/traverse/dependency-analyzer.ts b/src/traverse/dependency-analyzer.ts
new file mode 100644
index 0000000..951ed0f
--- /dev/null
+++ b/src/traverse/dependency-analyzer.ts
@@ -0,0 +1,140 @@
+import { ASTTraversal } from '@daxserver/validation-schema-codegen/traverse/ast-traversal'
+import { InterfaceDeclaration, TypeAliasDeclaration } from 'ts-morph'
+
+/**
+ * Dependency analyzer for determining processing order
+ */
+export class DependencyAnalyzer {
+ /**
+ * Extract interface names referenced by a type alias
+ */
+ extractInterfaceReferences(
+ typeAlias: TypeAliasDeclaration,
+ interfaces: Map,
+ ): string[] {
+ return ASTTraversal.extractInterfaceReferences(typeAlias, interfaces)
+ }
+
+ /**
+ * Extract type alias names referenced by an interface
+ */
+ extractTypeAliasReferences(
+ interfaceDecl: InterfaceDeclaration,
+ typeAliases: Map,
+ ): string[] {
+ return ASTTraversal.extractTypeAliasReferences(interfaceDecl, typeAliases)
+ }
+
+ /**
+ * Check if any type aliases reference interfaces
+ */
+ hasInterfaceReferences(
+ typeAliases: TypeAliasDeclaration[],
+ interfaces: InterfaceDeclaration[],
+ ): boolean {
+ const interfaceMap = new Map()
+ for (const iface of interfaces) {
+ interfaceMap.set(iface.getName(), iface)
+ }
+
+ for (const typeAlias of typeAliases) {
+ const references = this.extractInterfaceReferences(typeAlias, interfaceMap)
+ if (references.length > 0) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ /**
+ * Get type aliases that reference interfaces, ordered by their dependencies
+ */
+ getTypeAliasesReferencingInterfaces(
+ typeAliases: TypeAliasDeclaration[],
+ interfaces: InterfaceDeclaration[],
+ ): { typeAlias: TypeAliasDeclaration; referencedInterfaces: string[] }[] {
+ const interfaceMap = new Map()
+ for (const iface of interfaces) {
+ interfaceMap.set(iface.getName(), iface)
+ }
+
+ const result: { typeAlias: TypeAliasDeclaration; referencedInterfaces: string[] }[] = []
+
+ for (const typeAlias of typeAliases) {
+ const references = this.extractInterfaceReferences(typeAlias, interfaceMap)
+ if (references.length > 0) {
+ result.push({
+ typeAlias,
+ referencedInterfaces: references,
+ })
+ }
+ }
+
+ return result
+ }
+
+ /**
+ * Determine the correct processing order for interfaces and type aliases
+ * Returns an object indicating which should be processed first
+ */
+ analyzeProcessingOrder(
+ typeAliases: TypeAliasDeclaration[],
+ interfaces: InterfaceDeclaration[],
+ ): {
+ processInterfacesFirst: boolean
+ typeAliasesDependingOnInterfaces: string[]
+ interfacesDependingOnTypeAliases: string[]
+ } {
+ const typeAliasMap = new Map()
+ const interfaceMap = new Map()
+
+ for (const typeAlias of typeAliases) {
+ typeAliasMap.set(typeAlias.getName(), typeAlias)
+ }
+
+ for (const interfaceDecl of interfaces) {
+ interfaceMap.set(interfaceDecl.getName(), interfaceDecl)
+ }
+
+ const typeAliasesDependingOnInterfaces: string[] = []
+ const interfacesDependingOnTypeAliases: string[] = []
+
+ // Check type aliases that depend on interfaces
+ for (const typeAlias of typeAliases) {
+ const interfaceRefs = this.extractInterfaceReferences(typeAlias, interfaceMap)
+ if (interfaceRefs.length > 0) {
+ typeAliasesDependingOnInterfaces.push(typeAlias.getName())
+ }
+ }
+
+ // Check interfaces that depend on type aliases
+ for (const interfaceDecl of interfaces) {
+ const typeAliasRefs = this.extractTypeAliasReferences(interfaceDecl, typeAliasMap)
+ if (typeAliasRefs.length > 0) {
+ interfacesDependingOnTypeAliases.push(interfaceDecl.getName())
+ }
+ }
+
+ // Determine processing order:
+ // If interfaces depend on type aliases, process type aliases first
+ // If only type aliases depend on interfaces, process interfaces first
+ // If both have dependencies, process type aliases that interfaces depend on first,
+ // then interfaces, then type aliases that depend on interfaces
+ const processInterfacesFirst =
+ interfacesDependingOnTypeAliases.length === 0 && typeAliasesDependingOnInterfaces.length > 0
+
+ return {
+ processInterfacesFirst,
+ typeAliasesDependingOnInterfaces,
+ interfacesDependingOnTypeAliases,
+ }
+ }
+
+ /**
+ * Clear internal caches
+ */
+ clearCache(): void {
+ ASTTraversal.clearCache()
+ }
+}
diff --git a/src/utils/dependency-collector.ts b/src/traverse/dependency-collector.ts
similarity index 97%
rename from src/utils/dependency-collector.ts
rename to src/traverse/dependency-collector.ts
index 24648ba..b3b0668 100644
--- a/src/utils/dependency-collector.ts
+++ b/src/traverse/dependency-collector.ts
@@ -1,11 +1,11 @@
import {
DefaultFileResolver,
type FileResolver,
-} from '@daxserver/validation-schema-codegen/utils/dependency-file-resolver'
+} from '@daxserver/validation-schema-codegen/traverse/dependency-file-resolver'
import {
DefaultTypeReferenceExtractor,
type TypeReferenceExtractor,
-} from '@daxserver/validation-schema-codegen/utils/dependency-type'
+} from '@daxserver/validation-schema-codegen/traverse/dependency-type'
import { ImportDeclaration, SourceFile, TypeAliasDeclaration } from 'ts-morph'
export interface TypeDependency {
diff --git a/src/utils/dependency-file-resolver.ts b/src/traverse/dependency-file-resolver.ts
similarity index 100%
rename from src/utils/dependency-file-resolver.ts
rename to src/traverse/dependency-file-resolver.ts
diff --git a/src/utils/dependency-type.ts b/src/traverse/dependency-type.ts
similarity index 97%
rename from src/utils/dependency-type.ts
rename to src/traverse/dependency-type.ts
index 29ef00b..0bc1fb4 100644
--- a/src/utils/dependency-type.ts
+++ b/src/traverse/dependency-type.ts
@@ -1,4 +1,4 @@
-import type { TypeDependency } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import type { TypeDependency } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import { Node, TypeReferenceNode } from 'ts-morph'
export interface TypeReferenceExtractor {
diff --git a/src/ts-morph-codegen.ts b/src/ts-morph-codegen.ts
index 8105a5c..f6611ef 100644
--- a/src/ts-morph-codegen.ts
+++ b/src/ts-morph-codegen.ts
@@ -6,7 +6,8 @@ import { EnumParser } from '@daxserver/validation-schema-codegen/parsers/parse-e
import { FunctionDeclarationParser } from '@daxserver/validation-schema-codegen/parsers/parse-function-declarations'
import { InterfaceParser } from '@daxserver/validation-schema-codegen/parsers/parse-interfaces'
import { TypeAliasParser } from '@daxserver/validation-schema-codegen/parsers/parse-type-aliases'
-import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyAnalyzer } from '@daxserver/validation-schema-codegen/traverse/dependency-analyzer'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import { getInterfaceProcessingOrder } from '@daxserver/validation-schema-codegen/utils/interface-processing-order'
import { Project, ts } from 'ts-morph'
@@ -68,10 +69,38 @@ export const generateCode = async ({
const interfaceParser = new InterfaceParser(parserOptions)
const functionDeclarationParser = new FunctionDeclarationParser(parserOptions)
const dependencyCollector = new DependencyCollector()
+ const dependencyAnalyzer = new DependencyAnalyzer()
// Collect all dependencies in correct order
const importDeclarations = sourceFile.getImportDeclarations()
const localTypeAliases = sourceFile.getTypeAliases()
+ const interfaces = sourceFile.getInterfaces()
+
+ // Analyze cross-dependencies between interfaces and type aliases
+ const dependencyAnalysis = dependencyAnalyzer.analyzeProcessingOrder(localTypeAliases, interfaces)
+
+ // Handle different dependency scenarios:
+ // 1. If interfaces depend on type aliases, process those type aliases first
+ // 2. If only type aliases depend on interfaces, process interfaces first
+ // 3. If both scenarios exist, process in order: type aliases interfaces depend on -> interfaces -> type aliases that depend on interfaces
+
+ const hasInterfacesDependingOnTypeAliases =
+ dependencyAnalysis.interfacesDependingOnTypeAliases.length > 0
+ const hasTypeAliasesDependingOnInterfaces =
+ dependencyAnalysis.typeAliasesDependingOnInterfaces.length > 0
+
+ if (hasInterfacesDependingOnTypeAliases && !hasTypeAliasesDependingOnInterfaces) {
+ // Case 1: Only interfaces depend on type aliases - process type aliases first (normal order)
+ // This will be handled by the normal dependency collection below
+ } else if (!hasInterfacesDependingOnTypeAliases && hasTypeAliasesDependingOnInterfaces) {
+ // Case 2: Only type aliases depend on interfaces - process interfaces first
+ getInterfaceProcessingOrder(interfaces).forEach((i) => {
+ interfaceParser.parse(i)
+ })
+ } else if (hasInterfacesDependingOnTypeAliases && hasTypeAliasesDependingOnInterfaces) {
+ // Case 3: Both dependencies exist - process type aliases that interfaces depend on first
+ // This will be handled by the normal dependency collection below, then interfaces, then remaining type aliases
+ }
// Always add local types first so they can be included in topological sort
dependencyCollector.addLocalTypes(localTypeAliases, sourceFile)
@@ -81,20 +110,57 @@ export const generateCode = async ({
exportEverything,
)
- // Process all dependencies (both imported and local) in topological order
- orderedDependencies.forEach((dependency) => {
- if (!processedTypes.has(dependency.typeAlias.getName())) {
- typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
- }
- })
-
- // Process any remaining local types that weren't included in the dependency graph
- if (exportEverything) {
- localTypeAliases.forEach((typeAlias) => {
- if (!processedTypes.has(typeAlias.getName())) {
- typeAliasParser.parseWithImportFlag(typeAlias, false)
+ if (!hasInterfacesDependingOnTypeAliases && hasTypeAliasesDependingOnInterfaces) {
+ // Case 2: Only process type aliases that don't depend on interfaces
+ orderedDependencies.forEach((dependency) => {
+ const dependsOnInterface = dependencyAnalysis.typeAliasesDependingOnInterfaces.includes(
+ dependency.typeAlias.getName(),
+ )
+ if (!dependsOnInterface && !processedTypes.has(dependency.typeAlias.getName())) {
+ typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
}
})
+ } else if (hasInterfacesDependingOnTypeAliases && hasTypeAliasesDependingOnInterfaces) {
+ // Case 3: Process only type aliases that interfaces depend on (phase 1)
+ orderedDependencies.forEach((dependency) => {
+ const interfaceDependsOnThis = dependencyAnalysis.interfacesDependingOnTypeAliases.some(
+ (interfaceName) => {
+ const interfaceDecl = interfaces.find((i) => i.getName() === interfaceName)
+ if (!interfaceDecl) return false
+ const typeAliasRefs = dependencyAnalyzer.extractTypeAliasReferences(
+ interfaceDecl,
+ new Map(localTypeAliases.map((ta) => [ta.getName(), ta])),
+ )
+ return typeAliasRefs.includes(dependency.typeAlias.getName())
+ },
+ )
+ const dependsOnInterface = dependencyAnalysis.typeAliasesDependingOnInterfaces.includes(
+ dependency.typeAlias.getName(),
+ )
+ if (
+ interfaceDependsOnThis &&
+ !dependsOnInterface &&
+ !processedTypes.has(dependency.typeAlias.getName())
+ ) {
+ typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
+ }
+ })
+ } else {
+ // Case 1: Process all dependencies (both imported and local) in topological order
+ orderedDependencies.forEach((dependency) => {
+ if (!processedTypes.has(dependency.typeAlias.getName())) {
+ typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
+ }
+ })
+
+ // Process any remaining local types that weren't included in the dependency graph
+ if (exportEverything) {
+ localTypeAliases.forEach((typeAlias) => {
+ if (!processedTypes.has(typeAlias.getName())) {
+ typeAliasParser.parseWithImportFlag(typeAlias, false)
+ }
+ })
+ }
}
// Process enums
@@ -103,9 +169,37 @@ export const generateCode = async ({
})
// Process interfaces in dependency order
- getInterfaceProcessingOrder(sourceFile.getInterfaces()).forEach((i) => {
- interfaceParser.parse(i)
- })
+ if (
+ hasInterfacesDependingOnTypeAliases ||
+ (!hasInterfacesDependingOnTypeAliases && !hasTypeAliasesDependingOnInterfaces)
+ ) {
+ // Case 1 and Case 3: Process interfaces after type aliases they depend on
+ getInterfaceProcessingOrder(interfaces).forEach((i) => {
+ interfaceParser.parse(i)
+ })
+ }
+ // Case 2: Interfaces were already processed above
+
+ // Process remaining type aliases that depend on interfaces (Case 2 and Case 3)
+ if (hasTypeAliasesDependingOnInterfaces) {
+ orderedDependencies.forEach((dependency) => {
+ const dependsOnInterface = dependencyAnalysis.typeAliasesDependingOnInterfaces.includes(
+ dependency.typeAlias.getName(),
+ )
+ if (dependsOnInterface && !processedTypes.has(dependency.typeAlias.getName())) {
+ typeAliasParser.parseWithImportFlag(dependency.typeAlias, dependency.isImported)
+ }
+ })
+
+ // Process any remaining local types that weren't included in the dependency graph
+ if (exportEverything) {
+ localTypeAliases.forEach((typeAlias) => {
+ if (!processedTypes.has(typeAlias.getName())) {
+ typeAliasParser.parseWithImportFlag(typeAlias, false)
+ }
+ })
+ }
+ }
// Process function declarations
sourceFile.getFunctions().forEach((f) => {
diff --git a/tests/dependency-collector.integration.test.ts b/tests/dependency-collector.integration.test.ts
index 1a8bcab..9132d3d 100644
--- a/tests/dependency-collector.integration.test.ts
+++ b/tests/dependency-collector.integration.test.ts
@@ -1,4 +1,4 @@
-import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import { createSourceFile } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/dependency-collector.performance.test.ts b/tests/dependency-collector.performance.test.ts
index 44e8099..a702723 100644
--- a/tests/dependency-collector.performance.test.ts
+++ b/tests/dependency-collector.performance.test.ts
@@ -1,4 +1,4 @@
-import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import { createSourceFile } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'
diff --git a/tests/dependency-collector.unit.test.ts b/tests/dependency-collector.unit.test.ts
index 963146f..a25e525 100644
--- a/tests/dependency-collector.unit.test.ts
+++ b/tests/dependency-collector.unit.test.ts
@@ -1,12 +1,12 @@
-import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import {
DefaultFileResolver,
type FileResolver,
-} from '@daxserver/validation-schema-codegen/utils/dependency-file-resolver'
+} from '@daxserver/validation-schema-codegen/traverse/dependency-file-resolver'
import {
DefaultTypeReferenceExtractor,
type TypeReferenceExtractor,
-} from '@daxserver/validation-schema-codegen/utils/dependency-type'
+} from '@daxserver/validation-schema-codegen/traverse/dependency-type'
import { describe, expect, mock, test } from 'bun:test'
import type { ImportDeclaration, SourceFile, TypeAliasDeclaration, TypeNode } from 'ts-morph'
diff --git a/tests/handlers/typebox/interfaces.test.ts b/tests/handlers/typebox/interfaces.test.ts
index 2be4b04..a8aa1aa 100644
--- a/tests/handlers/typebox/interfaces.test.ts
+++ b/tests/handlers/typebox/interfaces.test.ts
@@ -201,34 +201,105 @@ describe('Interfaces', () => {
)
})
- test('generic types', () => {
- const sourceFile = createSourceFile(
- project,
- `
- interface A { a: T }
- interface B extends A { b: number }
- `,
- )
-
- expect(generateFormattedCode(sourceFile)).resolves.toBe(
- formatWithPrettier(
+ describe('generic types', () => {
+ test('generic types', () => {
+ const sourceFile = createSourceFile(
+ project,
`
- const A = (T: T) => Type.Object({
- a: T
- });
+ interface A { a: T }
+ interface B extends A { b: number }
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(
+ `
+ const A = (T: T) => Type.Object({
+ a: T
+ });
+
+ type A = Static>>;
+
+ const B = Type.Composite([A(Type.Number()), Type.Object({
+ b: Type.Number()
+ })]);
+
+ type B = Static;
+ `,
+ true,
+ true,
+ ),
+ )
+ })
+
+ test('generic types extension', () => {
+ const sourceFile = createSourceFile(
+ project,
+ `
+ interface A { a: T }
+ interface B extends A { b: T }
+ `,
+ )
+
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(
+ `
+ const A = (T: T) => Type.Object({
+ a: T
+ });
+
+ type A = Static>>;
+
+ const B = (T: T) => Type.Composite([A(T), Type.Object({
+ b: T
+ })]);
+
+ type B = Static>>;
+ `,
+ true,
+ true,
+ ),
+ )
+ })
+
+ test('generic types with extended type', () => {
+ const sourceFile = createSourceFile(
+ project,
+ `
+ declare const A: readonly ["a", "b"]
+ type A = typeof A[number]
+ interface B { a: T }
+ type C = B<'a'>
+ type D = B<'b'>
+ `,
+ )
- type A = Static>>;
+ expect(generateFormattedCode(sourceFile)).resolves.toBe(
+ formatWithPrettier(
+ `
+ const A = Type.Union([Type.Literal('a'), Type.Literal('b')])
- const B = Type.Composite([A(Type.Number()), Type.Object({
- b: Type.Number()
- })]);
+ type A = Static
- type B = Static;
- `,
- true,
- true,
- ),
- )
+ const B = (T: T) => Type.Object({
+ a: T
+ })
+
+ type B = Static>>
+
+ const C = B(Type.Literal('a'))
+
+ type C = Static
+
+ const D = B(Type.Literal('b'))
+
+ type D = Static
+ `,
+ true,
+ true,
+ ),
+ )
+ })
})
})
})
diff --git a/tests/import-resolution.test.ts b/tests/import-resolution.test.ts
index 173e343..a055bd8 100644
--- a/tests/import-resolution.test.ts
+++ b/tests/import-resolution.test.ts
@@ -1,4 +1,4 @@
-import { DependencyCollector } from '@daxserver/validation-schema-codegen/utils/dependency-collector'
+import { DependencyCollector } from '@daxserver/validation-schema-codegen/traverse/dependency-collector'
import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils'
import { beforeEach, describe, expect, test } from 'bun:test'
import { Project } from 'ts-morph'