Module Graph
Source: src/engine/module-graph.ts
What
Builds a directed dependency graph of NestJS @Module() classes and their relationships.
Why
Project-scoped rules need to understand module relationships. Circular dependency detection, unused export analysis, orphan module identification, and cross-module boundary violations all require a graph representation of the module system.
Input
project: Project // ts-morph AST project
files: string[] // file paths to scan
Output
interface ModuleGraph {
modules: Map<string, ModuleNode> // module name → node
edges: Map<string, Set<string>> // module → set of imported modules
providerToModule: Map<string, ModuleNode> // provider name → owning module
}
interface ModuleNode {
name: string // class name
filePath: string
classDeclaration: ClassDeclaration
imports: string[] // from @Module({ imports: [...] })
exports: string[] // from @Module({ exports: [...] })
providers: string[] // from @Module({ providers: [...] })
controllers: string[] // from @Module({ controllers: [...] })
}
How It Works
Two-Pass Algorithm
Pass 1 — Module Collection:
Scans every file for classes decorated with @Module(). For each module:
- Extracts the decorator argument (the metadata object)
- Parses
imports,exports,providers, andcontrollersarrays - Creates a
ModuleNode
Pass 2 — Edge Building:
For each module's imports array, creates directed edges in the graph. Also builds the providerToModule reverse index mapping each provider name back to its containing module.
Special Syntax Handling
The parser handles NestJS-specific patterns:
forwardRef(() => SomeModule)— extractsSomeModulefrom the arrow function body- Spread syntax
...someArray— extracts the variable name - Direct class references —
SomeModuleis used as-is
Circular Dependency Detection
findCircularDeps() uses DFS (depth-first search) with a recursion stack to detect cycles:
function findCircularDeps(graph: ModuleGraph): string[][] {
// Returns array of cycles, each cycle is an array of module names
// e.g., [["ModuleA", "ModuleB", "ModuleA"]]
}
Helper Functions
getModuleByClassName(graph, name)— find a module by its class namefindProviderModule(graph, providerName)— find which module owns a providertraceProviderEdges(fromModule, toModule, providers, providerToModule, project, files)— find which providers/controllers in one module depend on providers in another module. ReturnsProviderEdge[]with{ consumer, dependency }. Used byno-circular-module-depsto generate concrete fix suggestions.
Debugging Tips
- If a module does not appear in the graph, verify that it has the
@Module()decorator and is in a file included by the file collector. - Circular dependency detection works on the
importsgraph. Additionally,traceProviderEdgestraces provider-level dependencies across module boundaries, enabling concrete suggestions about which providers to extract to break a cycle. - The
providerToModuleindex is built from@Module({ providers: [...] }). If a provider is not listed in any module's providers array, it will not appear in this index.