driftdetect-core 0.4.6 → 0.5.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/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/csharp-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/csharp-extractor.d.ts +35 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/csharp-extractor.js +362 -4
- package/dist/call-graph/extractors/csharp-extractor.js.map +1 -1
- package/dist/call-graph/extractors/csharp-hybrid-extractor.d.ts +37 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.js +408 -0
- package/dist/call-graph/extractors/csharp-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.d.ts +102 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.d.ts.map +1 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.js +289 -0
- package/dist/call-graph/extractors/hybrid-extractor-base.js.map +1 -0
- package/dist/call-graph/extractors/index.d.ts +17 -13
- package/dist/call-graph/extractors/index.d.ts.map +1 -1
- package/dist/call-graph/extractors/index.js +24 -23
- package/dist/call-graph/extractors/index.js.map +1 -1
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/java-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/java-extractor.d.ts +15 -0
- package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/java-extractor.js +120 -4
- package/dist/call-graph/extractors/java-extractor.js.map +1 -1
- package/dist/call-graph/extractors/java-hybrid-extractor.d.ts +36 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.js +426 -0
- package/dist/call-graph/extractors/java-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/php-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/php-extractor.d.ts +48 -1
- package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/php-extractor.js +460 -6
- package/dist/call-graph/extractors/php-extractor.js.map +1 -1
- package/dist/call-graph/extractors/php-hybrid-extractor.d.ts +35 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.js +393 -0
- package/dist/call-graph/extractors/php-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/python-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/python-hybrid-extractor.d.ts +85 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.js +462 -0
- package/dist/call-graph/extractors/python-hybrid-extractor.js.map +1 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.d.ts +154 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.js +346 -0
- package/dist/call-graph/extractors/regex/base-regex-extractor.js.map +1 -0
- package/dist/call-graph/extractors/regex/csharp-regex.d.ts +34 -0
- package/dist/call-graph/extractors/regex/csharp-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/csharp-regex.js +393 -0
- package/dist/call-graph/extractors/regex/csharp-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/index.d.ts +25 -0
- package/dist/call-graph/extractors/regex/index.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/index.js +66 -0
- package/dist/call-graph/extractors/regex/index.js.map +1 -0
- package/dist/call-graph/extractors/regex/java-regex.d.ts +34 -0
- package/dist/call-graph/extractors/regex/java-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/java-regex.js +327 -0
- package/dist/call-graph/extractors/regex/java-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/php-regex.d.ts +30 -0
- package/dist/call-graph/extractors/regex/php-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/php-regex.js +333 -0
- package/dist/call-graph/extractors/regex/php-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/python-regex.d.ts +46 -0
- package/dist/call-graph/extractors/regex/python-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/python-regex.js +380 -0
- package/dist/call-graph/extractors/regex/python-regex.js.map +1 -0
- package/dist/call-graph/extractors/regex/typescript-regex.d.ts +27 -0
- package/dist/call-graph/extractors/regex/typescript-regex.d.ts.map +1 -0
- package/dist/call-graph/extractors/regex/typescript-regex.js +349 -0
- package/dist/call-graph/extractors/regex/typescript-regex.js.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +7 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -1
- package/dist/call-graph/extractors/semantic-data-access-scanner.js +7 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -1
- package/dist/call-graph/extractors/types.d.ts +111 -0
- package/dist/call-graph/extractors/types.d.ts.map +1 -0
- package/dist/call-graph/extractors/types.js +68 -0
- package/dist/call-graph/extractors/types.js.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +8 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -1
- package/dist/call-graph/extractors/typescript-data-access-extractor.js +8 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -1
- package/dist/call-graph/extractors/typescript-hybrid-extractor.d.ts +116 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.js +635 -0
- package/dist/call-graph/extractors/typescript-hybrid-extractor.js.map +1 -0
- package/dist/error-handling/error-handling-analyzer.d.ts +73 -0
- package/dist/error-handling/error-handling-analyzer.d.ts.map +1 -0
- package/dist/error-handling/error-handling-analyzer.js +706 -0
- package/dist/error-handling/error-handling-analyzer.js.map +1 -0
- package/dist/error-handling/index.d.ts +8 -0
- package/dist/error-handling/index.d.ts.map +1 -0
- package/dist/error-handling/index.js +8 -0
- package/dist/error-handling/index.js.map +1 -0
- package/dist/error-handling/types.d.ts +307 -0
- package/dist/error-handling/types.d.ts.map +1 -0
- package/dist/error-handling/types.js +7 -0
- package/dist/error-handling/types.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -1
- package/dist/lake/pattern-shard-store.d.ts +6 -0
- package/dist/lake/pattern-shard-store.d.ts.map +1 -1
- package/dist/lake/pattern-shard-store.js +6 -0
- package/dist/lake/pattern-shard-store.js.map +1 -1
- package/dist/module-coupling/coupling-analyzer.d.ts +73 -0
- package/dist/module-coupling/coupling-analyzer.d.ts.map +1 -0
- package/dist/module-coupling/coupling-analyzer.js +668 -0
- package/dist/module-coupling/coupling-analyzer.js.map +1 -0
- package/dist/module-coupling/index.d.ts +9 -0
- package/dist/module-coupling/index.d.ts.map +1 -0
- package/dist/module-coupling/index.js +9 -0
- package/dist/module-coupling/index.js.map +1 -0
- package/dist/module-coupling/types.d.ts +273 -0
- package/dist/module-coupling/types.d.ts.map +1 -0
- package/dist/module-coupling/types.js +8 -0
- package/dist/module-coupling/types.js.map +1 -0
- package/dist/patterns/adapters/index.d.ts +11 -0
- package/dist/patterns/adapters/index.d.ts.map +1 -0
- package/dist/patterns/adapters/index.js +11 -0
- package/dist/patterns/adapters/index.js.map +1 -0
- package/dist/patterns/adapters/pattern-store-adapter.d.ts +59 -0
- package/dist/patterns/adapters/pattern-store-adapter.d.ts.map +1 -0
- package/dist/patterns/adapters/pattern-store-adapter.js +468 -0
- package/dist/patterns/adapters/pattern-store-adapter.js.map +1 -0
- package/dist/patterns/adapters/service-factory.d.ts +40 -0
- package/dist/patterns/adapters/service-factory.d.ts.map +1 -0
- package/dist/patterns/adapters/service-factory.js +144 -0
- package/dist/patterns/adapters/service-factory.js.map +1 -0
- package/dist/patterns/errors.d.ts +32 -0
- package/dist/patterns/errors.d.ts.map +1 -0
- package/dist/patterns/errors.js +45 -0
- package/dist/patterns/errors.js.map +1 -0
- package/dist/patterns/impl/cached-repository.d.ts +79 -0
- package/dist/patterns/impl/cached-repository.d.ts.map +1 -0
- package/dist/patterns/impl/cached-repository.js +296 -0
- package/dist/patterns/impl/cached-repository.js.map +1 -0
- package/dist/patterns/impl/file-repository.d.ts +75 -0
- package/dist/patterns/impl/file-repository.d.ts.map +1 -0
- package/dist/patterns/impl/file-repository.js +507 -0
- package/dist/patterns/impl/file-repository.js.map +1 -0
- package/dist/patterns/impl/index.d.ts +16 -0
- package/dist/patterns/impl/index.d.ts.map +1 -0
- package/dist/patterns/impl/index.js +21 -0
- package/dist/patterns/impl/index.js.map +1 -0
- package/dist/patterns/impl/memory-repository.d.ts +56 -0
- package/dist/patterns/impl/memory-repository.d.ts.map +1 -0
- package/dist/patterns/impl/memory-repository.js +323 -0
- package/dist/patterns/impl/memory-repository.js.map +1 -0
- package/dist/patterns/impl/pattern-service.d.ts +52 -0
- package/dist/patterns/impl/pattern-service.d.ts.map +1 -0
- package/dist/patterns/impl/pattern-service.js +382 -0
- package/dist/patterns/impl/pattern-service.js.map +1 -0
- package/dist/patterns/impl/repository-factory.d.ts +44 -0
- package/dist/patterns/impl/repository-factory.d.ts.map +1 -0
- package/dist/patterns/impl/repository-factory.js +140 -0
- package/dist/patterns/impl/repository-factory.js.map +1 -0
- package/dist/patterns/impl/unified-file-repository.d.ts +111 -0
- package/dist/patterns/impl/unified-file-repository.d.ts.map +1 -0
- package/dist/patterns/impl/unified-file-repository.js +677 -0
- package/dist/patterns/impl/unified-file-repository.js.map +1 -0
- package/dist/patterns/index.d.ts +23 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +41 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/repository.d.ts +241 -0
- package/dist/patterns/repository.d.ts.map +1 -0
- package/dist/patterns/repository.js +23 -0
- package/dist/patterns/repository.js.map +1 -0
- package/dist/patterns/service.d.ts +245 -0
- package/dist/patterns/service.d.ts.map +1 -0
- package/dist/patterns/service.js +25 -0
- package/dist/patterns/service.js.map +1 -0
- package/dist/patterns/types.d.ts +227 -0
- package/dist/patterns/types.d.ts.map +1 -0
- package/dist/patterns/types.js +117 -0
- package/dist/patterns/types.js.map +1 -0
- package/dist/store/pattern-store.d.ts +6 -0
- package/dist/store/pattern-store.d.ts.map +1 -1
- package/dist/store/pattern-store.js +6 -0
- package/dist/store/pattern-store.js.map +1 -1
- package/dist/test-topology/extractors/base-test-extractor.d.ts +89 -0
- package/dist/test-topology/extractors/base-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/base-test-extractor.js +187 -0
- package/dist/test-topology/extractors/base-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/csharp-test-extractor.d.ts +23 -0
- package/dist/test-topology/extractors/csharp-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/csharp-test-extractor.js +367 -0
- package/dist/test-topology/extractors/csharp-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/index.d.ts +12 -0
- package/dist/test-topology/extractors/index.d.ts.map +1 -0
- package/dist/test-topology/extractors/index.js +12 -0
- package/dist/test-topology/extractors/index.js.map +1 -0
- package/dist/test-topology/extractors/java-test-extractor.d.ts +20 -0
- package/dist/test-topology/extractors/java-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/java-test-extractor.js +275 -0
- package/dist/test-topology/extractors/java-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/php-test-extractor.d.ts +24 -0
- package/dist/test-topology/extractors/php-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/php-test-extractor.js +409 -0
- package/dist/test-topology/extractors/php-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/python-test-extractor.d.ts +23 -0
- package/dist/test-topology/extractors/python-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/python-test-extractor.js +342 -0
- package/dist/test-topology/extractors/python-test-extractor.js.map +1 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.d.ts +51 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.js +383 -0
- package/dist/test-topology/extractors/regex/csharp-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/index.d.ts +18 -0
- package/dist/test-topology/extractors/regex/index.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/index.js +43 -0
- package/dist/test-topology/extractors/regex/index.js.map +1 -0
- package/dist/test-topology/extractors/regex/java-test-regex.d.ts +50 -0
- package/dist/test-topology/extractors/regex/java-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/java-test-regex.js +370 -0
- package/dist/test-topology/extractors/regex/java-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/php-test-regex.d.ts +56 -0
- package/dist/test-topology/extractors/regex/php-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/php-test-regex.js +503 -0
- package/dist/test-topology/extractors/regex/php-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/python-test-regex.d.ts +57 -0
- package/dist/test-topology/extractors/regex/python-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/python-test-regex.js +381 -0
- package/dist/test-topology/extractors/regex/python-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.d.ts +60 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.d.ts.map +1 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.js +368 -0
- package/dist/test-topology/extractors/regex/typescript-test-regex.js.map +1 -0
- package/dist/test-topology/extractors/typescript-test-extractor.d.ts +24 -0
- package/dist/test-topology/extractors/typescript-test-extractor.d.ts.map +1 -0
- package/dist/test-topology/extractors/typescript-test-extractor.js +266 -0
- package/dist/test-topology/extractors/typescript-test-extractor.js.map +1 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.d.ts +98 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.d.ts.map +1 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.js +555 -0
- package/dist/test-topology/hybrid-test-topology-analyzer.js.map +1 -0
- package/dist/test-topology/index.d.ts +16 -0
- package/dist/test-topology/index.d.ts.map +1 -0
- package/dist/test-topology/index.js +19 -0
- package/dist/test-topology/index.js.map +1 -0
- package/dist/test-topology/test-topology-analyzer.d.ts +85 -0
- package/dist/test-topology/test-topology-analyzer.d.ts.map +1 -0
- package/dist/test-topology/test-topology-analyzer.js +538 -0
- package/dist/test-topology/test-topology-analyzer.js.map +1 -0
- package/dist/test-topology/types.d.ts +300 -0
- package/dist/test-topology/types.d.ts.map +1 -0
- package/dist/test-topology/types.js +7 -0
- package/dist/test-topology/types.js.map +1 -0
- package/dist/wrappers/clustering/clusterer.d.ts +43 -0
- package/dist/wrappers/clustering/clusterer.d.ts.map +1 -0
- package/dist/wrappers/clustering/clusterer.js +374 -0
- package/dist/wrappers/clustering/clusterer.js.map +1 -0
- package/dist/wrappers/clustering/exclusions.d.ts +47 -0
- package/dist/wrappers/clustering/exclusions.d.ts.map +1 -0
- package/dist/wrappers/clustering/exclusions.js +318 -0
- package/dist/wrappers/clustering/exclusions.js.map +1 -0
- package/dist/wrappers/clustering/index.d.ts +6 -0
- package/dist/wrappers/clustering/index.d.ts.map +1 -0
- package/dist/wrappers/clustering/index.js +6 -0
- package/dist/wrappers/clustering/index.js.map +1 -0
- package/dist/wrappers/detection/detector.d.ts +69 -0
- package/dist/wrappers/detection/detector.d.ts.map +1 -0
- package/dist/wrappers/detection/detector.js +279 -0
- package/dist/wrappers/detection/detector.js.map +1 -0
- package/dist/wrappers/detection/index.d.ts +5 -0
- package/dist/wrappers/detection/index.d.ts.map +1 -0
- package/dist/wrappers/detection/index.js +5 -0
- package/dist/wrappers/detection/index.js.map +1 -0
- package/dist/wrappers/export/index.d.ts +5 -0
- package/dist/wrappers/export/index.d.ts.map +1 -0
- package/dist/wrappers/export/index.js +5 -0
- package/dist/wrappers/export/index.js.map +1 -0
- package/dist/wrappers/export/json.d.ts +127 -0
- package/dist/wrappers/export/json.d.ts.map +1 -0
- package/dist/wrappers/export/json.js +160 -0
- package/dist/wrappers/export/json.js.map +1 -0
- package/dist/wrappers/index.d.ts +56 -0
- package/dist/wrappers/index.d.ts.map +1 -0
- package/dist/wrappers/index.js +159 -0
- package/dist/wrappers/index.js.map +1 -0
- package/dist/wrappers/integration/adapter.d.ts +52 -0
- package/dist/wrappers/integration/adapter.d.ts.map +1 -0
- package/dist/wrappers/integration/adapter.js +209 -0
- package/dist/wrappers/integration/adapter.js.map +1 -0
- package/dist/wrappers/integration/index.d.ts +9 -0
- package/dist/wrappers/integration/index.d.ts.map +1 -0
- package/dist/wrappers/integration/index.js +12 -0
- package/dist/wrappers/integration/index.js.map +1 -0
- package/dist/wrappers/integration/pattern-adapter.d.ts +52 -0
- package/dist/wrappers/integration/pattern-adapter.d.ts.map +1 -0
- package/dist/wrappers/integration/pattern-adapter.js +192 -0
- package/dist/wrappers/integration/pattern-adapter.js.map +1 -0
- package/dist/wrappers/integration/scanner.d.ts +85 -0
- package/dist/wrappers/integration/scanner.d.ts.map +1 -0
- package/dist/wrappers/integration/scanner.js +367 -0
- package/dist/wrappers/integration/scanner.js.map +1 -0
- package/dist/wrappers/primitives/discovery.d.ts +57 -0
- package/dist/wrappers/primitives/discovery.d.ts.map +1 -0
- package/dist/wrappers/primitives/discovery.js +389 -0
- package/dist/wrappers/primitives/discovery.js.map +1 -0
- package/dist/wrappers/primitives/index.d.ts +6 -0
- package/dist/wrappers/primitives/index.d.ts.map +1 -0
- package/dist/wrappers/primitives/index.js +6 -0
- package/dist/wrappers/primitives/index.js.map +1 -0
- package/dist/wrappers/primitives/registry.d.ts +63 -0
- package/dist/wrappers/primitives/registry.d.ts.map +1 -0
- package/dist/wrappers/primitives/registry.js +447 -0
- package/dist/wrappers/primitives/registry.js.map +1 -0
- package/dist/wrappers/types.d.ts +137 -0
- package/dist/wrappers/types.d.ts.map +1 -0
- package/dist/wrappers/types.js +7 -0
- package/dist/wrappers/types.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Coupling Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes module dependencies, calculates coupling metrics,
|
|
5
|
+
* detects cycles, and identifies refactoring opportunities.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Analyzer
|
|
9
|
+
// ============================================================================
|
|
10
|
+
export class ModuleCouplingAnalyzer {
|
|
11
|
+
graph = null;
|
|
12
|
+
callGraph = null;
|
|
13
|
+
options;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = {
|
|
16
|
+
includeExternal: false,
|
|
17
|
+
granularity: 'file',
|
|
18
|
+
maxDepth: 20,
|
|
19
|
+
...options,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set the call graph for enhanced analysis
|
|
24
|
+
*/
|
|
25
|
+
setCallGraph(callGraph) {
|
|
26
|
+
this.callGraph = callGraph;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the module coupling graph from the call graph
|
|
30
|
+
*/
|
|
31
|
+
build() {
|
|
32
|
+
if (!this.callGraph) {
|
|
33
|
+
throw new Error('Call graph required. Call setCallGraph() first.');
|
|
34
|
+
}
|
|
35
|
+
const modules = new Map();
|
|
36
|
+
const edges = [];
|
|
37
|
+
const importMap = new Map();
|
|
38
|
+
// Phase 1: Extract modules and imports from call graph
|
|
39
|
+
for (const [_funcId, func] of this.callGraph.functions) {
|
|
40
|
+
const modulePath = func.file;
|
|
41
|
+
// Skip external modules if configured
|
|
42
|
+
if (!this.options.includeExternal && this.isExternalModule(modulePath)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Get or create module node
|
|
46
|
+
if (!modules.has(modulePath)) {
|
|
47
|
+
modules.set(modulePath, this.createModuleNode(modulePath, func.language));
|
|
48
|
+
}
|
|
49
|
+
const moduleNode = modules.get(modulePath);
|
|
50
|
+
// Track exports
|
|
51
|
+
if (func.isExported) {
|
|
52
|
+
moduleNode.exports.push({
|
|
53
|
+
name: func.name,
|
|
54
|
+
kind: this.inferExportKind(func),
|
|
55
|
+
isTypeOnly: false,
|
|
56
|
+
line: func.startLine,
|
|
57
|
+
isReExport: false,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Track imports from function calls
|
|
61
|
+
for (const call of func.calls) {
|
|
62
|
+
for (const candidate of call.resolvedCandidates) {
|
|
63
|
+
const targetFunc = this.callGraph.functions.get(candidate);
|
|
64
|
+
if (!targetFunc)
|
|
65
|
+
continue;
|
|
66
|
+
const targetModule = targetFunc.file;
|
|
67
|
+
if (targetModule === modulePath)
|
|
68
|
+
continue; // Skip self-imports
|
|
69
|
+
if (!this.options.includeExternal && this.isExternalModule(targetModule))
|
|
70
|
+
continue;
|
|
71
|
+
// Create or update edge
|
|
72
|
+
if (!importMap.has(modulePath)) {
|
|
73
|
+
importMap.set(modulePath, new Map());
|
|
74
|
+
}
|
|
75
|
+
const moduleImports = importMap.get(modulePath);
|
|
76
|
+
if (!moduleImports.has(targetModule)) {
|
|
77
|
+
moduleImports.set(targetModule, {
|
|
78
|
+
from: modulePath,
|
|
79
|
+
to: targetModule,
|
|
80
|
+
symbols: [],
|
|
81
|
+
isTypeOnly: false,
|
|
82
|
+
weight: 0,
|
|
83
|
+
line: call.line,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const edge = moduleImports.get(targetModule);
|
|
87
|
+
if (!edge.symbols.includes(targetFunc.name)) {
|
|
88
|
+
edge.symbols.push(targetFunc.name);
|
|
89
|
+
edge.weight++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Phase 2: Build edges array and update module relationships
|
|
95
|
+
for (const [fromModule, targets] of importMap) {
|
|
96
|
+
for (const [toModule, edge] of targets) {
|
|
97
|
+
edges.push(edge);
|
|
98
|
+
// Update module relationships
|
|
99
|
+
const fromNode = modules.get(fromModule);
|
|
100
|
+
const toNode = modules.get(toModule);
|
|
101
|
+
if (fromNode && !fromNode.imports.includes(toModule)) {
|
|
102
|
+
fromNode.imports.push(toModule);
|
|
103
|
+
}
|
|
104
|
+
if (toNode && !toNode.importedBy.includes(fromModule)) {
|
|
105
|
+
toNode.importedBy.push(fromModule);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Phase 3: Calculate metrics for each module
|
|
110
|
+
for (const [_path, node] of modules) {
|
|
111
|
+
node.metrics = this.calculateMetrics(node);
|
|
112
|
+
node.role = this.determineRole(node);
|
|
113
|
+
node.isLeaf = node.imports.length === 0;
|
|
114
|
+
node.isEntryPoint = this.callGraph.entryPoints.some(ep => ep.includes(node.path));
|
|
115
|
+
}
|
|
116
|
+
// Phase 4: Find unused exports
|
|
117
|
+
this.findUnusedExports(modules, edges);
|
|
118
|
+
// Phase 5: Detect cycles
|
|
119
|
+
const cycles = this.detectCycles(modules);
|
|
120
|
+
// Phase 6: Calculate aggregate metrics
|
|
121
|
+
const metrics = this.calculateAggregateMetrics(modules, edges, cycles);
|
|
122
|
+
this.graph = {
|
|
123
|
+
modules,
|
|
124
|
+
edges,
|
|
125
|
+
cycles,
|
|
126
|
+
metrics,
|
|
127
|
+
generatedAt: new Date().toISOString(),
|
|
128
|
+
projectRoot: this.options.rootDir,
|
|
129
|
+
};
|
|
130
|
+
return this.graph;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the built graph
|
|
134
|
+
*/
|
|
135
|
+
getGraph() {
|
|
136
|
+
return this.graph;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Analyze coupling for a specific module
|
|
140
|
+
*/
|
|
141
|
+
analyzeModule(modulePath) {
|
|
142
|
+
if (!this.graph)
|
|
143
|
+
return null;
|
|
144
|
+
const module = this.graph.modules.get(modulePath);
|
|
145
|
+
if (!module)
|
|
146
|
+
return null;
|
|
147
|
+
// Get direct dependencies with details
|
|
148
|
+
const directDependencies = module.imports.map(dep => {
|
|
149
|
+
const edge = this.graph.edges.find(e => e.from === modulePath && e.to === dep);
|
|
150
|
+
return {
|
|
151
|
+
path: dep,
|
|
152
|
+
symbols: edge?.symbols ?? [],
|
|
153
|
+
weight: edge?.weight ?? 0,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
// Get direct dependents with details
|
|
157
|
+
const directDependents = module.importedBy.map(dep => {
|
|
158
|
+
const edge = this.graph.edges.find(e => e.from === dep && e.to === modulePath);
|
|
159
|
+
return {
|
|
160
|
+
path: dep,
|
|
161
|
+
symbols: edge?.symbols ?? [],
|
|
162
|
+
weight: edge?.weight ?? 0,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
// Calculate transitive dependencies
|
|
166
|
+
const transitiveDependencies = this.getTransitiveDependencies(modulePath);
|
|
167
|
+
const transitiveDependents = this.getTransitiveDependents(modulePath);
|
|
168
|
+
// Find cycles involving this module
|
|
169
|
+
const cyclesInvolved = this.graph.cycles.filter(c => c.path.includes(modulePath));
|
|
170
|
+
// Calculate health
|
|
171
|
+
const health = this.calculateModuleHealth(module, cyclesInvolved);
|
|
172
|
+
return {
|
|
173
|
+
module,
|
|
174
|
+
directDependencies,
|
|
175
|
+
directDependents,
|
|
176
|
+
transitiveDependencies,
|
|
177
|
+
transitiveDependents,
|
|
178
|
+
cyclesInvolved,
|
|
179
|
+
health,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Analyze refactor impact for a module
|
|
184
|
+
*/
|
|
185
|
+
analyzeRefactorImpact(modulePath) {
|
|
186
|
+
if (!this.graph)
|
|
187
|
+
return null;
|
|
188
|
+
const module = this.graph.modules.get(modulePath);
|
|
189
|
+
if (!module)
|
|
190
|
+
return null;
|
|
191
|
+
const transitiveDependents = this.getTransitiveDependents(modulePath);
|
|
192
|
+
const affectedModules = transitiveDependents.map(dep => {
|
|
193
|
+
const depModule = this.graph.modules.get(dep);
|
|
194
|
+
const edge = this.graph.edges.find(e => e.to === modulePath && e.from === dep);
|
|
195
|
+
return {
|
|
196
|
+
path: dep,
|
|
197
|
+
reason: edge
|
|
198
|
+
? `Imports: ${edge.symbols.slice(0, 3).join(', ')}${edge.symbols.length > 3 ? '...' : ''}`
|
|
199
|
+
: 'Transitive dependency',
|
|
200
|
+
effort: this.estimateRefactorEffort(depModule, edge),
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
const totalAffected = affectedModules.length;
|
|
204
|
+
const risk = this.calculateRefactorRisk(totalAffected, module);
|
|
205
|
+
const suggestions = this.generateRefactorSuggestions(module, affectedModules);
|
|
206
|
+
return {
|
|
207
|
+
target: modulePath,
|
|
208
|
+
affectedModules,
|
|
209
|
+
totalAffected,
|
|
210
|
+
risk,
|
|
211
|
+
suggestions,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get all dependency cycles
|
|
216
|
+
*/
|
|
217
|
+
getCycles(options = {}) {
|
|
218
|
+
if (!this.graph)
|
|
219
|
+
return [];
|
|
220
|
+
let cycles = this.graph.cycles;
|
|
221
|
+
if (options.maxCycleLength) {
|
|
222
|
+
cycles = cycles.filter(c => c.length <= options.maxCycleLength);
|
|
223
|
+
}
|
|
224
|
+
if (options.minSeverity) {
|
|
225
|
+
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
226
|
+
const minOrder = severityOrder[options.minSeverity];
|
|
227
|
+
cycles = cycles.filter(c => severityOrder[c.severity] <= minOrder);
|
|
228
|
+
}
|
|
229
|
+
return cycles;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get coupling hotspots
|
|
233
|
+
*/
|
|
234
|
+
getHotspots(options = {}) {
|
|
235
|
+
if (!this.graph)
|
|
236
|
+
return [];
|
|
237
|
+
const { limit = 10, minCoupling = 5 } = options;
|
|
238
|
+
const hotspots = [];
|
|
239
|
+
for (const [path, module] of this.graph.modules) {
|
|
240
|
+
const coupling = module.metrics.Ca + module.metrics.Ce;
|
|
241
|
+
if (coupling >= minCoupling) {
|
|
242
|
+
hotspots.push({ path, coupling, metrics: module.metrics });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return hotspots
|
|
246
|
+
.sort((a, b) => b.coupling - a.coupling)
|
|
247
|
+
.slice(0, limit);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get unused exports across all modules
|
|
251
|
+
*/
|
|
252
|
+
getUnusedExports() {
|
|
253
|
+
if (!this.graph)
|
|
254
|
+
return [];
|
|
255
|
+
const results = [];
|
|
256
|
+
for (const [path, module] of this.graph.modules) {
|
|
257
|
+
if (module.unusedExports.length > 0) {
|
|
258
|
+
const unusedExports = module.unusedExports.map(name => {
|
|
259
|
+
const exp = module.exports.find(e => e.name === name);
|
|
260
|
+
return {
|
|
261
|
+
name,
|
|
262
|
+
kind: exp?.kind ?? 'other',
|
|
263
|
+
line: exp?.line ?? 0,
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
results.push({
|
|
267
|
+
module: path,
|
|
268
|
+
unusedExports,
|
|
269
|
+
possibleReasons: this.inferUnusedExportReasons(module, unusedExports),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return results;
|
|
274
|
+
}
|
|
275
|
+
// ============================================================================
|
|
276
|
+
// Private Helpers
|
|
277
|
+
// ============================================================================
|
|
278
|
+
createModuleNode(path, language) {
|
|
279
|
+
return {
|
|
280
|
+
path,
|
|
281
|
+
language: language,
|
|
282
|
+
importedBy: [],
|
|
283
|
+
imports: [],
|
|
284
|
+
metrics: { Ca: 0, Ce: 0, instability: 0, abstractness: 0, distance: 0 },
|
|
285
|
+
role: 'isolated',
|
|
286
|
+
exports: [],
|
|
287
|
+
unusedExports: [],
|
|
288
|
+
isEntryPoint: false,
|
|
289
|
+
isLeaf: true,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
isExternalModule(path) {
|
|
293
|
+
return path.includes('node_modules') ||
|
|
294
|
+
path.includes('vendor') ||
|
|
295
|
+
path.includes('site-packages') ||
|
|
296
|
+
path.startsWith('@') ||
|
|
297
|
+
!path.includes('/');
|
|
298
|
+
}
|
|
299
|
+
inferExportKind(func) {
|
|
300
|
+
if (func.className)
|
|
301
|
+
return 'class';
|
|
302
|
+
if (func.name.startsWith('use'))
|
|
303
|
+
return 'function'; // React hooks
|
|
304
|
+
return 'function';
|
|
305
|
+
}
|
|
306
|
+
calculateMetrics(node) {
|
|
307
|
+
const Ca = node.importedBy.length;
|
|
308
|
+
const Ce = node.imports.length;
|
|
309
|
+
const instability = Ca + Ce > 0 ? Ce / (Ca + Ce) : 0;
|
|
310
|
+
// Calculate abstractness (ratio of interfaces/abstract types)
|
|
311
|
+
const abstractExports = node.exports.filter(e => e.kind === 'interface' || e.kind === 'type').length;
|
|
312
|
+
const abstractness = node.exports.length > 0
|
|
313
|
+
? abstractExports / node.exports.length
|
|
314
|
+
: 0;
|
|
315
|
+
const distance = Math.abs(abstractness + instability - 1);
|
|
316
|
+
return {
|
|
317
|
+
Ca,
|
|
318
|
+
Ce,
|
|
319
|
+
instability: Math.round(instability * 100) / 100,
|
|
320
|
+
abstractness: Math.round(abstractness * 100) / 100,
|
|
321
|
+
distance: Math.round(distance * 100) / 100,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
determineRole(node) {
|
|
325
|
+
const { Ca, Ce } = node.metrics;
|
|
326
|
+
if (Ca === 0 && Ce === 0)
|
|
327
|
+
return 'isolated';
|
|
328
|
+
if (Ca > Ce * 2)
|
|
329
|
+
return 'hub'; // Many depend on this
|
|
330
|
+
if (Ce > Ca * 2)
|
|
331
|
+
return 'authority'; // Depends on many
|
|
332
|
+
return 'balanced';
|
|
333
|
+
}
|
|
334
|
+
findUnusedExports(modules, edges) {
|
|
335
|
+
// Build set of all imported symbols per module
|
|
336
|
+
const importedSymbols = new Map();
|
|
337
|
+
for (const edge of edges) {
|
|
338
|
+
if (!importedSymbols.has(edge.to)) {
|
|
339
|
+
importedSymbols.set(edge.to, new Set());
|
|
340
|
+
}
|
|
341
|
+
for (const symbol of edge.symbols) {
|
|
342
|
+
importedSymbols.get(edge.to).add(symbol);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Find unused exports
|
|
346
|
+
for (const [path, module] of modules) {
|
|
347
|
+
const imported = importedSymbols.get(path) ?? new Set();
|
|
348
|
+
module.unusedExports = module.exports
|
|
349
|
+
.filter(exp => !imported.has(exp.name))
|
|
350
|
+
.map(exp => exp.name);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
detectCycles(modules) {
|
|
354
|
+
// Tarjan's algorithm for strongly connected components
|
|
355
|
+
const index = new Map();
|
|
356
|
+
const lowlink = new Map();
|
|
357
|
+
const onStack = new Set();
|
|
358
|
+
const stack = [];
|
|
359
|
+
const sccs = [];
|
|
360
|
+
let currentIndex = 0;
|
|
361
|
+
const strongConnect = (v) => {
|
|
362
|
+
index.set(v, currentIndex);
|
|
363
|
+
lowlink.set(v, currentIndex);
|
|
364
|
+
currentIndex++;
|
|
365
|
+
stack.push(v);
|
|
366
|
+
onStack.add(v);
|
|
367
|
+
const node = modules.get(v);
|
|
368
|
+
if (node) {
|
|
369
|
+
for (const w of node.imports) {
|
|
370
|
+
if (!modules.has(w))
|
|
371
|
+
continue; // Skip external
|
|
372
|
+
if (!index.has(w)) {
|
|
373
|
+
strongConnect(w);
|
|
374
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
|
|
375
|
+
}
|
|
376
|
+
else if (onStack.has(w)) {
|
|
377
|
+
lowlink.set(v, Math.min(lowlink.get(v), index.get(w)));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (lowlink.get(v) === index.get(v)) {
|
|
382
|
+
const scc = [];
|
|
383
|
+
let w;
|
|
384
|
+
do {
|
|
385
|
+
w = stack.pop();
|
|
386
|
+
onStack.delete(w);
|
|
387
|
+
scc.push(w);
|
|
388
|
+
} while (w !== v);
|
|
389
|
+
if (scc.length > 1) {
|
|
390
|
+
sccs.push(scc);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
for (const v of modules.keys()) {
|
|
395
|
+
if (!index.has(v)) {
|
|
396
|
+
strongConnect(v);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Convert SCCs to DependencyCycle objects
|
|
400
|
+
return sccs.map((scc, i) => this.createCycle(scc, i, modules));
|
|
401
|
+
}
|
|
402
|
+
createCycle(path, index, modules) {
|
|
403
|
+
const length = path.length;
|
|
404
|
+
const severity = this.calculateCycleSeverity(path, modules);
|
|
405
|
+
const breakPoints = this.suggestBreakPoints(path, modules);
|
|
406
|
+
// Calculate total weight
|
|
407
|
+
let totalWeight = 0;
|
|
408
|
+
for (let i = 0; i < path.length; i++) {
|
|
409
|
+
const from = path[i];
|
|
410
|
+
const to = path[(i + 1) % path.length];
|
|
411
|
+
const fromNode = modules.get(from);
|
|
412
|
+
if (fromNode?.imports.includes(to)) {
|
|
413
|
+
totalWeight++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
id: `cycle-${index}`,
|
|
418
|
+
path,
|
|
419
|
+
length,
|
|
420
|
+
severity,
|
|
421
|
+
breakPoints,
|
|
422
|
+
totalWeight,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
calculateCycleSeverity(path, modules) {
|
|
426
|
+
// Critical: cycles involving entry points or high-coupling modules
|
|
427
|
+
const hasEntryPoint = path.some(p => modules.get(p)?.isEntryPoint);
|
|
428
|
+
const hasHighCoupling = path.some(p => {
|
|
429
|
+
const m = modules.get(p);
|
|
430
|
+
return m && (m.metrics.Ca + m.metrics.Ce) > 10;
|
|
431
|
+
});
|
|
432
|
+
if (hasEntryPoint || path.length > 5)
|
|
433
|
+
return 'critical';
|
|
434
|
+
if (hasHighCoupling || path.length > 3)
|
|
435
|
+
return 'warning';
|
|
436
|
+
return 'info';
|
|
437
|
+
}
|
|
438
|
+
suggestBreakPoints(path, modules) {
|
|
439
|
+
const suggestions = [];
|
|
440
|
+
for (let i = 0; i < path.length; i++) {
|
|
441
|
+
const from = path[i];
|
|
442
|
+
const to = path[(i + 1) % path.length];
|
|
443
|
+
const fromNode = modules.get(from);
|
|
444
|
+
const toNode = modules.get(to);
|
|
445
|
+
if (!fromNode || !toNode)
|
|
446
|
+
continue;
|
|
447
|
+
// Prefer breaking edges from unstable to stable modules
|
|
448
|
+
const effort = this.estimateBreakEffort(fromNode, toNode);
|
|
449
|
+
const rationale = this.generateBreakRationale(fromNode, toNode);
|
|
450
|
+
const approach = this.suggestBreakApproach(fromNode, toNode);
|
|
451
|
+
suggestions.push({
|
|
452
|
+
edge: { from, to },
|
|
453
|
+
rationale,
|
|
454
|
+
effort,
|
|
455
|
+
approach,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// Sort by effort (low first)
|
|
459
|
+
const effortOrder = { low: 0, medium: 1, high: 2 };
|
|
460
|
+
return suggestions.sort((a, b) => effortOrder[a.effort] - effortOrder[b.effort]);
|
|
461
|
+
}
|
|
462
|
+
estimateBreakEffort(from, to) {
|
|
463
|
+
// Low effort if from is unstable (easy to change)
|
|
464
|
+
if (from.metrics.instability > 0.7)
|
|
465
|
+
return 'low';
|
|
466
|
+
// High effort if to is a hub (many depend on it)
|
|
467
|
+
if (to.role === 'hub')
|
|
468
|
+
return 'high';
|
|
469
|
+
return 'medium';
|
|
470
|
+
}
|
|
471
|
+
generateBreakRationale(from, to) {
|
|
472
|
+
if (from.metrics.instability > to.metrics.instability) {
|
|
473
|
+
return `${from.path} is more unstable (${from.metrics.instability}) than ${to.path} (${to.metrics.instability})`;
|
|
474
|
+
}
|
|
475
|
+
if (to.role === 'hub') {
|
|
476
|
+
return `${to.path} is a hub with ${to.metrics.Ca} dependents`;
|
|
477
|
+
}
|
|
478
|
+
return 'Balanced coupling on both sides';
|
|
479
|
+
}
|
|
480
|
+
suggestBreakApproach(from, to) {
|
|
481
|
+
if (from.metrics.instability > 0.7) {
|
|
482
|
+
return 'Extract shared interface to a new module';
|
|
483
|
+
}
|
|
484
|
+
if (to.role === 'hub') {
|
|
485
|
+
return 'Use dependency injection to invert the dependency';
|
|
486
|
+
}
|
|
487
|
+
return 'Consider extracting common functionality to a shared module';
|
|
488
|
+
}
|
|
489
|
+
calculateAggregateMetrics(modules, edges, cycles) {
|
|
490
|
+
let totalInstability = 0;
|
|
491
|
+
let totalDistance = 0;
|
|
492
|
+
const zoneOfPain = [];
|
|
493
|
+
const zoneOfUselessness = [];
|
|
494
|
+
const hotspots = [];
|
|
495
|
+
const isolatedModules = [];
|
|
496
|
+
for (const [path, module] of modules) {
|
|
497
|
+
totalInstability += module.metrics.instability;
|
|
498
|
+
totalDistance += module.metrics.distance;
|
|
499
|
+
const coupling = module.metrics.Ca + module.metrics.Ce;
|
|
500
|
+
hotspots.push({ path, coupling });
|
|
501
|
+
// Zone of pain: stable (I < 0.3) and concrete (A < 0.3)
|
|
502
|
+
if (module.metrics.instability < 0.3 && module.metrics.abstractness < 0.3) {
|
|
503
|
+
zoneOfPain.push(path);
|
|
504
|
+
}
|
|
505
|
+
// Zone of uselessness: unstable (I > 0.7) and abstract (A > 0.7)
|
|
506
|
+
if (module.metrics.instability > 0.7 && module.metrics.abstractness > 0.7) {
|
|
507
|
+
zoneOfUselessness.push(path);
|
|
508
|
+
}
|
|
509
|
+
if (module.role === 'isolated') {
|
|
510
|
+
isolatedModules.push(path);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const moduleCount = modules.size;
|
|
514
|
+
return {
|
|
515
|
+
totalModules: moduleCount,
|
|
516
|
+
totalEdges: edges.length,
|
|
517
|
+
avgInstability: moduleCount > 0 ? Math.round((totalInstability / moduleCount) * 100) / 100 : 0,
|
|
518
|
+
avgDistance: moduleCount > 0 ? Math.round((totalDistance / moduleCount) * 100) / 100 : 0,
|
|
519
|
+
cycleCount: cycles.length,
|
|
520
|
+
zoneOfPain,
|
|
521
|
+
zoneOfUselessness,
|
|
522
|
+
hotspots: hotspots.sort((a, b) => b.coupling - a.coupling).slice(0, 10),
|
|
523
|
+
isolatedModules,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
getTransitiveDependencies(modulePath) {
|
|
527
|
+
if (!this.graph)
|
|
528
|
+
return [];
|
|
529
|
+
const visited = new Set();
|
|
530
|
+
const queue = [modulePath];
|
|
531
|
+
while (queue.length > 0) {
|
|
532
|
+
const current = queue.shift();
|
|
533
|
+
if (visited.has(current))
|
|
534
|
+
continue;
|
|
535
|
+
visited.add(current);
|
|
536
|
+
const node = this.graph.modules.get(current);
|
|
537
|
+
if (node) {
|
|
538
|
+
for (const dep of node.imports) {
|
|
539
|
+
if (!visited.has(dep)) {
|
|
540
|
+
queue.push(dep);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
visited.delete(modulePath);
|
|
546
|
+
return Array.from(visited);
|
|
547
|
+
}
|
|
548
|
+
getTransitiveDependents(modulePath) {
|
|
549
|
+
if (!this.graph)
|
|
550
|
+
return [];
|
|
551
|
+
const visited = new Set();
|
|
552
|
+
const queue = [modulePath];
|
|
553
|
+
while (queue.length > 0) {
|
|
554
|
+
const current = queue.shift();
|
|
555
|
+
if (visited.has(current))
|
|
556
|
+
continue;
|
|
557
|
+
visited.add(current);
|
|
558
|
+
const node = this.graph.modules.get(current);
|
|
559
|
+
if (node) {
|
|
560
|
+
for (const dep of node.importedBy) {
|
|
561
|
+
if (!visited.has(dep)) {
|
|
562
|
+
queue.push(dep);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
visited.delete(modulePath);
|
|
568
|
+
return Array.from(visited);
|
|
569
|
+
}
|
|
570
|
+
calculateModuleHealth(module, cycles) {
|
|
571
|
+
let score = 100;
|
|
572
|
+
const issues = [];
|
|
573
|
+
const suggestions = [];
|
|
574
|
+
// Penalize high coupling
|
|
575
|
+
const coupling = module.metrics.Ca + module.metrics.Ce;
|
|
576
|
+
if (coupling > 20) {
|
|
577
|
+
score -= 30;
|
|
578
|
+
issues.push(`Very high coupling (${coupling})`);
|
|
579
|
+
suggestions.push('Consider splitting into smaller modules');
|
|
580
|
+
}
|
|
581
|
+
else if (coupling > 10) {
|
|
582
|
+
score -= 15;
|
|
583
|
+
issues.push(`High coupling (${coupling})`);
|
|
584
|
+
}
|
|
585
|
+
// Penalize cycles
|
|
586
|
+
if (cycles.length > 0) {
|
|
587
|
+
score -= cycles.length * 10;
|
|
588
|
+
issues.push(`Involved in ${cycles.length} cycle(s)`);
|
|
589
|
+
suggestions.push('Break dependency cycles to improve maintainability');
|
|
590
|
+
}
|
|
591
|
+
// Penalize zone of pain
|
|
592
|
+
if (module.metrics.instability < 0.3 && module.metrics.abstractness < 0.3) {
|
|
593
|
+
score -= 20;
|
|
594
|
+
issues.push('In zone of pain (stable but concrete)');
|
|
595
|
+
suggestions.push('Add abstractions or make more flexible');
|
|
596
|
+
}
|
|
597
|
+
// Penalize unused exports
|
|
598
|
+
if (module.unusedExports.length > 3) {
|
|
599
|
+
score -= 10;
|
|
600
|
+
issues.push(`${module.unusedExports.length} unused exports`);
|
|
601
|
+
suggestions.push('Remove or document unused exports');
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
score: Math.max(0, score),
|
|
605
|
+
issues,
|
|
606
|
+
suggestions,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
estimateRefactorEffort(module, edge) {
|
|
610
|
+
if (!module)
|
|
611
|
+
return 'medium';
|
|
612
|
+
const symbolCount = edge?.symbols.length ?? 0;
|
|
613
|
+
if (symbolCount > 5)
|
|
614
|
+
return 'high';
|
|
615
|
+
if (symbolCount > 2)
|
|
616
|
+
return 'medium';
|
|
617
|
+
return 'low';
|
|
618
|
+
}
|
|
619
|
+
calculateRefactorRisk(totalAffected, module) {
|
|
620
|
+
if (totalAffected > 20 || module.role === 'hub')
|
|
621
|
+
return 'critical';
|
|
622
|
+
if (totalAffected > 10)
|
|
623
|
+
return 'high';
|
|
624
|
+
if (totalAffected > 5)
|
|
625
|
+
return 'medium';
|
|
626
|
+
return 'low';
|
|
627
|
+
}
|
|
628
|
+
generateRefactorSuggestions(module, affected) {
|
|
629
|
+
const suggestions = [];
|
|
630
|
+
if (module.role === 'hub') {
|
|
631
|
+
suggestions.push('Consider using dependency injection to reduce direct coupling');
|
|
632
|
+
}
|
|
633
|
+
const highEffortCount = affected.filter(a => a.effort === 'high').length;
|
|
634
|
+
if (highEffortCount > 3) {
|
|
635
|
+
suggestions.push('Create a facade or adapter to minimize breaking changes');
|
|
636
|
+
}
|
|
637
|
+
if (module.exports.length > 10) {
|
|
638
|
+
suggestions.push('Split module into smaller, focused modules');
|
|
639
|
+
}
|
|
640
|
+
if (suggestions.length === 0) {
|
|
641
|
+
suggestions.push('Refactor should be straightforward with proper testing');
|
|
642
|
+
}
|
|
643
|
+
return suggestions;
|
|
644
|
+
}
|
|
645
|
+
inferUnusedExportReasons(module, unusedExports) {
|
|
646
|
+
const reasons = [];
|
|
647
|
+
// Check if it's likely a public API
|
|
648
|
+
if (module.path.includes('index') || module.isEntryPoint) {
|
|
649
|
+
reasons.push('public-api');
|
|
650
|
+
}
|
|
651
|
+
// Check for test-related exports
|
|
652
|
+
if (unusedExports.some(e => e.name.includes('test') || e.name.includes('mock'))) {
|
|
653
|
+
reasons.push('test-only');
|
|
654
|
+
}
|
|
655
|
+
// Default to dead-code if no other reason
|
|
656
|
+
if (reasons.length === 0) {
|
|
657
|
+
reasons.push('dead-code');
|
|
658
|
+
}
|
|
659
|
+
return reasons;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// ============================================================================
|
|
663
|
+
// Factory
|
|
664
|
+
// ============================================================================
|
|
665
|
+
export function createModuleCouplingAnalyzer(options) {
|
|
666
|
+
return new ModuleCouplingAnalyzer(options);
|
|
667
|
+
}
|
|
668
|
+
//# sourceMappingURL=coupling-analyzer.js.map
|