@@ -2,14 +2,20 @@ import { FileGraph } from '@daxserver/validation-schema-codegen/traverse/file-gr
22import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph'
33import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types'
44import { generateQualifiedNodeName } from '@daxserver/validation-schema-codegen/utils/generate-qualified-name'
5- import { topologicalSort } from 'graphology-dag'
65import {
6+ GraphVisualizer ,
7+ type VisualizationOptions ,
8+ } from '@daxserver/validation-schema-codegen/utils/graph-visualizer'
9+ import { hasCycle , topologicalSort } from 'graphology-dag'
10+ import {
11+ EnumDeclaration ,
12+ FunctionDeclaration ,
713 ImportDeclaration ,
814 InterfaceDeclaration ,
915 Node ,
1016 SourceFile ,
17+ SyntaxKind ,
1118 TypeAliasDeclaration ,
12- TypeReferenceNode ,
1319} from 'ts-morph'
1420
1521/**
@@ -113,32 +119,29 @@ export class DependencyTraversal {
113119 * Extract dependencies for all nodes in the graph
114120 */
115121 extractDependencies ( ) : void {
116- // Extract dependencies for all nodes in the graph
117122 for ( const nodeId of this . nodeGraph . nodes ( ) ) {
118123 const nodeData = this . nodeGraph . getNode ( nodeId )
119124
125+ let nodeToAnalyze : Node | undefined
126+
120127 if ( nodeData . type === 'typeAlias' ) {
121128 const typeAlias = nodeData . node as TypeAliasDeclaration
122- const typeNode = typeAlias . getTypeNode ( )
123- if ( ! typeNode ) continue
129+ nodeToAnalyze = typeAlias . getTypeNode ( )
130+ } else if ( nodeData . type === 'interface' ) {
131+ nodeToAnalyze = nodeData . node as InterfaceDeclaration
132+ } else if ( nodeData . type === 'enum' ) {
133+ nodeToAnalyze = nodeData . node as EnumDeclaration
134+ } else if ( nodeData . type === 'function' ) {
135+ nodeToAnalyze = nodeData . node as FunctionDeclaration
136+ }
124137
125- const typeReferences = this . extractTypeReferences ( typeNode )
138+ if ( ! nodeToAnalyze ) continue
126139
127- // Add edges for dependencies
128- for ( const referencedType of typeReferences ) {
129- if ( this . nodeGraph . hasNode ( referencedType ) ) {
130- this . nodeGraph . addDependency ( referencedType , nodeId )
131- }
132- }
133- } else if ( nodeData . type === 'interface' ) {
134- const interfaceDecl = nodeData . node as InterfaceDeclaration
135- const typeReferences = this . extractTypeReferences ( interfaceDecl )
140+ const typeReferences = this . extractTypeReferences ( nodeToAnalyze )
136141
137- // Add edges for dependencies
138- for ( const referencedType of typeReferences ) {
139- if ( this . nodeGraph . hasNode ( referencedType ) ) {
140- this . nodeGraph . addDependency ( referencedType , nodeId )
141- }
142+ for ( const referencedType of typeReferences ) {
143+ if ( this . nodeGraph . hasNode ( referencedType ) ) {
144+ this . nodeGraph . addDependency ( referencedType , nodeId )
142145 }
143146 }
144147 }
@@ -229,21 +232,29 @@ export class DependencyTraversal {
229232 }
230233
231234 /**
232- * Get nodes in dependency order (dependencies first)
233- * Retrieved from the graph, not from SourceFile
235+ * Get nodes in dependency order from graph
236+ * Handles circular dependencies gracefully by falling back to simple node order
234237 */
235238 getNodesToPrint ( ) : TraversedNode [ ] {
236- try {
237- // Use topological sort to ensure dependencies are printed first
238- const sortedNodeIds = topologicalSort ( this . nodeGraph )
239- return sortedNodeIds . map ( ( nodeId : string ) => this . nodeGraph . getNodeAttributes ( nodeId ) )
240- } catch {
241- // Handle circular dependencies by returning nodes in insertion order
242- // This ensures dependencies are still processed before dependents when possible
243- return Array . from ( this . nodeGraph . nodes ( ) ) . map ( ( nodeId : string ) =>
244- this . nodeGraph . getNodeAttributes ( nodeId ) ,
245- )
246- }
239+ const nodes = hasCycle ( this . nodeGraph )
240+ ? Array . from ( this . nodeGraph . nodes ( ) )
241+ : topologicalSort ( this . nodeGraph )
242+
243+ return nodes . map ( ( nodeId : string ) => this . nodeGraph . getNode ( nodeId ) )
244+ }
245+
246+ /**
247+ * Generate HTML visualization of the dependency graph
248+ */
249+ async visualizeGraph ( options : VisualizationOptions = { } ) : Promise < string > {
250+ return GraphVisualizer . generateVisualization ( this . nodeGraph , options )
251+ }
252+
253+ /**
254+ * Get the node graph for debugging purposes
255+ */
256+ getNodeGraph ( ) : NodeGraph {
257+ return this . nodeGraph
247258 }
248259
249260 private extractTypeReferences ( node : Node ) : string [ ] {
@@ -255,8 +266,7 @@ export class DependencyTraversal {
255266 visited . add ( node )
256267
257268 if ( Node . isTypeReference ( node ) ) {
258- const typeRefNode = node as TypeReferenceNode
259- const typeName = typeRefNode . getTypeName ( ) . getText ( )
269+ const typeName = node . getTypeName ( ) . getText ( )
260270
261271 for ( const qualifiedName of this . nodeGraph . nodes ( ) ) {
262272 const nodeData = this . nodeGraph . getNode ( qualifiedName )
@@ -265,8 +275,44 @@ export class DependencyTraversal {
265275 break
266276 }
267277 }
278+ }
268279
269- return
280+ // Handle typeof expressions (TypeQuery nodes)
281+ if ( Node . isTypeQuery ( node ) ) {
282+ const exprName = node . getExprName ( )
283+
284+ if ( Node . isIdentifier ( exprName ) || Node . isQualifiedName ( exprName ) ) {
285+ const typeName = exprName . getText ( )
286+
287+ for ( const qualifiedName of this . nodeGraph . nodes ( ) ) {
288+ const nodeData = this . nodeGraph . getNode ( qualifiedName )
289+ if ( nodeData . originalName === typeName ) {
290+ references . push ( qualifiedName )
291+ break
292+ }
293+ }
294+ }
295+ }
296+
297+ // Handle interface inheritance (extends clauses)
298+ if ( Node . isInterfaceDeclaration ( node ) ) {
299+ const heritageClauses = node . getHeritageClauses ( )
300+
301+ for ( const heritageClause of heritageClauses ) {
302+ if ( heritageClause . getToken ( ) !== SyntaxKind . ExtendsKeyword ) continue
303+
304+ for ( const typeNode of heritageClause . getTypeNodes ( ) ) {
305+ const typeName = typeNode . getText ( )
306+
307+ for ( const qualifiedName of this . nodeGraph . nodes ( ) ) {
308+ const nodeData = this . nodeGraph . getNode ( qualifiedName )
309+ if ( nodeData . originalName === typeName ) {
310+ references . push ( qualifiedName )
311+ break
312+ }
313+ }
314+ }
315+ }
270316 }
271317
272318 node . forEachChild ( traverse )
0 commit comments