@nexus-ioc/graph-analyzer 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +63 -26
- package/dist/analyzer/circular-dependency-detector.d.ts +29 -9
- package/dist/analyzer/circular-dependency-detector.js +106 -63
- package/dist/analyzer/circular-dependency-detector.js.map +1 -1
- package/dist/analyzer/module-depth-analyzer.d.ts +10 -13
- package/dist/analyzer/module-depth-analyzer.js +40 -52
- package/dist/analyzer/module-depth-analyzer.js.map +1 -1
- package/dist/analyzer/provider-scope-analyzer.d.ts +7 -44
- package/dist/analyzer/provider-scope-analyzer.js +13 -84
- package/dist/analyzer/provider-scope-analyzer.js.map +1 -1
- package/dist/analyzer/unused-provider-detector.d.ts +11 -9
- package/dist/analyzer/unused-provider-detector.js +35 -55
- package/dist/analyzer/unused-provider-detector.js.map +1 -1
- package/dist/cli.js +34 -129
- package/dist/cli.js.map +1 -1
- package/dist/config/config-loader.js +3 -27
- package/dist/config/config-loader.js.map +1 -1
- package/dist/config/config-schema.d.ts +2 -16
- package/dist/config/config-schema.js +0 -18
- package/dist/config/config-schema.js.map +1 -1
- package/dist/graph/build-nexus-graph-model.d.ts +13 -0
- package/dist/graph/build-nexus-graph-model.js +202 -0
- package/dist/graph/build-nexus-graph-model.js.map +1 -0
- package/dist/graph/nexus-graph-model.d.ts +112 -0
- package/dist/graph/nexus-graph-model.js +10 -0
- package/dist/graph/nexus-graph-model.js.map +1 -0
- package/dist/graph/scope-resolution.d.ts +16 -0
- package/dist/graph/scope-resolution.js +46 -0
- package/dist/graph/scope-resolution.js.map +1 -0
- package/dist/index.d.ts +27 -29
- package/dist/index.js +34 -34
- package/dist/index.js.map +1 -1
- package/dist/interfaces/graph-output.interface.d.ts +49 -14
- package/dist/utils/error-formatter.js +1 -6
- package/dist/utils/error-formatter.js.map +1 -1
- package/dist/utils/validator.d.ts +0 -5
- package/dist/utils/validator.js +0 -60
- package/dist/utils/validator.js.map +1 -1
- package/dist/visualize/json-formatter.d.ts +5 -6
- package/dist/visualize/json-formatter.js +49 -47
- package/dist/visualize/json-formatter.js.map +1 -1
- package/package.json +5 -4
- package/dist/interfaces/providers.interface.d.ts +0 -10
- package/dist/interfaces/providers.interface.js +0 -3
- package/dist/interfaces/providers.interface.js.map +0 -1
- package/dist/parser/dependency-extractor.d.ts +0 -88
- package/dist/parser/dependency-extractor.js +0 -334
- package/dist/parser/dependency-extractor.js.map +0 -1
- package/dist/parser/parse-entry-file.d.ts +0 -15
- package/dist/parser/parse-entry-file.js +0 -46
- package/dist/parser/parse-entry-file.js.map +0 -1
- package/dist/parser/parse-imports.d.ts +0 -15
- package/dist/parser/parse-imports.js +0 -65
- package/dist/parser/parse-imports.js.map +0 -1
- package/dist/parser/parse-ns-module.d.ts +0 -39
- package/dist/parser/parse-ns-module.js +0 -122
- package/dist/parser/parse-ns-module.js.map +0 -1
- package/dist/parser/parse-provider.d.ts +0 -2
- package/dist/parser/parse-provider.js +0 -7
- package/dist/parser/parse-provider.js.map +0 -1
- package/dist/parser/parse-ts-config.d.ts +0 -10
- package/dist/parser/parse-ts-config.js +0 -40
- package/dist/parser/parse-ts-config.js.map +0 -1
- package/dist/parser/parser-rec.d.ts +0 -1
- package/dist/parser/parser-rec.js +0 -52
- package/dist/parser/parser-rec.js.map +0 -1
- package/dist/parser/parser.d.ts +0 -1
- package/dist/parser/parser.js +0 -61
- package/dist/parser/parser.js.map +0 -1
- package/dist/parser/provider/class-parser.d.ts +0 -20
- package/dist/parser/provider/class-parser.js +0 -137
- package/dist/parser/provider/class-parser.js.map +0 -1
- package/dist/parser/provider/providers-parser.d.ts +0 -9
- package/dist/parser/provider/providers-parser.js +0 -32
- package/dist/parser/provider/providers-parser.js.map +0 -1
- package/dist/parser/provider/use-class-parser.d.ts +0 -25
- package/dist/parser/provider/use-class-parser.js +0 -161
- package/dist/parser/provider/use-class-parser.js.map +0 -1
- package/dist/parser/provider/use-factory-parser.d.ts +0 -15
- package/dist/parser/provider/use-factory-parser.js +0 -53
- package/dist/parser/provider/use-factory-parser.js.map +0 -1
- package/dist/parser/provider/use-value-parser.d.ts +0 -13
- package/dist/parser/provider/use-value-parser.js +0 -41
- package/dist/parser/provider/use-value-parser.js.map +0 -1
- package/dist/parser/rules.d.ts +0 -10
- package/dist/parser/rules.js +0 -37
- package/dist/parser/rules.js.map +0 -1
- package/dist/visualize/generator.d.ts +0 -26
- package/dist/visualize/generator.js +0 -75
- package/dist/visualize/generator.js.map +0 -1
- package/dist/visualize/graph-analyzer.d.ts +0 -126
- package/dist/visualize/graph-analyzer.js +0 -199
- package/dist/visualize/graph-analyzer.js.map +0 -1
- package/dist/visualize/html-generator.d.ts +0 -71
- package/dist/visualize/html-generator.js +0 -1312
- package/dist/visualize/html-generator.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to the @nexus-ioc/graph-analyzer package will be documented
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 0.2.0 (2026-10-12)
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
- `scopeAnalysis` JSON shape: Transient providers were previously mislabelled `"Singleton"` in `providerScopes` and are now labelled `"Transient"`. `scopedProviders` is renamed to `transientProviders`. `scopeMismatches` and the `ScopeMismatch` export are removed, because `Scope.Scoped` no longer exists in `@nexus-ioc/core`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Child scopes: the graph follows `@Module({ children })`. JSON modules gain `scope` (the module that roots the child scope) and `children`, and a provider dependency gets `crossScope: true` when a child-scope provider gets it from an ancestor scope (approximate).
|
|
17
|
+
|
|
8
18
|
## [0.1.0] - 2025-01-18
|
|
9
19
|
|
|
10
20
|
**First public release of @nexus-ioc/graph-analyzer!**
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](./src)
|
|
9
9
|
[](https://nodejs.org/)
|
|
10
10
|
|
|
11
|
-
Analyze and visualize dependency injection graphs in [Nexus IoC](https://github.com/Isqanderm/ioc) applications. Extract complete metadata about modules, providers, and dependencies **without running your application**. Detect circular dependencies, unused providers,
|
|
11
|
+
Analyze and visualize dependency injection graphs in [Nexus IoC](https://github.com/Isqanderm/ioc) applications. Extract complete metadata about modules, providers, and dependencies **without running your application**. Detect circular dependencies, unused providers, provider scopes, and visualize your architecture with interactive HTML graphs.
|
|
12
12
|
|
|
13
13
|
## ✨ Features
|
|
14
14
|
|
|
@@ -24,7 +24,7 @@ Analyze and visualize dependency injection graphs in [Nexus IoC](https://github.
|
|
|
24
24
|
- 🔄 **Circular Dependency Detection** - Detect circular imports and provider dependencies
|
|
25
25
|
- 🗑️ **Unused Provider Detection** - Find providers that are registered but never injected
|
|
26
26
|
- 📊 **Module Depth Analysis** - Analyze module hierarchy depth and complexity metrics
|
|
27
|
-
- 🎯 **Provider Scope Analysis** -
|
|
27
|
+
- 🎯 **Provider Scope Analysis** - Report each provider's scope (Singleton or Transient)
|
|
28
28
|
- ⚠️ **Missing Decorator Detection** - Identify dependencies without explicit `@Inject` decorators
|
|
29
29
|
|
|
30
30
|
### Visualization & Output
|
|
@@ -141,7 +141,7 @@ graph-analyzer [options] <entry-file>
|
|
|
141
141
|
| `--check-circular` | Detect circular dependencies in modules and providers | `false` |
|
|
142
142
|
| `--check-unused` | Detect providers that are registered but never injected | `false` |
|
|
143
143
|
| `--check-depth` | Analyze module hierarchy depth and complexity | `false` |
|
|
144
|
-
| `--check-scope` |
|
|
144
|
+
| `--check-scope` | Report provider scopes (Singleton or Transient) | `false` |
|
|
145
145
|
| `--check-ci` | Enable all checks (circular, unused, depth, scope) | `false` |
|
|
146
146
|
| `--deep-module-threshold <n>` | Threshold for identifying deep modules | `5` |
|
|
147
147
|
|
|
@@ -177,7 +177,7 @@ npx graph-analyzer --check-unused -f html src/main.ts
|
|
|
177
177
|
# Analyze module depth and complexity
|
|
178
178
|
npx graph-analyzer --check-depth --deep-module-threshold 3 src/main.ts
|
|
179
179
|
|
|
180
|
-
#
|
|
180
|
+
# Report provider scopes (Singleton or Transient)
|
|
181
181
|
npx graph-analyzer --check-scope src/main.ts
|
|
182
182
|
|
|
183
183
|
# HTML with dark theme and WebStorm links
|
|
@@ -302,41 +302,77 @@ npx graph-analyzer --check-depth --deep-module-threshold 5 src/main.ts
|
|
|
302
302
|
|
|
303
303
|
### Provider Scope Analysis
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
Reports the scope of every provider. `Scope.Scoped` was removed from `@nexus-ioc/core`, so only `Singleton` and `Transient` exist and there are no scope mismatches to detect.
|
|
306
306
|
|
|
307
307
|
```bash
|
|
308
308
|
npx graph-analyzer --check-scope src/main.ts
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
-
**Detects:**
|
|
312
|
-
- Singleton providers depending on Request-scoped providers (memory leak risk)
|
|
313
|
-
- Scope inconsistencies that can cause unexpected behavior
|
|
314
|
-
- Provides suggestions for fixing scope issues
|
|
315
|
-
|
|
316
311
|
**Example Output:**
|
|
317
312
|
```json
|
|
318
313
|
{
|
|
319
314
|
"analysis": {
|
|
320
315
|
"scopeAnalysis": {
|
|
321
|
-
"
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
"severity": "error",
|
|
328
|
-
"message": "Singleton provider 'UserService' depends on Request-scoped provider 'RequestContext'",
|
|
329
|
-
"suggestions": [
|
|
330
|
-
"Change 'UserService' to Request scope",
|
|
331
|
-
"Change 'RequestContext' to Singleton scope if it doesn't need request-specific state"
|
|
332
|
-
]
|
|
333
|
-
}
|
|
316
|
+
"totalProviders": 2,
|
|
317
|
+
"singletonProviders": 1,
|
|
318
|
+
"transientProviders": 1,
|
|
319
|
+
"providerScopes": [
|
|
320
|
+
{ "token": "UserService", "module": "AppModule", "scope": "Singleton", "type": "Class", "dependencies": ["RequestContext"] },
|
|
321
|
+
{ "token": "RequestContext", "module": "AppModule", "scope": "Transient", "type": "Class", "dependencies": [] }
|
|
334
322
|
]
|
|
335
323
|
}
|
|
336
324
|
}
|
|
337
325
|
}
|
|
338
326
|
```
|
|
339
327
|
|
|
328
|
+
**Migration from earlier output:**
|
|
329
|
+
- Transient providers were previously labelled `"Singleton"` in `providerScopes`; they are now labelled `"Transient"`.
|
|
330
|
+
- `scopedProviders` is renamed to `transientProviders`.
|
|
331
|
+
- `scopeMismatches` and the `ScopeMismatch` export are removed.
|
|
332
|
+
|
|
333
|
+
### Child Scopes
|
|
334
|
+
|
|
335
|
+
Modules listed in `@Module({ children })` (direct class references and the `lazy(() => import("...").then((m) => m.X))` shape) are not imports: each one opens a child container scope that is created at runtime with `createChild()`. The analyzer follows them, resolves each scope's dependencies with the declaring module's visibility as the fallback, and reports the scopes in the output. Applications without `children` are unchanged.
|
|
336
|
+
|
|
337
|
+
**Example Output:**
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"modules": [
|
|
341
|
+
{
|
|
342
|
+
"name": "AppModule",
|
|
343
|
+
"path": "/path/to/app.module.ts",
|
|
344
|
+
"imports": [],
|
|
345
|
+
"exports": [],
|
|
346
|
+
"providers": ["EventBus"],
|
|
347
|
+
"isGlobal": false,
|
|
348
|
+
"scope": "AppModule",
|
|
349
|
+
"children": [{ "name": "ProfileModule", "path": "/path/to/profile.module.ts" }]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"name": "ProfileModule",
|
|
353
|
+
"path": "/path/to/profile.module.ts",
|
|
354
|
+
"imports": [],
|
|
355
|
+
"exports": [],
|
|
356
|
+
"providers": ["ProfileStore"],
|
|
357
|
+
"isGlobal": false,
|
|
358
|
+
"scope": "ProfileModule"
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
"providers": [
|
|
362
|
+
{
|
|
363
|
+
"token": "ProfileStore",
|
|
364
|
+
"type": "Class",
|
|
365
|
+
"module": { "name": "ProfileModule", "path": "/path/to/profile.module.ts" },
|
|
366
|
+
"dependencies": [{ "token": "EventBus", "optional": false, "crossScope": true }]
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
- `scope` on a module is the name of the module that roots its scope (a module present in several scopes is tagged with the first one). `children` lists the modules it declares as children.
|
|
373
|
+
- `crossScope: true` on a dependency means a provider in a child scope gets that token from an ancestor scope. It is approximate: it only checks that some module of an ancestor scope registers the token, not the imports/exports visibility rules. `@nexus-ioc/type-checker` reports the visibility-correct answer as `fromParentScope`.
|
|
374
|
+
- Circular dependency detection is scope-aware: a child-scope provider that shadows a parent token (the parent's `A -> B` plus a child's `B -> A`) is not reported as a cycle, while a real cycle inside a child scope still is.
|
|
375
|
+
|
|
340
376
|
### Missing Decorator Detection
|
|
341
377
|
|
|
342
378
|
Identifies dependencies that lack explicit `@Inject` decorators.
|
|
@@ -403,7 +439,7 @@ The JSON output provides a structured representation of your dependency graph wi
|
|
|
403
439
|
"circularDependencies": [],
|
|
404
440
|
"unusedProviders": [],
|
|
405
441
|
"depthAnalysis": { "maxDepth": 3, "averageDepth": 2.1 },
|
|
406
|
-
"scopeAnalysis": { "
|
|
442
|
+
"scopeAnalysis": { "totalProviders": 0, "singletonProviders": 0, "transientProviders": 0, "providerScopes": [] }
|
|
407
443
|
},
|
|
408
444
|
"metadata": {
|
|
409
445
|
"entryPoint": "/path/to/main.ts",
|
|
@@ -493,7 +529,7 @@ class UserService {
|
|
|
493
529
|
|
|
494
530
|
✅ **Different Provider Types**
|
|
495
531
|
```typescript
|
|
496
|
-
@
|
|
532
|
+
@Module({
|
|
497
533
|
providers: [
|
|
498
534
|
UserService, // Class
|
|
499
535
|
{ provide: 'CONFIG', useValue: config }, // UseValue
|
|
@@ -505,11 +541,12 @@ class UserService {
|
|
|
505
541
|
|
|
506
542
|
### Token Types
|
|
507
543
|
|
|
508
|
-
The analyzer detects
|
|
544
|
+
The analyzer detects four types of injection tokens:
|
|
509
545
|
|
|
510
546
|
- **Class tokens**: `@Inject(UserService)`
|
|
511
547
|
- **String tokens**: `@Inject('CONFIG')`
|
|
512
548
|
- **Symbol tokens**: `@Inject(Symbol.for('key'))`
|
|
549
|
+
- **InjectionToken constants**: `@Inject(CONFIG)` where `const CONFIG = new InjectionToken<Config>('CONFIG')`. The constant is one token across re-exports, and graph labels show the const name.
|
|
513
550
|
|
|
514
551
|
## 🏗️ Architecture
|
|
515
552
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ParseNsModule } from "../parser/parse-ns-module";
|
|
1
|
+
import type { NexusGraphModel } from "../graph/nexus-graph-model";
|
|
3
2
|
/**
|
|
4
3
|
* Represents a circular dependency cycle
|
|
5
4
|
*/
|
|
@@ -30,11 +29,14 @@ export interface CircularDependencyAnalysis {
|
|
|
30
29
|
* Detects circular dependencies in module imports and provider dependencies
|
|
31
30
|
*
|
|
32
31
|
* Uses depth-first search (DFS) to detect cycles in the dependency graph.
|
|
33
|
-
* Can detect both module-level circular imports and provider-level circular
|
|
32
|
+
* Can detect both module-level circular imports and provider-level circular
|
|
33
|
+
* dependencies — the latter covering both constructor-injected dependencies
|
|
34
|
+
* and `useFactory` `inject` tokens, since `NexusGraphModel` merges both into
|
|
35
|
+
* a single provider dependency list.
|
|
34
36
|
*
|
|
35
37
|
* @example
|
|
36
38
|
* ```typescript
|
|
37
|
-
* const detector = new CircularDependencyDetector(
|
|
39
|
+
* const detector = new CircularDependencyDetector(graphModel);
|
|
38
40
|
* const analysis = detector.analyze();
|
|
39
41
|
*
|
|
40
42
|
* if (analysis.hasCircularDependencies) {
|
|
@@ -46,13 +48,15 @@ export interface CircularDependencyAnalysis {
|
|
|
46
48
|
* ```
|
|
47
49
|
*/
|
|
48
50
|
export declare class CircularDependencyDetector {
|
|
49
|
-
private readonly
|
|
51
|
+
private readonly graphModel;
|
|
52
|
+
/** `provider.id -> provider.token`, populated by `buildProviderDependencyMap()`. */
|
|
53
|
+
private readonly providerLabels;
|
|
50
54
|
/**
|
|
51
55
|
* Create a new CircularDependencyDetector instance
|
|
52
56
|
*
|
|
53
|
-
* @param
|
|
57
|
+
* @param graphModel - The application's graph model
|
|
54
58
|
*/
|
|
55
|
-
constructor(
|
|
59
|
+
constructor(graphModel: NexusGraphModel);
|
|
56
60
|
/**
|
|
57
61
|
* Analyze the dependency graph for circular dependencies
|
|
58
62
|
*
|
|
@@ -71,9 +75,12 @@ export declare class CircularDependencyDetector {
|
|
|
71
75
|
*/
|
|
72
76
|
private detectModuleCircularDependencies;
|
|
73
77
|
/**
|
|
74
|
-
* Depth-first search for module circular dependencies
|
|
78
|
+
* Depth-first search for module circular dependencies. Traverses by
|
|
79
|
+
* `GraphModuleNode.id` (collision-free); `cyclePath`/`path` are built
|
|
80
|
+
* from ids and translated to display names only when a cycle is reported.
|
|
75
81
|
*/
|
|
76
82
|
private dfsModules;
|
|
83
|
+
private moduleLabel;
|
|
77
84
|
/**
|
|
78
85
|
* Detect circular dependencies in provider dependencies
|
|
79
86
|
*
|
|
@@ -84,9 +91,22 @@ export declare class CircularDependencyDetector {
|
|
|
84
91
|
*/
|
|
85
92
|
private detectProviderCircularDependencies;
|
|
86
93
|
/**
|
|
87
|
-
* Build a map of provider
|
|
94
|
+
* Build a map of provider ids to their dependency ids and a map of
|
|
95
|
+
* provider ids to display tokens, across every module in the graph.
|
|
96
|
+
* Keyed by `GraphProviderNode.id`/`GraphProviderDependency.tokenId`
|
|
97
|
+
* (collision-free), not the rendered `token` — two different provider
|
|
98
|
+
* classes named alike must not collapse into one map entry.
|
|
88
99
|
*/
|
|
89
100
|
private buildProviderDependencyMap;
|
|
101
|
+
/**
|
|
102
|
+
* With child scopes, every scope has its own provider instances: a node
|
|
103
|
+
* is `(scope, provider id)`, and an edge goes to the nearest scope (own
|
|
104
|
+
* first, then ancestors) that registers the dependency. This keeps a
|
|
105
|
+
* child provider that overrides a parent token from being merged with
|
|
106
|
+
* the parent's provider into a false cycle.
|
|
107
|
+
*/
|
|
108
|
+
private buildScopedProviderDependencyMap;
|
|
109
|
+
private providerLabel;
|
|
90
110
|
/**
|
|
91
111
|
* Depth-first search for provider circular dependencies
|
|
92
112
|
*/
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CircularDependencyDetector = void 0;
|
|
4
|
+
const scope_resolution_1 = require("../graph/scope-resolution");
|
|
4
5
|
/**
|
|
5
6
|
* Detects circular dependencies in module imports and provider dependencies
|
|
6
7
|
*
|
|
7
8
|
* Uses depth-first search (DFS) to detect cycles in the dependency graph.
|
|
8
|
-
* Can detect both module-level circular imports and provider-level circular
|
|
9
|
+
* Can detect both module-level circular imports and provider-level circular
|
|
10
|
+
* dependencies — the latter covering both constructor-injected dependencies
|
|
11
|
+
* and `useFactory` `inject` tokens, since `NexusGraphModel` merges both into
|
|
12
|
+
* a single provider dependency list.
|
|
9
13
|
*
|
|
10
14
|
* @example
|
|
11
15
|
* ```typescript
|
|
12
|
-
* const detector = new CircularDependencyDetector(
|
|
16
|
+
* const detector = new CircularDependencyDetector(graphModel);
|
|
13
17
|
* const analysis = detector.analyze();
|
|
14
18
|
*
|
|
15
19
|
* if (analysis.hasCircularDependencies) {
|
|
@@ -24,10 +28,12 @@ class CircularDependencyDetector {
|
|
|
24
28
|
/**
|
|
25
29
|
* Create a new CircularDependencyDetector instance
|
|
26
30
|
*
|
|
27
|
-
* @param
|
|
31
|
+
* @param graphModel - The application's graph model
|
|
28
32
|
*/
|
|
29
|
-
constructor(
|
|
30
|
-
this.
|
|
33
|
+
constructor(graphModel) {
|
|
34
|
+
this.graphModel = graphModel;
|
|
35
|
+
/** `provider.id -> provider.token`, populated by `buildProviderDependencyMap()`. */
|
|
36
|
+
this.providerLabels = new Map();
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Analyze the dependency graph for circular dependencies
|
|
@@ -64,52 +70,59 @@ class CircularDependencyDetector {
|
|
|
64
70
|
const visited = new Set();
|
|
65
71
|
const recursionStack = new Set();
|
|
66
72
|
const path = [];
|
|
67
|
-
const
|
|
68
|
-
if (!
|
|
73
|
+
const entryModuleId = this.graphModel.entryModuleId;
|
|
74
|
+
if (!entryModuleId) {
|
|
69
75
|
return cycles;
|
|
70
76
|
}
|
|
71
77
|
// Start DFS from entry module
|
|
72
|
-
this.dfsModules(
|
|
78
|
+
this.dfsModules(entryModuleId, visited, recursionStack, path, cycles);
|
|
73
79
|
return cycles;
|
|
74
80
|
}
|
|
75
81
|
/**
|
|
76
|
-
* Depth-first search for module circular dependencies
|
|
82
|
+
* Depth-first search for module circular dependencies. Traverses by
|
|
83
|
+
* `GraphModuleNode.id` (collision-free); `cyclePath`/`path` are built
|
|
84
|
+
* from ids and translated to display names only when a cycle is reported.
|
|
77
85
|
*/
|
|
78
|
-
dfsModules(
|
|
86
|
+
dfsModules(moduleId, visited, recursionStack, path, cycles) {
|
|
79
87
|
// Mark current node as visited and add to recursion stack
|
|
80
|
-
visited.add(
|
|
81
|
-
recursionStack.add(
|
|
82
|
-
path.push(
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
88
|
+
visited.add(moduleId);
|
|
89
|
+
recursionStack.add(moduleId);
|
|
90
|
+
path.push(moduleId);
|
|
91
|
+
const module = this.graphModel.modules.get(moduleId);
|
|
92
|
+
if (!module) {
|
|
85
93
|
// Clean up and return
|
|
86
|
-
recursionStack.delete(
|
|
94
|
+
recursionStack.delete(moduleId);
|
|
87
95
|
path.pop();
|
|
88
96
|
return;
|
|
89
97
|
}
|
|
90
98
|
// Visit all imported modules
|
|
91
|
-
for (const importedModule of
|
|
92
|
-
|
|
99
|
+
for (const importedModule of module.imports) {
|
|
100
|
+
const importedId = importedModule.id;
|
|
101
|
+
if (!visited.has(importedId)) {
|
|
93
102
|
// Recursively visit unvisited module
|
|
94
|
-
this.dfsModules(
|
|
103
|
+
this.dfsModules(importedId, visited, recursionStack, path, cycles);
|
|
95
104
|
}
|
|
96
|
-
else if (recursionStack.has(
|
|
105
|
+
else if (recursionStack.has(importedId)) {
|
|
97
106
|
// Found a cycle - extract the cycle path
|
|
98
|
-
const cycleStartIndex = path.indexOf(
|
|
107
|
+
const cycleStartIndex = path.indexOf(importedId);
|
|
99
108
|
const cyclePath = path.slice(cycleStartIndex);
|
|
100
|
-
cyclePath.push(
|
|
109
|
+
cyclePath.push(importedId); // Complete the cycle
|
|
110
|
+
const cycleLabels = cyclePath.map((id) => this.moduleLabel(id));
|
|
101
111
|
cycles.push({
|
|
102
112
|
type: "module",
|
|
103
|
-
cycle:
|
|
113
|
+
cycle: cycleLabels,
|
|
104
114
|
severity: "error",
|
|
105
|
-
message: `Circular module import detected: ${
|
|
115
|
+
message: `Circular module import detected: ${cycleLabels.join(" -> ")}`,
|
|
106
116
|
});
|
|
107
117
|
}
|
|
108
118
|
}
|
|
109
119
|
// Remove from recursion stack and path
|
|
110
|
-
recursionStack.delete(
|
|
120
|
+
recursionStack.delete(moduleId);
|
|
111
121
|
path.pop();
|
|
112
122
|
}
|
|
123
|
+
moduleLabel(moduleId) {
|
|
124
|
+
return this.graphModel.modules.get(moduleId)?.name ?? moduleId;
|
|
125
|
+
}
|
|
113
126
|
/**
|
|
114
127
|
* Detect circular dependencies in provider dependencies
|
|
115
128
|
*
|
|
@@ -126,76 +139,106 @@ class CircularDependencyDetector {
|
|
|
126
139
|
// Build provider dependency map
|
|
127
140
|
const providerDeps = this.buildProviderDependencyMap();
|
|
128
141
|
// Run DFS from each provider
|
|
129
|
-
for (const
|
|
130
|
-
if (!visited.has(
|
|
131
|
-
this.dfsProviders(
|
|
142
|
+
for (const providerId of providerDeps.keys()) {
|
|
143
|
+
if (!visited.has(providerId)) {
|
|
144
|
+
this.dfsProviders(providerId, providerDeps, visited, recursionStack, path, cycles);
|
|
132
145
|
}
|
|
133
146
|
}
|
|
134
147
|
return cycles;
|
|
135
148
|
}
|
|
136
149
|
/**
|
|
137
|
-
* Build a map of provider
|
|
150
|
+
* Build a map of provider ids to their dependency ids and a map of
|
|
151
|
+
* provider ids to display tokens, across every module in the graph.
|
|
152
|
+
* Keyed by `GraphProviderNode.id`/`GraphProviderDependency.tokenId`
|
|
153
|
+
* (collision-free), not the rendered `token` — two different provider
|
|
154
|
+
* classes named alike must not collapse into one map entry.
|
|
138
155
|
*/
|
|
139
156
|
buildProviderDependencyMap() {
|
|
157
|
+
this.providerLabels.clear();
|
|
158
|
+
if (this.graphModel.scopes) {
|
|
159
|
+
return this.buildScopedProviderDependencyMap();
|
|
160
|
+
}
|
|
140
161
|
const providerDeps = new Map();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
for (const module of this.graphModel.modules.values()) {
|
|
163
|
+
for (const provider of module.providers) {
|
|
164
|
+
const dependencies = provider.dependencies
|
|
165
|
+
.filter((dependency) => !dependency.optional && dependency.tokenId)
|
|
166
|
+
.map((dependency) => dependency.tokenId);
|
|
167
|
+
providerDeps.set(provider.id, dependencies);
|
|
168
|
+
this.providerLabels.set(provider.id, provider.token);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return providerDeps;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* With child scopes, every scope has its own provider instances: a node
|
|
175
|
+
* is `(scope, provider id)`, and an edge goes to the nearest scope (own
|
|
176
|
+
* first, then ancestors) that registers the dependency. This keeps a
|
|
177
|
+
* child provider that overrides a parent token from being merged with
|
|
178
|
+
* the parent's provider into a false cycle.
|
|
179
|
+
*/
|
|
180
|
+
buildScopedProviderDependencyMap() {
|
|
181
|
+
const providerDeps = new Map();
|
|
182
|
+
const scopeIndex = new scope_resolution_1.GraphScopeIndex(this.graphModel);
|
|
183
|
+
const key = (scopeRootId, providerId) => `${scopeRootId}\u0000${providerId}`;
|
|
184
|
+
for (const scope of this.graphModel.scopes ?? []) {
|
|
185
|
+
for (const moduleId of scope.moduleIds) {
|
|
186
|
+
const module = this.graphModel.modules.get(moduleId);
|
|
187
|
+
for (const provider of module?.providers ?? []) {
|
|
188
|
+
const dependencies = provider.dependencies
|
|
189
|
+
.filter((dependency) => !dependency.optional && dependency.tokenId)
|
|
190
|
+
.map((dependency) => {
|
|
191
|
+
const tokenId = dependency.tokenId;
|
|
192
|
+
const providingScope = scopeIndex.providingScope(scope.rootId, tokenId) ??
|
|
193
|
+
scope.rootId;
|
|
194
|
+
return key(providingScope, tokenId);
|
|
195
|
+
});
|
|
196
|
+
const providerKey = key(scope.rootId, provider.id);
|
|
197
|
+
providerDeps.set(providerKey, dependencies);
|
|
198
|
+
this.providerLabels.set(providerKey, provider.token);
|
|
159
199
|
}
|
|
160
|
-
providerDeps.set(provider.token, dependencies);
|
|
161
200
|
}
|
|
162
201
|
}
|
|
163
202
|
return providerDeps;
|
|
164
203
|
}
|
|
204
|
+
providerLabel(providerId) {
|
|
205
|
+
return this.providerLabels.get(providerId) ?? providerId;
|
|
206
|
+
}
|
|
165
207
|
/**
|
|
166
208
|
* Depth-first search for provider circular dependencies
|
|
167
209
|
*/
|
|
168
|
-
dfsProviders(
|
|
210
|
+
dfsProviders(providerId, providerDeps, visited, recursionStack, path, cycles) {
|
|
169
211
|
// Mark current node as visited and add to recursion stack
|
|
170
|
-
visited.add(
|
|
171
|
-
recursionStack.add(
|
|
172
|
-
path.push(
|
|
173
|
-
const dependencies = providerDeps.get(
|
|
212
|
+
visited.add(providerId);
|
|
213
|
+
recursionStack.add(providerId);
|
|
214
|
+
path.push(providerId);
|
|
215
|
+
const dependencies = providerDeps.get(providerId) || [];
|
|
174
216
|
// Visit all dependencies
|
|
175
|
-
for (const
|
|
176
|
-
if (!providerDeps.has(
|
|
217
|
+
for (const depId of dependencies) {
|
|
218
|
+
if (!providerDeps.has(depId)) {
|
|
177
219
|
// Dependency is not a provider (might be external), skip
|
|
178
220
|
continue;
|
|
179
221
|
}
|
|
180
|
-
if (!visited.has(
|
|
222
|
+
if (!visited.has(depId)) {
|
|
181
223
|
// Recursively visit unvisited dependency
|
|
182
|
-
this.dfsProviders(
|
|
224
|
+
this.dfsProviders(depId, providerDeps, visited, recursionStack, path, cycles);
|
|
183
225
|
}
|
|
184
|
-
else if (recursionStack.has(
|
|
226
|
+
else if (recursionStack.has(depId)) {
|
|
185
227
|
// Found a cycle - extract the cycle path
|
|
186
|
-
const cycleStartIndex = path.indexOf(
|
|
228
|
+
const cycleStartIndex = path.indexOf(depId);
|
|
187
229
|
const cyclePath = path.slice(cycleStartIndex);
|
|
188
|
-
cyclePath.push(
|
|
230
|
+
cyclePath.push(depId); // Complete the cycle
|
|
231
|
+
const cycleLabels = cyclePath.map((id) => this.providerLabel(id));
|
|
189
232
|
cycles.push({
|
|
190
233
|
type: "provider",
|
|
191
|
-
cycle:
|
|
234
|
+
cycle: cycleLabels,
|
|
192
235
|
severity: "error",
|
|
193
|
-
message: `Circular provider dependency detected: ${
|
|
236
|
+
message: `Circular provider dependency detected: ${cycleLabels.join(" -> ")}`,
|
|
194
237
|
});
|
|
195
238
|
}
|
|
196
239
|
}
|
|
197
240
|
// Remove from recursion stack and path
|
|
198
|
-
recursionStack.delete(
|
|
241
|
+
recursionStack.delete(providerId);
|
|
199
242
|
path.pop();
|
|
200
243
|
}
|
|
201
244
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"circular-dependency-detector.js","sourceRoot":"","sources":["../../src/analyzer/circular-dependency-detector.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"circular-dependency-detector.js","sourceRoot":"","sources":["../../src/analyzer/circular-dependency-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ParseNsModule } from "../parser/parse-ns-module";
|
|
1
|
+
import type { NexusGraphModel } from "../graph/nexus-graph-model";
|
|
3
2
|
/**
|
|
4
3
|
* Information about a module's position in the hierarchy
|
|
5
4
|
*/
|
|
@@ -59,7 +58,7 @@ export interface ModuleDepthAnalysis {
|
|
|
59
58
|
*
|
|
60
59
|
* @example
|
|
61
60
|
* ```typescript
|
|
62
|
-
* const analyzer = new ModuleDepthAnalyzer(
|
|
61
|
+
* const analyzer = new ModuleDepthAnalyzer(graphModel);
|
|
63
62
|
* const analysis = analyzer.analyze();
|
|
64
63
|
*
|
|
65
64
|
* console.log(`Max depth: ${analysis.maxDepth}`);
|
|
@@ -71,15 +70,15 @@ export interface ModuleDepthAnalysis {
|
|
|
71
70
|
* ```
|
|
72
71
|
*/
|
|
73
72
|
export declare class ModuleDepthAnalyzer {
|
|
74
|
-
private readonly
|
|
73
|
+
private readonly graphModel;
|
|
75
74
|
private readonly deepModuleThreshold;
|
|
76
75
|
/**
|
|
77
76
|
* Create a new ModuleDepthAnalyzer instance
|
|
78
77
|
*
|
|
79
|
-
* @param
|
|
78
|
+
* @param graphModel - The application's graph model
|
|
80
79
|
* @param deepModuleThreshold - Custom threshold for deep modules (optional)
|
|
81
80
|
*/
|
|
82
|
-
constructor(
|
|
81
|
+
constructor(graphModel: NexusGraphModel, deepModuleThreshold?: number);
|
|
83
82
|
/**
|
|
84
83
|
* Analyze the module hierarchy depth and complexity
|
|
85
84
|
*
|
|
@@ -87,19 +86,17 @@ export declare class ModuleDepthAnalyzer {
|
|
|
87
86
|
*/
|
|
88
87
|
analyze(): ModuleDepthAnalysis;
|
|
89
88
|
/**
|
|
90
|
-
*
|
|
91
|
-
*/
|
|
92
|
-
private getModules;
|
|
93
|
-
/**
|
|
94
|
-
* Calculate depth for each module using BFS from entry point
|
|
89
|
+
* Calculate depth for each module (by id) using BFS from entry point
|
|
95
90
|
*/
|
|
96
91
|
private calculateModuleDepths;
|
|
97
92
|
/**
|
|
98
|
-
* Calculate complexity metrics for each module
|
|
93
|
+
* Calculate complexity metrics for each module. `moduleDepths` is keyed
|
|
94
|
+
* by id; the returned `ModuleDepthInfo[]` reports display names, as before.
|
|
99
95
|
*/
|
|
100
96
|
private calculateComplexityMetrics;
|
|
101
97
|
/**
|
|
102
|
-
* Calculate fan-in for all modules (how many modules depend on each
|
|
98
|
+
* Calculate fan-in for all modules (how many modules depend on each
|
|
99
|
+
* module), keyed by id
|
|
103
100
|
*/
|
|
104
101
|
private calculateFanIn;
|
|
105
102
|
/**
|