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,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes error handling patterns, boundaries, and propagation chains.
|
|
5
|
+
* Detects gaps in error handling coverage.
|
|
6
|
+
*/
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Analyzer
|
|
9
|
+
// ============================================================================
|
|
10
|
+
export class ErrorHandlingAnalyzer {
|
|
11
|
+
topology = null;
|
|
12
|
+
callGraph = null;
|
|
13
|
+
options;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = {
|
|
16
|
+
includeAsync: true,
|
|
17
|
+
detectFrameworkBoundaries: true,
|
|
18
|
+
maxPropagationDepth: 20,
|
|
19
|
+
...options,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Set the call graph for analysis
|
|
24
|
+
*/
|
|
25
|
+
setCallGraph(callGraph) {
|
|
26
|
+
this.callGraph = callGraph;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the error handling topology
|
|
30
|
+
*/
|
|
31
|
+
build() {
|
|
32
|
+
if (!this.callGraph) {
|
|
33
|
+
throw new Error('Call graph required. Call setCallGraph() first.');
|
|
34
|
+
}
|
|
35
|
+
const functions = new Map();
|
|
36
|
+
const boundaries = [];
|
|
37
|
+
const propagationChains = [];
|
|
38
|
+
// Phase 1: Analyze each function's error handling
|
|
39
|
+
for (const [funcId, func] of this.callGraph.functions) {
|
|
40
|
+
const profile = this.analyzeFunction(funcId, func);
|
|
41
|
+
functions.set(funcId, profile);
|
|
42
|
+
// Detect boundaries
|
|
43
|
+
if (profile.hasTryCatch && profile.catchClauses.length > 0) {
|
|
44
|
+
const boundary = this.detectBoundary(funcId, func, profile);
|
|
45
|
+
if (boundary) {
|
|
46
|
+
boundaries.push(boundary);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Phase 2: Build propagation chains for throwing functions
|
|
51
|
+
for (const [funcId, profile] of functions) {
|
|
52
|
+
if (profile.canThrow) {
|
|
53
|
+
const chain = this.traceErrorPropagation(funcId, functions);
|
|
54
|
+
if (chain) {
|
|
55
|
+
propagationChains.push(chain);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Phase 3: Find unhandled paths
|
|
60
|
+
const unhandledPaths = this.findUnhandledPaths(propagationChains, functions);
|
|
61
|
+
this.topology = {
|
|
62
|
+
functions,
|
|
63
|
+
boundaries,
|
|
64
|
+
unhandledPaths,
|
|
65
|
+
propagationChains,
|
|
66
|
+
generatedAt: new Date().toISOString(),
|
|
67
|
+
projectRoot: this.options.rootDir,
|
|
68
|
+
};
|
|
69
|
+
return this.topology;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get the built topology
|
|
73
|
+
*/
|
|
74
|
+
getTopology() {
|
|
75
|
+
return this.topology;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get aggregate metrics
|
|
79
|
+
*/
|
|
80
|
+
getMetrics() {
|
|
81
|
+
if (!this.topology)
|
|
82
|
+
return null;
|
|
83
|
+
const functions = Array.from(this.topology.functions.values());
|
|
84
|
+
const totalFunctions = functions.length;
|
|
85
|
+
const functionsWithTryCatch = functions.filter(f => f.hasTryCatch).length;
|
|
86
|
+
const functionsThatThrow = functions.filter(f => f.canThrow).length;
|
|
87
|
+
const swallowedErrorCount = functions.filter(f => f.catchClauses.some(c => c.action === 'swallow')).length;
|
|
88
|
+
const unhandledAsyncCount = functions.filter(f => f.asyncHandling?.hasUnhandledPromises).length;
|
|
89
|
+
const avgQualityScore = totalFunctions > 0
|
|
90
|
+
? Math.round(functions.reduce((sum, f) => sum + f.qualityScore, 0) / totalFunctions)
|
|
91
|
+
: 0;
|
|
92
|
+
const unhandledBySeverity = {
|
|
93
|
+
critical: 0,
|
|
94
|
+
high: 0,
|
|
95
|
+
medium: 0,
|
|
96
|
+
low: 0,
|
|
97
|
+
};
|
|
98
|
+
for (const path of this.topology.unhandledPaths) {
|
|
99
|
+
unhandledBySeverity[path.severity]++;
|
|
100
|
+
}
|
|
101
|
+
const frameworkBoundaries = this.topology.boundaries.filter(b => b.isFrameworkBoundary).length;
|
|
102
|
+
return {
|
|
103
|
+
totalFunctions,
|
|
104
|
+
functionsWithTryCatch,
|
|
105
|
+
functionsThatThrow,
|
|
106
|
+
boundaryCount: this.topology.boundaries.length,
|
|
107
|
+
unhandledCount: this.topology.unhandledPaths.length,
|
|
108
|
+
unhandledBySeverity,
|
|
109
|
+
avgQualityScore,
|
|
110
|
+
swallowedErrorCount,
|
|
111
|
+
unhandledAsyncCount,
|
|
112
|
+
frameworkBoundaries,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get summary for display
|
|
117
|
+
*/
|
|
118
|
+
getSummary() {
|
|
119
|
+
const metrics = this.getMetrics();
|
|
120
|
+
if (!metrics || !this.topology)
|
|
121
|
+
return null;
|
|
122
|
+
const coveragePercent = metrics.totalFunctions > 0
|
|
123
|
+
? Math.round((metrics.functionsWithTryCatch / metrics.totalFunctions) * 100)
|
|
124
|
+
: 0;
|
|
125
|
+
const qualityDistribution = {
|
|
126
|
+
excellent: 0,
|
|
127
|
+
good: 0,
|
|
128
|
+
fair: 0,
|
|
129
|
+
poor: 0,
|
|
130
|
+
};
|
|
131
|
+
for (const func of this.topology.functions.values()) {
|
|
132
|
+
const quality = this.scoreToQuality(func.qualityScore);
|
|
133
|
+
qualityDistribution[quality]++;
|
|
134
|
+
}
|
|
135
|
+
const topIssues = [];
|
|
136
|
+
if (metrics.swallowedErrorCount > 0) {
|
|
137
|
+
topIssues.push({
|
|
138
|
+
type: 'swallowed',
|
|
139
|
+
count: metrics.swallowedErrorCount,
|
|
140
|
+
severity: 'high',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (metrics.unhandledAsyncCount > 0) {
|
|
144
|
+
topIssues.push({
|
|
145
|
+
type: 'unhandled-async',
|
|
146
|
+
count: metrics.unhandledAsyncCount,
|
|
147
|
+
severity: 'high',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (metrics.unhandledBySeverity.critical > 0) {
|
|
151
|
+
topIssues.push({
|
|
152
|
+
type: 'no-boundary',
|
|
153
|
+
count: metrics.unhandledBySeverity.critical,
|
|
154
|
+
severity: 'critical',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Sort by severity
|
|
158
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
159
|
+
topIssues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
160
|
+
return {
|
|
161
|
+
totalFunctions: metrics.totalFunctions,
|
|
162
|
+
coveragePercent,
|
|
163
|
+
unhandledPaths: metrics.unhandledCount,
|
|
164
|
+
criticalUnhandled: metrics.unhandledBySeverity.critical,
|
|
165
|
+
avgQuality: metrics.avgQualityScore,
|
|
166
|
+
qualityDistribution,
|
|
167
|
+
topIssues: topIssues.slice(0, 5),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Analyze a specific function
|
|
172
|
+
*/
|
|
173
|
+
analyzeFunction(funcId, func) {
|
|
174
|
+
const funcNode = func ?? this.callGraph?.functions.get(funcId);
|
|
175
|
+
if (!funcNode) {
|
|
176
|
+
throw new Error(`Function not found: ${funcId}`);
|
|
177
|
+
}
|
|
178
|
+
// Detect error handling constructs from function metadata
|
|
179
|
+
const hasTryCatch = this.detectTryCatch(funcNode);
|
|
180
|
+
const canThrow = this.detectThrows(funcNode);
|
|
181
|
+
const throwLocations = this.findThrowLocations(funcNode);
|
|
182
|
+
const catchClauses = this.extractCatchClauses(funcNode);
|
|
183
|
+
const rethrows = catchClauses.some(c => c.action === 'rethrow');
|
|
184
|
+
const isAsync = this.isAsyncFunction(funcNode);
|
|
185
|
+
const asyncHandling = isAsync && this.options.includeAsync
|
|
186
|
+
? this.analyzeAsyncHandling(funcNode)
|
|
187
|
+
: null;
|
|
188
|
+
const qualityScore = this.calculateQualityScore({
|
|
189
|
+
hasTryCatch,
|
|
190
|
+
canThrow,
|
|
191
|
+
catchClauses,
|
|
192
|
+
rethrows,
|
|
193
|
+
asyncHandling,
|
|
194
|
+
isAsync,
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
functionId: funcId,
|
|
198
|
+
file: funcNode.file,
|
|
199
|
+
name: funcNode.name,
|
|
200
|
+
qualifiedName: funcNode.className
|
|
201
|
+
? `${funcNode.className}.${funcNode.name}`
|
|
202
|
+
: funcNode.name,
|
|
203
|
+
line: funcNode.startLine,
|
|
204
|
+
hasTryCatch,
|
|
205
|
+
canThrow,
|
|
206
|
+
throwLocations,
|
|
207
|
+
catchClauses,
|
|
208
|
+
rethrows,
|
|
209
|
+
asyncHandling,
|
|
210
|
+
isAsync,
|
|
211
|
+
qualityScore,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get detailed analysis for a function
|
|
216
|
+
*/
|
|
217
|
+
getFunctionAnalysis(funcId) {
|
|
218
|
+
if (!this.topology || !this.callGraph)
|
|
219
|
+
return null;
|
|
220
|
+
const profile = this.topology.functions.get(funcId);
|
|
221
|
+
if (!profile)
|
|
222
|
+
return null;
|
|
223
|
+
const func = this.callGraph.functions.get(funcId);
|
|
224
|
+
if (!func)
|
|
225
|
+
return null;
|
|
226
|
+
// Find incoming errors (from callees)
|
|
227
|
+
const incomingErrors = [];
|
|
228
|
+
for (const call of func.calls) {
|
|
229
|
+
for (const candidate of call.resolvedCandidates) {
|
|
230
|
+
const calleeProfile = this.topology.functions.get(candidate);
|
|
231
|
+
if (calleeProfile?.canThrow) {
|
|
232
|
+
incomingErrors.push({
|
|
233
|
+
from: candidate,
|
|
234
|
+
errorType: 'Error', // Would need AST analysis for specific types
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Find outgoing errors (to callers)
|
|
240
|
+
const outgoingErrors = [];
|
|
241
|
+
for (const caller of func.calledBy) {
|
|
242
|
+
const callerProfile = this.topology.functions.get(caller.callerId);
|
|
243
|
+
outgoingErrors.push({
|
|
244
|
+
to: caller.callerId,
|
|
245
|
+
caught: callerProfile?.hasTryCatch ?? false,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// Check if protected by boundary
|
|
249
|
+
const foundBoundary = this.topology.boundaries.find(b => b.catchesFrom.includes(funcId));
|
|
250
|
+
// Find issues
|
|
251
|
+
const issues = [];
|
|
252
|
+
if (profile.canThrow && !profile.hasTryCatch && !foundBoundary) {
|
|
253
|
+
issues.push({
|
|
254
|
+
type: 'unprotected-throw',
|
|
255
|
+
message: 'Function throws but has no error handling',
|
|
256
|
+
severity: 'high',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (profile.catchClauses.some(c => c.action === 'swallow')) {
|
|
260
|
+
const swallowLine = profile.catchClauses.find(c => c.action === 'swallow')?.line;
|
|
261
|
+
issues.push({
|
|
262
|
+
type: 'swallowed-error',
|
|
263
|
+
message: 'Error is caught but swallowed (empty catch block)',
|
|
264
|
+
severity: 'medium',
|
|
265
|
+
...(swallowLine !== undefined && { line: swallowLine }),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (profile.asyncHandling?.hasUnhandledPromises) {
|
|
269
|
+
issues.push({
|
|
270
|
+
type: 'unhandled-promise',
|
|
271
|
+
message: 'Async function has unhandled promise rejections',
|
|
272
|
+
severity: 'high',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (profile.catchClauses.some(c => c.errorType === 'any')) {
|
|
276
|
+
issues.push({
|
|
277
|
+
type: 'bare-catch',
|
|
278
|
+
message: 'Catch clause catches all errors without type checking',
|
|
279
|
+
severity: 'low',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Generate suggestions
|
|
283
|
+
const suggestions = [];
|
|
284
|
+
if (issues.some(i => i.type === 'unprotected-throw')) {
|
|
285
|
+
suggestions.push('Add try/catch block or ensure caller handles errors');
|
|
286
|
+
}
|
|
287
|
+
if (issues.some(i => i.type === 'swallowed-error')) {
|
|
288
|
+
suggestions.push('Log the error or rethrow it instead of swallowing');
|
|
289
|
+
}
|
|
290
|
+
if (issues.some(i => i.type === 'unhandled-promise')) {
|
|
291
|
+
suggestions.push('Add .catch() to promise chains or use try/catch with await');
|
|
292
|
+
}
|
|
293
|
+
const result = {
|
|
294
|
+
profile,
|
|
295
|
+
incomingErrors,
|
|
296
|
+
outgoingErrors,
|
|
297
|
+
isProtected: !!foundBoundary,
|
|
298
|
+
issues,
|
|
299
|
+
suggestions,
|
|
300
|
+
};
|
|
301
|
+
if (foundBoundary) {
|
|
302
|
+
result.protectingBoundary = foundBoundary;
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Find error handling gaps
|
|
308
|
+
*/
|
|
309
|
+
getGaps(options = {}) {
|
|
310
|
+
if (!this.topology)
|
|
311
|
+
return [];
|
|
312
|
+
const { minSeverity = 'low', limit = 20, includeSuggestions = true, files } = options;
|
|
313
|
+
const gaps = [];
|
|
314
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
315
|
+
const minOrder = severityOrder[minSeverity];
|
|
316
|
+
for (const [funcId, profile] of this.topology.functions) {
|
|
317
|
+
// Filter by files if specified
|
|
318
|
+
if (files && !files.some(f => profile.file.includes(f)))
|
|
319
|
+
continue;
|
|
320
|
+
// Check for various gaps
|
|
321
|
+
if (profile.canThrow && !profile.hasTryCatch) {
|
|
322
|
+
const severity = this.calculateGapSeverity(profile, 'no-try-catch');
|
|
323
|
+
if (severityOrder[severity] <= minOrder) {
|
|
324
|
+
gaps.push({
|
|
325
|
+
functionId: funcId,
|
|
326
|
+
file: profile.file,
|
|
327
|
+
name: profile.qualifiedName,
|
|
328
|
+
line: profile.line,
|
|
329
|
+
gapType: 'no-try-catch',
|
|
330
|
+
severity,
|
|
331
|
+
description: 'Function can throw but has no error handling',
|
|
332
|
+
suggestion: includeSuggestions
|
|
333
|
+
? 'Add try/catch block to handle potential errors'
|
|
334
|
+
: '',
|
|
335
|
+
riskScore: this.calculateRiskScore(profile, 'no-try-catch'),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (profile.catchClauses.some(c => c.action === 'swallow')) {
|
|
340
|
+
const severity = this.calculateGapSeverity(profile, 'swallowed-error');
|
|
341
|
+
if (severityOrder[severity] <= minOrder) {
|
|
342
|
+
gaps.push({
|
|
343
|
+
functionId: funcId,
|
|
344
|
+
file: profile.file,
|
|
345
|
+
name: profile.qualifiedName,
|
|
346
|
+
line: profile.catchClauses.find(c => c.action === 'swallow')?.line ?? profile.line,
|
|
347
|
+
gapType: 'swallowed-error',
|
|
348
|
+
severity,
|
|
349
|
+
description: 'Error is caught but not handled (empty catch block)',
|
|
350
|
+
suggestion: includeSuggestions
|
|
351
|
+
? 'Log the error or rethrow it'
|
|
352
|
+
: '',
|
|
353
|
+
riskScore: this.calculateRiskScore(profile, 'swallowed-error'),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (profile.asyncHandling?.hasUnhandledPromises) {
|
|
358
|
+
const severity = this.calculateGapSeverity(profile, 'unhandled-async');
|
|
359
|
+
if (severityOrder[severity] <= minOrder) {
|
|
360
|
+
gaps.push({
|
|
361
|
+
functionId: funcId,
|
|
362
|
+
file: profile.file,
|
|
363
|
+
name: profile.qualifiedName,
|
|
364
|
+
line: profile.asyncHandling.unhandledLocations[0]?.line ?? profile.line,
|
|
365
|
+
gapType: 'unhandled-async',
|
|
366
|
+
severity,
|
|
367
|
+
description: 'Async function has unhandled promise rejections',
|
|
368
|
+
suggestion: includeSuggestions
|
|
369
|
+
? 'Add .catch() or wrap await in try/catch'
|
|
370
|
+
: '',
|
|
371
|
+
riskScore: this.calculateRiskScore(profile, 'unhandled-async'),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (profile.catchClauses.some(c => c.errorType === 'any' && c.action !== 'rethrow')) {
|
|
376
|
+
const severity = 'low';
|
|
377
|
+
if (severityOrder[severity] <= minOrder) {
|
|
378
|
+
gaps.push({
|
|
379
|
+
functionId: funcId,
|
|
380
|
+
file: profile.file,
|
|
381
|
+
name: profile.qualifiedName,
|
|
382
|
+
line: profile.catchClauses.find(c => c.errorType === 'any')?.line ?? profile.line,
|
|
383
|
+
gapType: 'bare-catch',
|
|
384
|
+
severity,
|
|
385
|
+
description: 'Catch clause catches all errors without type checking',
|
|
386
|
+
suggestion: includeSuggestions
|
|
387
|
+
? 'Consider catching specific error types'
|
|
388
|
+
: '',
|
|
389
|
+
riskScore: this.calculateRiskScore(profile, 'bare-catch'),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Sort by risk score descending
|
|
395
|
+
gaps.sort((a, b) => b.riskScore - a.riskScore);
|
|
396
|
+
return gaps.slice(0, limit);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get error boundaries
|
|
400
|
+
*/
|
|
401
|
+
getBoundaries(options = {}) {
|
|
402
|
+
if (!this.topology)
|
|
403
|
+
return [];
|
|
404
|
+
let boundaries = this.topology.boundaries;
|
|
405
|
+
if (!options.includeFramework) {
|
|
406
|
+
boundaries = boundaries.filter(b => !b.isFrameworkBoundary);
|
|
407
|
+
}
|
|
408
|
+
if (options.minCoverage !== undefined) {
|
|
409
|
+
boundaries = boundaries.filter(b => b.coverage >= options.minCoverage);
|
|
410
|
+
}
|
|
411
|
+
return boundaries.sort((a, b) => b.coverage - a.coverage);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get unhandled error paths
|
|
415
|
+
*/
|
|
416
|
+
getUnhandledPaths(minSeverity = 'low') {
|
|
417
|
+
if (!this.topology)
|
|
418
|
+
return [];
|
|
419
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
420
|
+
const minOrder = severityOrder[minSeverity];
|
|
421
|
+
return this.topology.unhandledPaths
|
|
422
|
+
.filter(p => severityOrder[p.severity] <= minOrder)
|
|
423
|
+
.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
424
|
+
}
|
|
425
|
+
// ============================================================================
|
|
426
|
+
// Private Helpers
|
|
427
|
+
// ============================================================================
|
|
428
|
+
detectTryCatch(func) {
|
|
429
|
+
// Heuristic: check if function body contains try/catch patterns
|
|
430
|
+
// In a full implementation, this would use AST analysis
|
|
431
|
+
// For now, we use metadata from the call graph if available
|
|
432
|
+
return func.hasTryCatch ?? false;
|
|
433
|
+
}
|
|
434
|
+
detectThrows(func) {
|
|
435
|
+
// Heuristic: check if function can throw
|
|
436
|
+
// Functions that call other throwing functions can also throw
|
|
437
|
+
return func.canThrow ??
|
|
438
|
+
func.calls.length > 0; // Conservative: assume any function with calls can throw
|
|
439
|
+
}
|
|
440
|
+
findThrowLocations(func) {
|
|
441
|
+
// Would need AST analysis for precise locations
|
|
442
|
+
return func.throwLocations ?? [];
|
|
443
|
+
}
|
|
444
|
+
extractCatchClauses(func) {
|
|
445
|
+
// Would need AST analysis for precise extraction
|
|
446
|
+
// Return empty for now - real implementation would parse AST
|
|
447
|
+
return func.catchClauses ?? [];
|
|
448
|
+
}
|
|
449
|
+
isAsyncFunction(func) {
|
|
450
|
+
if (func.isAsync)
|
|
451
|
+
return true;
|
|
452
|
+
if (func.name.includes('async'))
|
|
453
|
+
return true;
|
|
454
|
+
return func.returnType?.includes('Promise') ?? false;
|
|
455
|
+
}
|
|
456
|
+
analyzeAsyncHandling(_func) {
|
|
457
|
+
// Would need AST analysis for precise detection
|
|
458
|
+
return {
|
|
459
|
+
hasCatch: false,
|
|
460
|
+
hasAsyncTryCatch: false,
|
|
461
|
+
hasUnhandledPromises: false,
|
|
462
|
+
unhandledLocations: [],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
calculateQualityScore(profile) {
|
|
466
|
+
let score = 50; // Base score
|
|
467
|
+
// Positive factors
|
|
468
|
+
if (profile.hasTryCatch)
|
|
469
|
+
score += 20;
|
|
470
|
+
if (profile.catchClauses?.some(c => c.action === 'recover'))
|
|
471
|
+
score += 15;
|
|
472
|
+
if (profile.catchClauses?.some(c => c.action === 'transform'))
|
|
473
|
+
score += 10;
|
|
474
|
+
if (profile.catchClauses?.some(c => c.preservesError))
|
|
475
|
+
score += 5;
|
|
476
|
+
if (profile.asyncHandling?.hasAsyncTryCatch)
|
|
477
|
+
score += 10;
|
|
478
|
+
if (profile.asyncHandling?.hasCatch)
|
|
479
|
+
score += 5;
|
|
480
|
+
// Negative factors
|
|
481
|
+
if (profile.canThrow && !profile.hasTryCatch)
|
|
482
|
+
score -= 20;
|
|
483
|
+
if (profile.catchClauses?.some(c => c.action === 'swallow'))
|
|
484
|
+
score -= 25;
|
|
485
|
+
if (profile.catchClauses?.some(c => c.errorType === 'any'))
|
|
486
|
+
score -= 5;
|
|
487
|
+
if (profile.asyncHandling?.hasUnhandledPromises)
|
|
488
|
+
score -= 20;
|
|
489
|
+
return Math.max(0, Math.min(100, score));
|
|
490
|
+
}
|
|
491
|
+
scoreToQuality(score) {
|
|
492
|
+
if (score >= 80)
|
|
493
|
+
return 'excellent';
|
|
494
|
+
if (score >= 60)
|
|
495
|
+
return 'good';
|
|
496
|
+
if (score >= 40)
|
|
497
|
+
return 'fair';
|
|
498
|
+
return 'poor';
|
|
499
|
+
}
|
|
500
|
+
detectBoundary(funcId, func, profile) {
|
|
501
|
+
if (!this.callGraph)
|
|
502
|
+
return null;
|
|
503
|
+
// Find what this function catches from
|
|
504
|
+
const catchesFrom = [];
|
|
505
|
+
for (const call of func.calls) {
|
|
506
|
+
for (const candidate of call.resolvedCandidates) {
|
|
507
|
+
const calleeProfile = this.topology?.functions.get(candidate);
|
|
508
|
+
if (calleeProfile?.canThrow) {
|
|
509
|
+
catchesFrom.push(candidate);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (catchesFrom.length === 0)
|
|
514
|
+
return null;
|
|
515
|
+
// Detect framework boundaries
|
|
516
|
+
const isFrameworkBoundary = this.isFrameworkBoundary(func);
|
|
517
|
+
const frameworkType = this.detectFrameworkType(func);
|
|
518
|
+
// Calculate coverage
|
|
519
|
+
const totalCallees = func.calls.reduce((sum, c) => sum + c.resolvedCandidates.length, 0);
|
|
520
|
+
const coverage = totalCallees > 0 ? (catchesFrom.length / totalCallees) * 100 : 0;
|
|
521
|
+
const boundary = {
|
|
522
|
+
functionId: funcId,
|
|
523
|
+
file: func.file,
|
|
524
|
+
name: profile.qualifiedName,
|
|
525
|
+
catchesFrom,
|
|
526
|
+
handledTypes: profile.catchClauses.map(c => c.errorType),
|
|
527
|
+
isFrameworkBoundary,
|
|
528
|
+
coverage: Math.round(coverage),
|
|
529
|
+
line: profile.catchClauses[0]?.line ?? func.startLine,
|
|
530
|
+
};
|
|
531
|
+
if (frameworkType) {
|
|
532
|
+
boundary.frameworkType = frameworkType;
|
|
533
|
+
}
|
|
534
|
+
return boundary;
|
|
535
|
+
}
|
|
536
|
+
isFrameworkBoundary(func) {
|
|
537
|
+
const name = func.name.toLowerCase();
|
|
538
|
+
const className = func.className?.toLowerCase() ?? '';
|
|
539
|
+
// React error boundaries
|
|
540
|
+
if (name === 'componentdidcatch' || className.includes('errorboundary'))
|
|
541
|
+
return true;
|
|
542
|
+
// Express/Koa middleware
|
|
543
|
+
if (func.parameters?.length === 4)
|
|
544
|
+
return true; // (err, req, res, next)
|
|
545
|
+
// NestJS exception filters
|
|
546
|
+
if (className.includes('filter') && name === 'catch')
|
|
547
|
+
return true;
|
|
548
|
+
// Spring exception handlers
|
|
549
|
+
if (func.annotations?.some((a) => a.includes('ExceptionHandler') || a.includes('ControllerAdvice')))
|
|
550
|
+
return true;
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
detectFrameworkType(func) {
|
|
554
|
+
const name = func.name.toLowerCase();
|
|
555
|
+
const className = func.className?.toLowerCase() ?? '';
|
|
556
|
+
if (name === 'componentdidcatch' || className.includes('errorboundary')) {
|
|
557
|
+
return 'react-error-boundary';
|
|
558
|
+
}
|
|
559
|
+
if (func.parameters?.length === 4) {
|
|
560
|
+
return 'express-middleware';
|
|
561
|
+
}
|
|
562
|
+
if (className.includes('filter') && name === 'catch') {
|
|
563
|
+
return 'nestjs-filter';
|
|
564
|
+
}
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
traceErrorPropagation(throwerId, functions) {
|
|
568
|
+
if (!this.callGraph)
|
|
569
|
+
return null;
|
|
570
|
+
const path = [throwerId];
|
|
571
|
+
let current = throwerId;
|
|
572
|
+
let depth = 0;
|
|
573
|
+
const maxDepth = this.options.maxPropagationDepth ?? 20;
|
|
574
|
+
while (depth < maxDepth) {
|
|
575
|
+
const func = this.callGraph.functions.get(current);
|
|
576
|
+
if (!func || func.calledBy.length === 0) {
|
|
577
|
+
// No more callers - error escapes
|
|
578
|
+
return {
|
|
579
|
+
source: { functionId: throwerId, throwLine: functions.get(throwerId)?.line ?? 0 },
|
|
580
|
+
sink: null,
|
|
581
|
+
propagationPath: path,
|
|
582
|
+
transformations: [],
|
|
583
|
+
depth,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
// Check if any caller catches
|
|
587
|
+
for (const caller of func.calledBy) {
|
|
588
|
+
const callerProfile = functions.get(caller.callerId);
|
|
589
|
+
if (callerProfile?.hasTryCatch) {
|
|
590
|
+
// Found a boundary
|
|
591
|
+
return {
|
|
592
|
+
source: { functionId: throwerId, throwLine: functions.get(throwerId)?.line ?? 0 },
|
|
593
|
+
sink: {
|
|
594
|
+
functionId: caller.callerId,
|
|
595
|
+
catchLine: callerProfile.catchClauses[0]?.line ?? callerProfile.line
|
|
596
|
+
},
|
|
597
|
+
propagationPath: path,
|
|
598
|
+
transformations: [],
|
|
599
|
+
depth,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Move to first caller (simplified - real impl would explore all paths)
|
|
604
|
+
const nextCaller = func.calledBy[0]?.callerId;
|
|
605
|
+
if (!nextCaller || path.includes(nextCaller))
|
|
606
|
+
break; // Avoid cycles
|
|
607
|
+
path.push(nextCaller);
|
|
608
|
+
current = nextCaller;
|
|
609
|
+
depth++;
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
findUnhandledPaths(chains, functions) {
|
|
614
|
+
const unhandled = [];
|
|
615
|
+
for (const chain of chains) {
|
|
616
|
+
if (chain.sink === null) {
|
|
617
|
+
// This error escapes without being caught
|
|
618
|
+
const sourceProfile = functions.get(chain.source.functionId);
|
|
619
|
+
const entryPoint = chain.propagationPath[chain.propagationPath.length - 1] ?? chain.source.functionId;
|
|
620
|
+
const entryProfile = functions.get(entryPoint);
|
|
621
|
+
const severity = this.calculatePathSeverity(entryProfile);
|
|
622
|
+
unhandled.push({
|
|
623
|
+
entryPoint,
|
|
624
|
+
path: chain.propagationPath,
|
|
625
|
+
errorType: 'Error', // Would need AST for specific type
|
|
626
|
+
severity,
|
|
627
|
+
suggestedBoundary: this.suggestBoundaryLocation(chain, functions),
|
|
628
|
+
reason: `Error from ${sourceProfile?.qualifiedName ?? chain.source.functionId} escapes to ${entryProfile?.qualifiedName ?? entryPoint}`,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return unhandled;
|
|
633
|
+
}
|
|
634
|
+
calculatePathSeverity(entryProfile) {
|
|
635
|
+
if (!entryProfile)
|
|
636
|
+
return 'medium';
|
|
637
|
+
// Entry points and exported functions are more critical
|
|
638
|
+
const func = this.callGraph?.functions.get(entryProfile.functionId);
|
|
639
|
+
if (func?.isExported)
|
|
640
|
+
return 'critical';
|
|
641
|
+
// Check if file is an entry point
|
|
642
|
+
if (this.callGraph?.entryPoints.includes(entryProfile.file))
|
|
643
|
+
return 'critical';
|
|
644
|
+
return 'medium';
|
|
645
|
+
}
|
|
646
|
+
suggestBoundaryLocation(chain, _functions) {
|
|
647
|
+
// Suggest adding error handling at the middle of the chain
|
|
648
|
+
const midIndex = Math.floor(chain.propagationPath.length / 2);
|
|
649
|
+
return chain.propagationPath[midIndex] ?? chain.source.functionId;
|
|
650
|
+
}
|
|
651
|
+
calculateGapSeverity(profile, gapType) {
|
|
652
|
+
const func = this.callGraph?.functions.get(profile.functionId);
|
|
653
|
+
// Entry points and exported functions are more critical
|
|
654
|
+
const isEntryPointFile = this.callGraph?.entryPoints.includes(profile.file) ?? false;
|
|
655
|
+
if (func?.isExported || isEntryPointFile) {
|
|
656
|
+
return gapType === 'swallowed-error' ? 'high' : 'critical';
|
|
657
|
+
}
|
|
658
|
+
switch (gapType) {
|
|
659
|
+
case 'no-try-catch':
|
|
660
|
+
return profile.canThrow ? 'high' : 'medium';
|
|
661
|
+
case 'swallowed-error':
|
|
662
|
+
return 'high';
|
|
663
|
+
case 'unhandled-async':
|
|
664
|
+
return 'high';
|
|
665
|
+
case 'bare-catch':
|
|
666
|
+
return 'low';
|
|
667
|
+
default:
|
|
668
|
+
return 'medium';
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
calculateRiskScore(profile, gapType) {
|
|
672
|
+
let score = 50;
|
|
673
|
+
// Gap type weights
|
|
674
|
+
switch (gapType) {
|
|
675
|
+
case 'no-try-catch':
|
|
676
|
+
score += 20;
|
|
677
|
+
break;
|
|
678
|
+
case 'swallowed-error':
|
|
679
|
+
score += 30;
|
|
680
|
+
break;
|
|
681
|
+
case 'unhandled-async':
|
|
682
|
+
score += 25;
|
|
683
|
+
break;
|
|
684
|
+
case 'bare-catch':
|
|
685
|
+
score += 5;
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
// Function importance
|
|
689
|
+
const func = this.callGraph?.functions.get(profile.functionId);
|
|
690
|
+
const isEntryPointFile = this.callGraph?.entryPoints.includes(profile.file) ?? false;
|
|
691
|
+
if (func?.isExported)
|
|
692
|
+
score += 15;
|
|
693
|
+
if (isEntryPointFile)
|
|
694
|
+
score += 20;
|
|
695
|
+
if ((func?.calledBy.length ?? 0) > 5)
|
|
696
|
+
score += 10;
|
|
697
|
+
return Math.min(100, score);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// ============================================================================
|
|
701
|
+
// Factory
|
|
702
|
+
// ============================================================================
|
|
703
|
+
export function createErrorHandlingAnalyzer(options) {
|
|
704
|
+
return new ErrorHandlingAnalyzer(options);
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=error-handling-analyzer.js.map
|