Skip to content

Commit 6f520f1

Browse files
authored
feat(graph): rework dependency traversal with graphology DAG (#13)
1 parent 8a18529 commit 6f520f1

37 files changed

+1754
-2539
lines changed

ARCHITECTURE.md

Lines changed: 161 additions & 326 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "@daxserver/validation-schema-codegen",
33
"version": "0.1.0",
4-
"main": "src/ts-morph-codegen.ts",
5-
"module": "src/ts-morph-codegen.ts",
4+
"main": "src/index.ts",
5+
"module": "src/index.ts",
66
"devDependencies": {
77
"@eslint/js": "^9.34.0",
88
"@prettier/sync": "^0.6.1",

src/index.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
createSourceFileFromInput,
3+
type InputOptions,
4+
} from '@daxserver/validation-schema-codegen/input-handler'
5+
import { TypeBoxPrinter } from '@daxserver/validation-schema-codegen/printer/typebox-printer'
6+
import { DependencyTraversal } from '@daxserver/validation-schema-codegen/traverse/dependency-traversal'
7+
import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types'
8+
import { Node, Project, SourceFile, ts } from 'ts-morph'
9+
10+
const createOutputFile = (hasGenericInterfaces: boolean) => {
11+
const newSourceFile = new Project().createSourceFile('output.ts', '', {
12+
overwrite: true,
13+
})
14+
15+
// Add imports
16+
const namedImports = [
17+
'Type',
18+
{
19+
name: 'Static',
20+
isTypeOnly: true,
21+
},
22+
]
23+
24+
if (hasGenericInterfaces) {
25+
namedImports.push({
26+
name: 'TSchema',
27+
isTypeOnly: true,
28+
})
29+
}
30+
31+
newSourceFile.addImportDeclaration({
32+
moduleSpecifier: '@sinclair/typebox',
33+
namedImports,
34+
})
35+
36+
return newSourceFile
37+
}
38+
39+
const printSortedNodes = (sortedTraversedNodes: TraversedNode[], newSourceFile: SourceFile) => {
40+
const printer = new TypeBoxPrinter({
41+
newSourceFile,
42+
printer: ts.createPrinter(),
43+
})
44+
45+
// Process nodes in topological order
46+
for (const traversedNode of sortedTraversedNodes) {
47+
printer.printNode(traversedNode)
48+
}
49+
50+
return newSourceFile.getFullText()
51+
}
52+
53+
export const generateCode = ({ sourceCode, filePath, ...options }: InputOptions): string => {
54+
// Create source file from input
55+
const sourceFile = createSourceFileFromInput({
56+
sourceCode,
57+
filePath,
58+
...options,
59+
})
60+
61+
// Create dependency traversal and start traversal
62+
const dependencyTraversal = new DependencyTraversal()
63+
const traversedNodes = dependencyTraversal.startTraversal(sourceFile)
64+
65+
// Check if any interfaces have generic type parameters
66+
const hasGenericInterfaces = traversedNodes.some(
67+
(t) => Node.isInterfaceDeclaration(t.node) && t.node.getTypeParameters().length > 0,
68+
)
69+
70+
// Create output file with proper imports
71+
const newSourceFile = createOutputFile(hasGenericInterfaces)
72+
73+
// Print sorted nodes to output
74+
const result = printSortedNodes(traversedNodes, newSourceFile)
75+
76+
return result
77+
}

src/parsers/base-parser.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
import { ExportGetableNode, Node, SourceFile, ts } from 'ts-morph'
1+
import { Node, SourceFile, ts } from 'ts-morph'
22

33
export interface BaseParserOptions {
44
newSourceFile: SourceFile
55
printer: ts.Printer
66
processedTypes: Set<string>
7-
exportEverything?: boolean
87
}
98

109
export abstract class BaseParser {
1110
protected newSourceFile: SourceFile
1211
protected printer: ts.Printer
1312
protected processedTypes: Set<string>
14-
protected exportEverything: boolean
1513

1614
constructor(options: BaseParserOptions) {
1715
this.newSourceFile = options.newSourceFile
1816
this.printer = options.printer
1917
this.processedTypes = options.processedTypes
20-
this.exportEverything = options.exportEverything ?? false
21-
}
22-
23-
protected getIsExported(node: ExportGetableNode, isImported: boolean = false): boolean {
24-
if (this.exportEverything) {
25-
return true
26-
}
27-
return isImported ? false : node.hasExportKeyword()
2818
}
2919

3020
abstract parse(node: Node): void

src/parsers/parse-enums.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,37 @@ import { EnumDeclaration, VariableDeclarationKind } from 'ts-morph'
44

55
export class EnumParser extends BaseParser {
66
parse(enumDeclaration: EnumDeclaration): void {
7-
const typeName = enumDeclaration.getName()
8-
const enumText = enumDeclaration.getText()
9-
const isExported = this.getIsExported(enumDeclaration)
10-
this.newSourceFile.addStatements(isExported ? `export ${enumText}` : enumText)
7+
const enumName = enumDeclaration.getName()
8+
const schemaName = `${enumName}Schema`
9+
10+
this.newSourceFile.addEnum({
11+
name: enumName,
12+
isExported: true,
13+
members: enumDeclaration.getMembers().map((member) => ({
14+
name: member.getName(),
15+
value: member.hasInitializer() ? member.getValue() : undefined,
16+
})),
17+
})
18+
19+
// Generate TypeBox type
20+
const typeboxType = `Type.Enum(${enumName})`
1121

1222
this.newSourceFile.addVariableStatement({
13-
isExported,
23+
isExported: true,
1424
declarationKind: VariableDeclarationKind.Const,
1525
declarations: [
1626
{
17-
name: typeName,
18-
initializer: `Type.Enum(${typeName})`,
27+
name: schemaName,
28+
initializer: typeboxType,
1929
},
2030
],
2131
})
2232

2333
addStaticTypeAlias(
2434
this.newSourceFile,
25-
typeName,
35+
schemaName,
2636
this.newSourceFile.compilerNode,
2737
this.printer,
28-
isExported,
2938
)
3039
}
3140
}

src/parsers/parse-function-declarations.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,10 @@ import { FunctionDeclaration, ts, VariableDeclarationKind } from 'ts-morph'
66

77
export class FunctionDeclarationParser extends BaseParser {
88
parse(functionDecl: FunctionDeclaration): void {
9-
this.parseWithImportFlag(functionDecl, false)
10-
}
11-
12-
parseWithImportFlag(functionDecl: FunctionDeclaration, isImported: boolean): void {
13-
this.parseFunctionWithImportFlag(functionDecl, isImported)
14-
}
15-
16-
private parseFunctionWithImportFlag(
17-
functionDecl: FunctionDeclaration,
18-
isImported: boolean,
19-
): void {
209
const functionName = functionDecl.getName()
21-
if (!functionName) {
22-
return
23-
}
10+
if (!functionName) return
2411

25-
if (this.processedTypes.has(functionName)) {
26-
return
27-
}
12+
if (this.processedTypes.has(functionName)) return
2813
this.processedTypes.add(functionName)
2914

3015
// Get function parameters and return type
@@ -70,10 +55,8 @@ export class FunctionDeclarationParser extends BaseParser {
7055
this.newSourceFile.compilerNode,
7156
)
7257

73-
const isExported = this.getIsExported(functionDecl, isImported)
74-
7558
this.newSourceFile.addVariableStatement({
76-
isExported,
59+
isExported: true,
7760
declarationKind: VariableDeclarationKind.Const,
7861
declarations: [
7962
{
@@ -88,7 +71,6 @@ export class FunctionDeclarationParser extends BaseParser {
8871
functionName,
8972
this.newSourceFile.compilerNode,
9073
this.printer,
91-
isExported,
9274
)
9375
}
9476
}

src/parsers/parse-interfaces.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@ import {
1010

1111
export class InterfaceParser extends BaseParser {
1212
parse(interfaceDecl: InterfaceDeclaration): void {
13-
this.parseWithImportFlag(interfaceDecl, false)
14-
}
15-
16-
parseWithImportFlag(interfaceDecl: InterfaceDeclaration, isImported: boolean): void {
17-
this.parseInterfaceWithImportFlag(interfaceDecl, isImported)
18-
}
19-
20-
private parseInterfaceWithImportFlag(
21-
interfaceDecl: InterfaceDeclaration,
22-
isImported: boolean,
23-
): void {
2413
const interfaceName = interfaceDecl.getName()
2514

2615
if (this.processedTypes.has(interfaceName)) {
@@ -30,17 +19,16 @@ export class InterfaceParser extends BaseParser {
3019
this.processedTypes.add(interfaceName)
3120

3221
const typeParameters = interfaceDecl.getTypeParameters()
33-
const isExported = this.getIsExported(interfaceDecl, isImported)
3422

3523
// Check if interface has type parameters (generic)
3624
if (typeParameters.length > 0) {
37-
this.parseGenericInterface(interfaceDecl, isExported)
25+
this.parseGenericInterface(interfaceDecl)
3826
} else {
39-
this.parseRegularInterface(interfaceDecl, isExported)
27+
this.parseRegularInterface(interfaceDecl)
4028
}
4129
}
4230

43-
private parseRegularInterface(interfaceDecl: InterfaceDeclaration, isExported: boolean): void {
31+
private parseRegularInterface(interfaceDecl: InterfaceDeclaration): void {
4432
const interfaceName = interfaceDecl.getName()
4533

4634
// Generate TypeBox type definition
@@ -52,7 +40,7 @@ export class InterfaceParser extends BaseParser {
5240
)
5341

5442
this.newSourceFile.addVariableStatement({
55-
isExported,
43+
isExported: true,
5644
declarationKind: VariableDeclarationKind.Const,
5745
declarations: [
5846
{
@@ -67,11 +55,10 @@ export class InterfaceParser extends BaseParser {
6755
interfaceName,
6856
this.newSourceFile.compilerNode,
6957
this.printer,
70-
isExported,
7158
)
7259
}
7360

74-
private parseGenericInterface(interfaceDecl: InterfaceDeclaration, isExported: boolean): void {
61+
private parseGenericInterface(interfaceDecl: InterfaceDeclaration): void {
7562
const interfaceName = interfaceDecl.getName()
7663
const typeParameters = interfaceDecl.getTypeParameters()
7764

@@ -85,7 +72,7 @@ export class InterfaceParser extends BaseParser {
8572

8673
// Add the function declaration
8774
this.newSourceFile.addVariableStatement({
88-
isExported,
75+
isExported: true,
8976
declarationKind: VariableDeclarationKind.Const,
9077
declarations: [
9178
{
@@ -96,14 +83,10 @@ export class InterfaceParser extends BaseParser {
9683
})
9784

9885
// Add generic type alias: type A<T extends TSchema> = Static<ReturnType<typeof A<T>>>
99-
this.addGenericTypeAlias(interfaceName, typeParameters, isExported)
86+
this.addGenericTypeAlias(interfaceName, typeParameters)
10087
}
10188

102-
private addGenericTypeAlias(
103-
name: string,
104-
typeParameters: TypeParameterDeclaration[],
105-
isExported: boolean,
106-
): void {
89+
private addGenericTypeAlias(name: string, typeParameters: TypeParameterDeclaration[]): void {
10790
// Create type parameters for the type alias
10891
const typeParamDeclarations = typeParameters.map((typeParam) => {
10992
const paramName = typeParam.getName()
@@ -152,7 +135,7 @@ export class InterfaceParser extends BaseParser {
152135
)
153136

154137
this.newSourceFile.addTypeAlias({
155-
isExported,
138+
isExported: true,
156139
name,
157140
typeParameters: typeParamDeclarations.map((tp) =>
158141
this.printer.printNode(ts.EmitHint.Unspecified, tp, this.newSourceFile.compilerNode),

src/parsers/parse-type-aliases.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,12 @@ import { ts, TypeAliasDeclaration, VariableDeclarationKind } from 'ts-morph'
66

77
export class TypeAliasParser extends BaseParser {
88
parse(typeAlias: TypeAliasDeclaration): void {
9-
this.parseWithImportFlag(typeAlias, false)
9+
this.parseWithImportFlag(typeAlias)
1010
}
1111

12-
parseWithImportFlag(typeAlias: TypeAliasDeclaration, isImported: boolean): void {
12+
parseWithImportFlag(typeAlias: TypeAliasDeclaration): void {
1313
const typeName = typeAlias.getName()
1414

15-
if (this.processedTypes.has(typeName)) {
16-
return
17-
}
18-
19-
this.processedTypes.add(typeName)
20-
2115
const typeNode = typeAlias.getTypeNode()
2216
const typeboxTypeNode = typeNode ? getTypeBoxType(typeNode) : makeTypeCall('Any')
2317
const typeboxType = this.printer.printNode(
@@ -26,10 +20,8 @@ export class TypeAliasParser extends BaseParser {
2620
this.newSourceFile.compilerNode,
2721
)
2822

29-
const isExported = this.getIsExported(typeAlias, isImported)
30-
3123
this.newSourceFile.addVariableStatement({
32-
isExported,
24+
isExported: true,
3325
declarationKind: VariableDeclarationKind.Const,
3426
declarations: [
3527
{
@@ -39,12 +31,6 @@ export class TypeAliasParser extends BaseParser {
3931
],
4032
})
4133

42-
addStaticTypeAlias(
43-
this.newSourceFile,
44-
typeName,
45-
this.newSourceFile.compilerNode,
46-
this.printer,
47-
isExported,
48-
)
34+
addStaticTypeAlias(this.newSourceFile, typeName, this.newSourceFile.compilerNode, this.printer)
4935
}
5036
}

0 commit comments

Comments
 (0)