ng-di-graph 0.4.0 → 0.4.2
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/README.md +6 -0
- package/dist/cli/index.cjs +178 -134
- package/dist/cli/index.cjs.map +1 -1
- package/dist/core/graph-filter.d.ts +3 -1
- package/dist/core/parser.d.ts +3 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -185,6 +185,12 @@ ng-di-graph --project ./tsconfig.json --verbose
|
|
|
185
185
|
# Shows detailed type resolution and warnings
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
## Release workflow
|
|
189
|
+
|
|
190
|
+
- PR-driven releases via Release Please; release PR merges tag `vX.Y.Z`.
|
|
191
|
+
- Tag pushes trigger publish workflow: lint → typecheck → test → build → pack → publish with provenance.
|
|
192
|
+
- Maintainer runbook: see docs/release/ci-publish.md.
|
|
193
|
+
|
|
188
194
|
## Error Handling
|
|
189
195
|
|
|
190
196
|
The tool provides graceful error handling:
|
package/dist/cli/index.cjs
CHANGED
|
@@ -510,32 +510,37 @@ function buildGraph(parsedClasses, logger) {
|
|
|
510
510
|
* @param adjacencyList The adjacency list for traversal
|
|
511
511
|
* @param resultSet The set to collect traversal results
|
|
512
512
|
* @param options CLI options for verbose output
|
|
513
|
+
* @param logger Optional logger for structured output
|
|
513
514
|
*/
|
|
514
|
-
function validateAndTraverseEntryPoints(entryPoints, graph, adjacencyList, resultSet, options) {
|
|
515
|
+
function validateAndTraverseEntryPoints(entryPoints, graph, adjacencyList, resultSet, options, logger) {
|
|
515
516
|
for (const entryPoint of entryPoints) if (graph.nodes.some((n) => n.id === entryPoint)) traverseFromEntry(entryPoint, adjacencyList, resultSet);
|
|
516
|
-
else if (options.verbose
|
|
517
|
+
else if (options.verbose && logger) {
|
|
518
|
+
const message = `Entry point '${entryPoint}' not found in graph`;
|
|
519
|
+
logger.warn(LogCategory.FILTERING, message, { entryPoint });
|
|
520
|
+
}
|
|
517
521
|
}
|
|
518
522
|
/**
|
|
519
523
|
* Filters a graph based on entry points and traversal direction
|
|
520
524
|
* @param graph The graph to filter
|
|
521
525
|
* @param options CLI options containing entry points and direction
|
|
526
|
+
* @param logger Optional logger for structured output
|
|
522
527
|
* @returns Filtered graph containing only nodes reachable from entry points
|
|
523
528
|
*/
|
|
524
|
-
function filterGraph(graph, options) {
|
|
529
|
+
function filterGraph(graph, options, logger) {
|
|
525
530
|
if (!options.entry || options.entry.length === 0) return graph;
|
|
526
531
|
const includedNodeIds = /* @__PURE__ */ new Set();
|
|
527
532
|
if (options.direction === "both") {
|
|
528
533
|
const upstreamAdjacencyList = buildAdjacencyList(graph, "upstream");
|
|
529
534
|
const upstreamNodes = /* @__PURE__ */ new Set();
|
|
530
|
-
validateAndTraverseEntryPoints(options.entry, graph, upstreamAdjacencyList, upstreamNodes, options);
|
|
535
|
+
validateAndTraverseEntryPoints(options.entry, graph, upstreamAdjacencyList, upstreamNodes, options, logger);
|
|
531
536
|
const downstreamAdjacencyList = buildAdjacencyList(graph, "downstream");
|
|
532
537
|
const downstreamNodes = /* @__PURE__ */ new Set();
|
|
533
|
-
validateAndTraverseEntryPoints(options.entry, graph, downstreamAdjacencyList, downstreamNodes, options);
|
|
538
|
+
validateAndTraverseEntryPoints(options.entry, graph, downstreamAdjacencyList, downstreamNodes, options, logger);
|
|
534
539
|
const combinedNodes = new Set([...upstreamNodes, ...downstreamNodes]);
|
|
535
540
|
for (const nodeId of combinedNodes) includedNodeIds.add(nodeId);
|
|
536
541
|
} else {
|
|
537
542
|
const adjacencyList = buildAdjacencyList(graph, options.direction);
|
|
538
|
-
validateAndTraverseEntryPoints(options.entry, graph, adjacencyList, includedNodeIds, options);
|
|
543
|
+
validateAndTraverseEntryPoints(options.entry, graph, adjacencyList, includedNodeIds, options, logger);
|
|
539
544
|
}
|
|
540
545
|
const filteredNodes = graph.nodes.filter((node) => includedNodeIds.has(node.id));
|
|
541
546
|
const filteredEdges = graph.edges.filter((edge) => includedNodeIds.has(edge.from) && includedNodeIds.has(edge.to));
|
|
@@ -554,9 +559,12 @@ function filterGraph(graph, options) {
|
|
|
554
559
|
}
|
|
555
560
|
return true;
|
|
556
561
|
});
|
|
557
|
-
if (options.verbose) {
|
|
558
|
-
|
|
559
|
-
|
|
562
|
+
if (options.verbose && logger) {
|
|
563
|
+
logger.info(LogCategory.FILTERING, "Filtered graph summary", {
|
|
564
|
+
nodeCount: filteredNodes.length,
|
|
565
|
+
edgeCount: filteredEdges.length
|
|
566
|
+
});
|
|
567
|
+
logger.debug(LogCategory.FILTERING, "Entry points applied", { entryPoints: options.entry });
|
|
560
568
|
}
|
|
561
569
|
return {
|
|
562
570
|
nodes: filteredNodes,
|
|
@@ -663,6 +671,21 @@ var AngularParser = class {
|
|
|
663
671
|
totalCount: 0
|
|
664
672
|
};
|
|
665
673
|
}
|
|
674
|
+
verboseInfo(category, message, context) {
|
|
675
|
+
if (!this._options.verbose || !this._logger) return;
|
|
676
|
+
this._logger.info(category, message, context);
|
|
677
|
+
}
|
|
678
|
+
verboseDebug(category, message, context) {
|
|
679
|
+
if (!this._options.verbose || !this._logger) return;
|
|
680
|
+
this._logger.debug(category, message, context);
|
|
681
|
+
}
|
|
682
|
+
warn(category, message, context) {
|
|
683
|
+
if (this._logger) {
|
|
684
|
+
this._logger.warn(category, message, context);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
ErrorHandler.warn(message, context?.filePath);
|
|
688
|
+
}
|
|
666
689
|
/**
|
|
667
690
|
* Reset global warning deduplication state (useful for testing)
|
|
668
691
|
*/
|
|
@@ -698,8 +721,23 @@ var AngularParser = class {
|
|
|
698
721
|
this._structuredWarnings.categories[category].push(warning);
|
|
699
722
|
this._structuredWarnings.totalCount++;
|
|
700
723
|
const location = warning.line ? `${warning.file}:${warning.line}:${warning.column}` : warning.file;
|
|
701
|
-
|
|
702
|
-
if (
|
|
724
|
+
const formattedWarning = `[${warning.severity.toUpperCase()}] ${warning.message} (${location})`;
|
|
725
|
+
if (this._logger) {
|
|
726
|
+
this._logger.warn(LogCategory.ERROR_RECOVERY, formattedWarning, {
|
|
727
|
+
category,
|
|
728
|
+
filePath: warning.file,
|
|
729
|
+
lineNumber: warning.line,
|
|
730
|
+
suggestion: warning.suggestion
|
|
731
|
+
});
|
|
732
|
+
if (warning.suggestion && this._options.verbose) this._logger.info(LogCategory.ERROR_RECOVERY, warning.suggestion, {
|
|
733
|
+
category,
|
|
734
|
+
filePath: warning.file,
|
|
735
|
+
lineNumber: warning.line
|
|
736
|
+
});
|
|
737
|
+
} else {
|
|
738
|
+
ErrorHandler.warn(formattedWarning, warning.file);
|
|
739
|
+
if (warning.suggestion && this._options.verbose) ErrorHandler.warn(warning.suggestion, warning.file);
|
|
740
|
+
}
|
|
703
741
|
GLOBAL_WARNING_KEYS.add(warnKey);
|
|
704
742
|
}
|
|
705
743
|
}
|
|
@@ -723,12 +761,6 @@ var AngularParser = class {
|
|
|
723
761
|
}
|
|
724
762
|
this._project = new ts_morph.Project({ tsConfigFilePath: tsConfigPath });
|
|
725
763
|
if (!this._project) throw ErrorHandler.createError("Failed to load TypeScript project", "PROJECT_LOAD_FAILED", this._options.project);
|
|
726
|
-
this._project.getSourceFiles();
|
|
727
|
-
const diagnostics = this._project.getProgram().getConfigFileParsingDiagnostics();
|
|
728
|
-
if (diagnostics.length > 0) {
|
|
729
|
-
const message = diagnostics[0].getMessageText();
|
|
730
|
-
throw ErrorHandler.createError(`TypeScript configuration error: ${message}`, "PROJECT_LOAD_FAILED", this._options.project, { diagnosticCount: diagnostics.length });
|
|
731
|
-
}
|
|
732
764
|
} catch (error) {
|
|
733
765
|
if (error instanceof CliError) throw error;
|
|
734
766
|
if (error instanceof Error) {
|
|
@@ -846,8 +878,11 @@ var AngularParser = class {
|
|
|
846
878
|
matchedCount: matchedFiles.length
|
|
847
879
|
});
|
|
848
880
|
if (this._options.verbose) {
|
|
849
|
-
|
|
850
|
-
|
|
881
|
+
this._logger?.info(LogCategory.FILE_PROCESSING, `Filtering to specific file(s): ${matchedFiles.length}`, {
|
|
882
|
+
matchedCount: matchedFiles.length,
|
|
883
|
+
targetCount: targetPaths.length
|
|
884
|
+
});
|
|
885
|
+
for (const file of matchedFiles) this._logger?.debug(LogCategory.FILE_PROCESSING, "Including file", { filePath: file.getFilePath() });
|
|
851
886
|
}
|
|
852
887
|
return matchedFiles;
|
|
853
888
|
}
|
|
@@ -866,14 +901,12 @@ var AngularParser = class {
|
|
|
866
901
|
this._circularTypeRefs.clear();
|
|
867
902
|
this._logger?.time("findDecoratedClasses");
|
|
868
903
|
this._logger?.info(LogCategory.FILE_PROCESSING, "Starting file processing", { fileCount: sourceFiles.length });
|
|
869
|
-
|
|
904
|
+
this._logger?.info(LogCategory.FILE_PROCESSING, "Processing source files", { fileCount: sourceFiles.length });
|
|
870
905
|
for (const sourceFile of sourceFiles) {
|
|
871
906
|
const filePath = sourceFile.getFilePath();
|
|
872
907
|
try {
|
|
873
|
-
|
|
874
|
-
this._logger?.debug(LogCategory.FILE_PROCESSING, "Processing file", { filePath });
|
|
908
|
+
this._logger?.debug(LogCategory.FILE_PROCESSING, "Parsing file", { filePath });
|
|
875
909
|
const classes = sourceFile.getClasses();
|
|
876
|
-
if (this._options.verbose) console.log(`File: ${filePath}, Classes: ${classes.length}`);
|
|
877
910
|
this._logger?.debug(LogCategory.AST_ANALYSIS, "Analyzing classes in file", {
|
|
878
911
|
filePath,
|
|
879
912
|
classCount: classes.length
|
|
@@ -882,7 +915,6 @@ var AngularParser = class {
|
|
|
882
915
|
const parsedClass = this.parseClassDeclaration(classDeclaration);
|
|
883
916
|
if (parsedClass) {
|
|
884
917
|
decoratedClasses.push(parsedClass);
|
|
885
|
-
if (this._options.verbose) console.log(`Found decorated class: ${parsedClass.name} (${parsedClass.kind})`);
|
|
886
918
|
this._logger?.info(LogCategory.AST_ANALYSIS, "Found decorated class", {
|
|
887
919
|
className: parsedClass.name,
|
|
888
920
|
kind: parsedClass.kind,
|
|
@@ -904,7 +936,10 @@ var AngularParser = class {
|
|
|
904
936
|
ErrorHandler.warn(`Failed to parse file (skipping): ${error instanceof Error ? error.message : "Unknown error"}`, filePath);
|
|
905
937
|
}
|
|
906
938
|
}
|
|
907
|
-
|
|
939
|
+
this._logger?.info(LogCategory.FILE_PROCESSING, "File processing summary", {
|
|
940
|
+
processedFiles,
|
|
941
|
+
skippedFiles
|
|
942
|
+
});
|
|
908
943
|
const elapsed = this._logger?.timeEnd("findDecoratedClasses") || 0;
|
|
909
944
|
this._logger?.info(LogCategory.PERFORMANCE, "File processing complete", {
|
|
910
945
|
totalClasses: decoratedClasses.length,
|
|
@@ -923,17 +958,26 @@ var AngularParser = class {
|
|
|
923
958
|
parseClassDeclaration(classDeclaration) {
|
|
924
959
|
const className = classDeclaration.getName();
|
|
925
960
|
if (!className) {
|
|
926
|
-
|
|
961
|
+
const message = "Skipping anonymous class - classes must be named for dependency injection analysis";
|
|
962
|
+
if (this._logger) this._logger.warn(LogCategory.AST_ANALYSIS, message);
|
|
963
|
+
else ErrorHandler.warn(message);
|
|
927
964
|
return null;
|
|
928
965
|
}
|
|
929
966
|
const decorators = classDeclaration.getDecorators();
|
|
930
967
|
if (this._options.verbose) {
|
|
931
968
|
const decoratorNames = decorators.map((d) => this.getDecoratorName(d)).join(", ");
|
|
932
|
-
|
|
969
|
+
this._logger?.debug(LogCategory.AST_ANALYSIS, "Decorator metadata", {
|
|
970
|
+
className,
|
|
971
|
+
decoratorCount: decorators.length,
|
|
972
|
+
decoratorNames
|
|
973
|
+
});
|
|
933
974
|
}
|
|
934
975
|
const angularDecorator = this.findAngularDecorator(decorators);
|
|
935
976
|
if (!angularDecorator) {
|
|
936
|
-
if (this._options.verbose && decorators.length > 0)
|
|
977
|
+
if (this._options.verbose && decorators.length > 0) this._logger?.debug(LogCategory.AST_ANALYSIS, "No Angular decorator found", {
|
|
978
|
+
className,
|
|
979
|
+
decoratorCount: decorators.length
|
|
980
|
+
});
|
|
937
981
|
return null;
|
|
938
982
|
}
|
|
939
983
|
return {
|
|
@@ -1015,14 +1059,14 @@ var AngularParser = class {
|
|
|
1015
1059
|
if (parent && parent.getKind() === ts_morph.SyntaxKind.CallExpression) {
|
|
1016
1060
|
const grandParent = parent.getParent();
|
|
1017
1061
|
if (grandParent && grandParent.getKind() === ts_morph.SyntaxKind.CallExpression) {
|
|
1018
|
-
|
|
1019
|
-
|
|
1062
|
+
this.warn(LogCategory.AST_ANALYSIS, "Skipping anonymous class - classes must be named for dependency injection analysis", { filePath: sourceFile.getFilePath() });
|
|
1063
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, `Anonymous class found in ${sourceFile.getFilePath()}`);
|
|
1020
1064
|
}
|
|
1021
1065
|
}
|
|
1022
1066
|
}
|
|
1023
1067
|
});
|
|
1024
1068
|
} catch (error) {
|
|
1025
|
-
|
|
1069
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, `Could not detect anonymous classes in ${sourceFile.getFilePath()}: ${error}`);
|
|
1026
1070
|
}
|
|
1027
1071
|
}
|
|
1028
1072
|
/**
|
|
@@ -1051,11 +1095,11 @@ var AngularParser = class {
|
|
|
1051
1095
|
};
|
|
1052
1096
|
const startTime = performance.now();
|
|
1053
1097
|
if (this._options.verbose && this._options.includeDecorators) {
|
|
1054
|
-
|
|
1098
|
+
this.verboseInfo(LogCategory.AST_ANALYSIS, "=== Decorator Analysis ===");
|
|
1055
1099
|
const className = classDeclaration.getName() || "unknown";
|
|
1056
|
-
|
|
1100
|
+
this.verboseInfo(LogCategory.AST_ANALYSIS, `Analyzing decorators for class: ${className}`, { className });
|
|
1057
1101
|
}
|
|
1058
|
-
if (this._options.verbose && !this._options.includeDecorators)
|
|
1102
|
+
if (this._options.verbose && !this._options.includeDecorators) this.verboseInfo(LogCategory.AST_ANALYSIS, "Decorator analysis disabled - --include-decorators flag not set");
|
|
1059
1103
|
const constructors = classDeclaration.getConstructors();
|
|
1060
1104
|
if (constructors.length > 0) {
|
|
1061
1105
|
const parameters = constructors[0].getParameters();
|
|
@@ -1109,7 +1153,7 @@ var AngularParser = class {
|
|
|
1109
1153
|
* @returns Token string or null
|
|
1110
1154
|
*/
|
|
1111
1155
|
handleGenericType(typeText, _filePath, _lineNumber, _columnNumber) {
|
|
1112
|
-
|
|
1156
|
+
this.verboseDebug(LogCategory.TYPE_RESOLUTION, `Processing generic type: ${typeText}`);
|
|
1113
1157
|
return typeText;
|
|
1114
1158
|
}
|
|
1115
1159
|
/**
|
|
@@ -1196,7 +1240,10 @@ var AngularParser = class {
|
|
|
1196
1240
|
*/
|
|
1197
1241
|
extractTypeTokenEnhanced(typeNode, filePath, lineNumber, columnNumber) {
|
|
1198
1242
|
const typeText = typeNode.getText();
|
|
1199
|
-
|
|
1243
|
+
this.verboseInfo(LogCategory.TYPE_RESOLUTION, `Type resolution steps: Processing '${typeText}' at ${filePath}:${lineNumber}:${columnNumber}`, {
|
|
1244
|
+
filePath,
|
|
1245
|
+
lineNumber
|
|
1246
|
+
});
|
|
1200
1247
|
if (this.isCircularTypeReference(typeText, typeNode)) {
|
|
1201
1248
|
this.addStructuredWarning("circularReferences", {
|
|
1202
1249
|
type: "circular_type_reference",
|
|
@@ -1260,7 +1307,10 @@ var AngularParser = class {
|
|
|
1260
1307
|
* @returns Resolved token or null
|
|
1261
1308
|
*/
|
|
1262
1309
|
resolveInferredTypeEnhanced(type, typeText, param, filePath, lineNumber, columnNumber) {
|
|
1263
|
-
|
|
1310
|
+
this.verboseInfo(LogCategory.TYPE_RESOLUTION, `Attempting to resolve inferred type: ${typeText}`, {
|
|
1311
|
+
filePath,
|
|
1312
|
+
lineNumber
|
|
1313
|
+
});
|
|
1264
1314
|
const symbol = type.getSymbol?.();
|
|
1265
1315
|
if (symbol) {
|
|
1266
1316
|
const symbolName = symbol.getName();
|
|
@@ -1362,14 +1412,20 @@ var AngularParser = class {
|
|
|
1362
1412
|
cacheKey = `${filePath}:${parameterName}:${typeText}`;
|
|
1363
1413
|
if (this._typeResolutionCache.has(cacheKey)) {
|
|
1364
1414
|
const cachedResult = this._typeResolutionCache.get(cacheKey);
|
|
1365
|
-
|
|
1415
|
+
this.verboseDebug(LogCategory.TYPE_RESOLUTION, `Cache hit for parameter '${parameterName}': ${typeText}`, {
|
|
1416
|
+
parameterName,
|
|
1417
|
+
cacheKey
|
|
1418
|
+
});
|
|
1366
1419
|
return cachedResult ? {
|
|
1367
1420
|
token: cachedResult,
|
|
1368
1421
|
flags,
|
|
1369
1422
|
parameterName
|
|
1370
1423
|
} : null;
|
|
1371
1424
|
}
|
|
1372
|
-
|
|
1425
|
+
this.verboseDebug(LogCategory.TYPE_RESOLUTION, `Cache miss for parameter '${parameterName}': ${typeText}`, {
|
|
1426
|
+
parameterName,
|
|
1427
|
+
cacheKey
|
|
1428
|
+
});
|
|
1373
1429
|
const resolvedToken = this.resolveInferredTypeEnhanced(type, typeText, param, filePath, lineNumber, columnNumber);
|
|
1374
1430
|
this._typeResolutionCache.set(cacheKey, resolvedToken);
|
|
1375
1431
|
if (resolvedToken) return {
|
|
@@ -1463,7 +1519,10 @@ var AngularParser = class {
|
|
|
1463
1519
|
if (this._options.verbose) {
|
|
1464
1520
|
const className = classDeclaration.getName() || "unknown";
|
|
1465
1521
|
const filePath = classDeclaration.getSourceFile().getFilePath();
|
|
1466
|
-
|
|
1522
|
+
this.warn(LogCategory.ERROR_RECOVERY, `Failed to extract inject() dependencies for class '${className}' in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1523
|
+
className,
|
|
1524
|
+
filePath
|
|
1525
|
+
});
|
|
1467
1526
|
}
|
|
1468
1527
|
}
|
|
1469
1528
|
return dependencies;
|
|
@@ -1499,7 +1558,10 @@ var AngularParser = class {
|
|
|
1499
1558
|
if (this._options.verbose) {
|
|
1500
1559
|
const propertyName = property.getName() || "unknown";
|
|
1501
1560
|
const filePath = property.getSourceFile().getFilePath();
|
|
1502
|
-
|
|
1561
|
+
this.warn(LogCategory.ERROR_RECOVERY, `Failed to parse inject() property '${propertyName}' in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1562
|
+
propertyName,
|
|
1563
|
+
filePath
|
|
1564
|
+
});
|
|
1503
1565
|
}
|
|
1504
1566
|
return null;
|
|
1505
1567
|
}
|
|
@@ -1525,7 +1587,7 @@ var AngularParser = class {
|
|
|
1525
1587
|
const propertyAssignment = prop;
|
|
1526
1588
|
const name = propertyAssignment.getName();
|
|
1527
1589
|
if (!supportedOptions.has(name)) {
|
|
1528
|
-
if (this._options.verbose)
|
|
1590
|
+
if (this._options.verbose) this.warn(LogCategory.AST_ANALYSIS, `Unknown inject() option: '${name}' - ignoring`);
|
|
1529
1591
|
continue;
|
|
1530
1592
|
}
|
|
1531
1593
|
const initializer = propertyAssignment.getInitializer();
|
|
@@ -1545,7 +1607,7 @@ var AngularParser = class {
|
|
|
1545
1607
|
}
|
|
1546
1608
|
}
|
|
1547
1609
|
} catch (error) {
|
|
1548
|
-
if (this._options.verbose)
|
|
1610
|
+
if (this._options.verbose) this.warn(LogCategory.ERROR_RECOVERY, `Failed to parse inject() options: ${error instanceof Error ? error.message : String(error)}`);
|
|
1549
1611
|
}
|
|
1550
1612
|
return flags;
|
|
1551
1613
|
}
|
|
@@ -1590,14 +1652,21 @@ var AngularParser = class {
|
|
|
1590
1652
|
name: decoratorName,
|
|
1591
1653
|
reason: "Unknown or unsupported decorator"
|
|
1592
1654
|
});
|
|
1593
|
-
|
|
1655
|
+
const className = parameter.getFirstAncestorByKind(ts_morph.SyntaxKind.ClassDeclaration)?.getName() || void 0;
|
|
1656
|
+
this.warn(LogCategory.AST_ANALYSIS, `Unknown or unsupported decorator: @${decoratorName}() - This decorator is not recognized as an Angular DI decorator and will be ignored.`, {
|
|
1657
|
+
filePath: parameter.getSourceFile().getFilePath(),
|
|
1658
|
+
className
|
|
1659
|
+
});
|
|
1594
1660
|
}
|
|
1595
1661
|
}
|
|
1596
1662
|
} catch (error) {
|
|
1597
1663
|
if (this._options.verbose) {
|
|
1598
1664
|
const paramName = parameter.getName();
|
|
1599
1665
|
const filePath = parameter.getSourceFile().getFilePath();
|
|
1600
|
-
|
|
1666
|
+
this.warn(LogCategory.ERROR_RECOVERY, `Failed to extract decorators for parameter '${paramName}' in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
1667
|
+
paramName,
|
|
1668
|
+
filePath
|
|
1669
|
+
});
|
|
1601
1670
|
}
|
|
1602
1671
|
return {};
|
|
1603
1672
|
}
|
|
@@ -1622,7 +1691,7 @@ var AngularParser = class {
|
|
|
1622
1691
|
}
|
|
1623
1692
|
return false;
|
|
1624
1693
|
} catch (error) {
|
|
1625
|
-
if (this._options.verbose)
|
|
1694
|
+
if (this._options.verbose) this.warn(LogCategory.ERROR_RECOVERY, `Could not verify inject() import in ${sourceFile.getFilePath()}: ${error instanceof Error ? error.message : String(error)}`, { filePath: sourceFile.getFilePath() });
|
|
1626
1695
|
return true;
|
|
1627
1696
|
}
|
|
1628
1697
|
}
|
|
@@ -1636,15 +1705,19 @@ var AngularParser = class {
|
|
|
1636
1705
|
if (!expression) return null;
|
|
1637
1706
|
if (expression.getKind() !== ts_morph.SyntaxKind.CallExpression) return null;
|
|
1638
1707
|
const callExpression = expression;
|
|
1708
|
+
const sourceFile = expression.getSourceFile();
|
|
1709
|
+
const warnVerbose = (message) => {
|
|
1710
|
+
if (this._options.verbose) this.warn(LogCategory.AST_ANALYSIS, message, { filePath: sourceFile.getFilePath() });
|
|
1711
|
+
};
|
|
1639
1712
|
try {
|
|
1640
1713
|
const callIdentifier = callExpression.getExpression();
|
|
1641
1714
|
if (callIdentifier.getKind() !== ts_morph.SyntaxKind.Identifier) return null;
|
|
1642
1715
|
if (callIdentifier.getText() !== "inject") return null;
|
|
1643
|
-
const sourceFile = expression.getSourceFile();
|
|
1644
|
-
if (!this.isAngularInjectImported(sourceFile)) return null;
|
|
1716
|
+
const sourceFile$1 = expression.getSourceFile();
|
|
1717
|
+
if (!this.isAngularInjectImported(sourceFile$1)) return null;
|
|
1645
1718
|
const args = callExpression.getArguments();
|
|
1646
1719
|
if (args.length === 0) {
|
|
1647
|
-
|
|
1720
|
+
warnVerbose("inject() called without token parameter - skipping");
|
|
1648
1721
|
return null;
|
|
1649
1722
|
}
|
|
1650
1723
|
const tokenArg = args[0];
|
|
@@ -1652,22 +1725,22 @@ var AngularParser = class {
|
|
|
1652
1725
|
if (tokenArg.getKind() === ts_morph.SyntaxKind.StringLiteral) {
|
|
1653
1726
|
token = tokenArg.getText().slice(1, -1);
|
|
1654
1727
|
if (!token) {
|
|
1655
|
-
|
|
1728
|
+
warnVerbose("inject() called with empty string token - skipping");
|
|
1656
1729
|
return null;
|
|
1657
1730
|
}
|
|
1658
1731
|
} else if (tokenArg.getKind() === ts_morph.SyntaxKind.Identifier) {
|
|
1659
1732
|
token = tokenArg.getText();
|
|
1660
1733
|
if (token === "undefined" || token === "null") {
|
|
1661
|
-
|
|
1734
|
+
warnVerbose(`inject() called with ${token} token - skipping`);
|
|
1662
1735
|
return null;
|
|
1663
1736
|
}
|
|
1664
1737
|
} else if (tokenArg.getKind() === ts_morph.SyntaxKind.NullKeyword) {
|
|
1665
|
-
|
|
1738
|
+
warnVerbose("inject() called with null token - skipping");
|
|
1666
1739
|
return null;
|
|
1667
1740
|
} else {
|
|
1668
1741
|
token = tokenArg.getText();
|
|
1669
1742
|
if (!token) {
|
|
1670
|
-
|
|
1743
|
+
warnVerbose("inject() called with invalid token expression - skipping");
|
|
1671
1744
|
return null;
|
|
1672
1745
|
}
|
|
1673
1746
|
}
|
|
@@ -1675,9 +1748,7 @@ var AngularParser = class {
|
|
|
1675
1748
|
if (args.length > 1 && this._options.includeDecorators) {
|
|
1676
1749
|
const optionsArg = args[1];
|
|
1677
1750
|
if (optionsArg.getKind() === ts_morph.SyntaxKind.ObjectLiteralExpression) flags = this.parseInjectOptions(optionsArg);
|
|
1678
|
-
else if (optionsArg.getKind() !== ts_morph.SyntaxKind.NullKeyword && optionsArg.getKind() !== ts_morph.SyntaxKind.UndefinedKeyword) {
|
|
1679
|
-
if (this._options.verbose) console.warn(`inject() called with invalid options type: ${optionsArg.getKindName()} - expected object literal`);
|
|
1680
|
-
}
|
|
1751
|
+
else if (optionsArg.getKind() !== ts_morph.SyntaxKind.NullKeyword && optionsArg.getKind() !== ts_morph.SyntaxKind.UndefinedKeyword) warnVerbose(`inject() called with invalid options type: ${optionsArg.getKindName()} - expected object literal`);
|
|
1681
1752
|
}
|
|
1682
1753
|
return {
|
|
1683
1754
|
token,
|
|
@@ -1685,10 +1756,7 @@ var AngularParser = class {
|
|
|
1685
1756
|
source: "inject"
|
|
1686
1757
|
};
|
|
1687
1758
|
} catch (error) {
|
|
1688
|
-
if (this._options.verbose) {
|
|
1689
|
-
const filePath = expression.getSourceFile().getFilePath();
|
|
1690
|
-
console.warn(`Warning: Failed to analyze inject() call in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1691
|
-
}
|
|
1759
|
+
if (this._options.verbose) warnVerbose(`Failed to analyze inject() call in ${sourceFile.getFilePath()}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1692
1760
|
return null;
|
|
1693
1761
|
}
|
|
1694
1762
|
}
|
|
@@ -1700,8 +1768,11 @@ var AngularParser = class {
|
|
|
1700
1768
|
*/
|
|
1701
1769
|
collectVerboseStats(param, dependency, verboseStats) {
|
|
1702
1770
|
const paramName = param.getName();
|
|
1703
|
-
|
|
1704
|
-
|
|
1771
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "Parameter analysis", {
|
|
1772
|
+
paramName,
|
|
1773
|
+
token: dependency.token,
|
|
1774
|
+
flags: dependency.flags
|
|
1775
|
+
});
|
|
1705
1776
|
if (Object.keys(dependency.flags || {}).length > 0) {
|
|
1706
1777
|
verboseStats.parametersWithDecorators++;
|
|
1707
1778
|
if (dependency.flags?.optional) verboseStats.decoratorCounts.optional++;
|
|
@@ -1721,7 +1792,10 @@ var AngularParser = class {
|
|
|
1721
1792
|
"Host"
|
|
1722
1793
|
].includes(decoratorName)) {
|
|
1723
1794
|
hasLegacyDecorators = true;
|
|
1724
|
-
|
|
1795
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, `Legacy decorator detected`, {
|
|
1796
|
+
decoratorName,
|
|
1797
|
+
paramName
|
|
1798
|
+
});
|
|
1725
1799
|
}
|
|
1726
1800
|
}
|
|
1727
1801
|
const initializer = param.getInitializer();
|
|
@@ -1732,29 +1806,44 @@ var AngularParser = class {
|
|
|
1732
1806
|
hasInjectPattern = true;
|
|
1733
1807
|
verboseStats.injectPatternsUsed++;
|
|
1734
1808
|
const flagsStr = JSON.stringify(dependency.flags);
|
|
1735
|
-
|
|
1809
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "inject() options detected", {
|
|
1810
|
+
paramName,
|
|
1811
|
+
flags: flagsStr
|
|
1812
|
+
});
|
|
1736
1813
|
}
|
|
1737
1814
|
}
|
|
1738
1815
|
}
|
|
1739
1816
|
if (hasLegacyDecorators) {
|
|
1740
1817
|
verboseStats.legacyDecoratorsUsed++;
|
|
1741
1818
|
if (hasInjectPattern) {
|
|
1742
|
-
|
|
1743
|
-
|
|
1819
|
+
this.verboseInfo(LogCategory.AST_ANALYSIS, "Decorator precedence analysis", {
|
|
1820
|
+
paramName,
|
|
1821
|
+
legacyDecorators: true,
|
|
1822
|
+
injectPattern: true
|
|
1823
|
+
});
|
|
1744
1824
|
const appliedFlags = Object.keys(dependency.flags || {}).filter((key) => dependency.flags?.[key] === true).map((key) => `@${key.charAt(0).toUpperCase() + key.slice(1)}`).join(", ");
|
|
1745
|
-
|
|
1825
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "Applied decorator flags", {
|
|
1826
|
+
paramName,
|
|
1827
|
+
appliedFlags
|
|
1828
|
+
});
|
|
1746
1829
|
const injectResult = this.analyzeInjectCall(initializer);
|
|
1747
1830
|
if (injectResult && Object.keys(injectResult.flags).length > 0) {
|
|
1748
1831
|
const overriddenFlags = JSON.stringify(injectResult.flags);
|
|
1749
|
-
|
|
1832
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "Overridden inject() options", {
|
|
1833
|
+
paramName,
|
|
1834
|
+
overriddenFlags
|
|
1835
|
+
});
|
|
1750
1836
|
}
|
|
1751
1837
|
const finalFlags = JSON.stringify(dependency.flags);
|
|
1752
|
-
|
|
1838
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "Final decorator flags", {
|
|
1839
|
+
paramName,
|
|
1840
|
+
finalFlags
|
|
1841
|
+
});
|
|
1753
1842
|
}
|
|
1754
1843
|
}
|
|
1755
1844
|
} else {
|
|
1756
1845
|
verboseStats.parametersWithoutDecorators++;
|
|
1757
|
-
|
|
1846
|
+
this.verboseDebug(LogCategory.AST_ANALYSIS, "No decorators detected", { paramName });
|
|
1758
1847
|
}
|
|
1759
1848
|
}
|
|
1760
1849
|
/**
|
|
@@ -1764,69 +1853,24 @@ var AngularParser = class {
|
|
|
1764
1853
|
* @param classDeclaration Class being analyzed
|
|
1765
1854
|
*/
|
|
1766
1855
|
outputVerboseAnalysis(dependencies, verboseStats, classDeclaration) {
|
|
1767
|
-
if (!this._options.verbose) return;
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
if (initializer) {
|
|
1786
|
-
const injectResult = this.analyzeInjectCall(initializer);
|
|
1787
|
-
if (injectResult) {
|
|
1788
|
-
if (injectResult.token.startsWith("\"") && injectResult.token.endsWith("\"")) console.log(`String token: ${injectResult.token}`);
|
|
1789
|
-
else console.log(`Service token: ${injectResult.token}`);
|
|
1790
|
-
if (Object.keys(injectResult.flags).length > 0) {
|
|
1791
|
-
const flagsStr = JSON.stringify(injectResult.flags);
|
|
1792
|
-
console.log(`inject() options detected: ${flagsStr}`);
|
|
1793
|
-
} else console.log("inject() with no options");
|
|
1794
|
-
}
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
if (verboseStats.skippedDecorators.length > 0) {
|
|
1801
|
-
console.log("Skipped Decorators");
|
|
1802
|
-
for (const skipped of verboseStats.skippedDecorators) {
|
|
1803
|
-
console.log(`${skipped.name}`);
|
|
1804
|
-
console.log(`Reason: ${skipped.reason}`);
|
|
1805
|
-
}
|
|
1806
|
-
console.log(`Total skipped: ${verboseStats.skippedDecorators.length}`);
|
|
1807
|
-
}
|
|
1808
|
-
console.log("Performance Metrics");
|
|
1809
|
-
console.log(`Decorator processing time: ${verboseStats.totalProcessingTime.toFixed(2)}ms`);
|
|
1810
|
-
console.log(`Total parameters analyzed: ${verboseStats.totalParameters}`);
|
|
1811
|
-
if (verboseStats.totalParameters > 0) {
|
|
1812
|
-
const avgTime = verboseStats.totalProcessingTime / verboseStats.totalParameters;
|
|
1813
|
-
console.log(`Average time per parameter: ${avgTime.toFixed(3)}ms`);
|
|
1814
|
-
}
|
|
1815
|
-
console.log("=== Analysis Summary ===");
|
|
1816
|
-
console.log(`Total dependencies: ${dependencies.length}`);
|
|
1817
|
-
console.log(`With decorator flags: ${verboseStats.parametersWithDecorators}`);
|
|
1818
|
-
console.log(`Without decorator flags: ${verboseStats.parametersWithoutDecorators}`);
|
|
1819
|
-
console.log(`Legacy decorators used: ${verboseStats.legacyDecoratorsUsed}`);
|
|
1820
|
-
console.log(`inject() patterns used: ${verboseStats.injectPatternsUsed}`);
|
|
1821
|
-
if (verboseStats.skippedDecorators.length > 0) console.log(`Unknown decorators skipped: ${verboseStats.skippedDecorators.length}`);
|
|
1822
|
-
if (verboseStats.parametersWithDecorators > 0) {
|
|
1823
|
-
console.log("Flags distribution:");
|
|
1824
|
-
if (verboseStats.decoratorCounts.optional > 0) console.log(`optional: ${verboseStats.decoratorCounts.optional}`);
|
|
1825
|
-
if (verboseStats.decoratorCounts.self > 0) console.log(`self: ${verboseStats.decoratorCounts.self}`);
|
|
1826
|
-
if (verboseStats.decoratorCounts.skipSelf > 0) console.log(`skipSelf: ${verboseStats.decoratorCounts.skipSelf}`);
|
|
1827
|
-
if (verboseStats.decoratorCounts.host > 0) console.log(`host: ${verboseStats.decoratorCounts.host}`);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1856
|
+
if (!this._options.verbose || !this._logger) return;
|
|
1857
|
+
const className = classDeclaration.getName() || "unknown";
|
|
1858
|
+
this._logger.info(LogCategory.AST_ANALYSIS, "Decorator analysis summary", {
|
|
1859
|
+
className,
|
|
1860
|
+
decoratorCounts: verboseStats.decoratorCounts,
|
|
1861
|
+
parametersWithDecorators: verboseStats.parametersWithDecorators,
|
|
1862
|
+
parametersWithoutDecorators: verboseStats.parametersWithoutDecorators,
|
|
1863
|
+
injectPatternsUsed: verboseStats.injectPatternsUsed,
|
|
1864
|
+
skippedDecorators: verboseStats.skippedDecorators.length,
|
|
1865
|
+
legacyDecoratorsUsed: verboseStats.legacyDecoratorsUsed,
|
|
1866
|
+
totalProcessingTime: verboseStats.totalProcessingTime,
|
|
1867
|
+
totalParameters: verboseStats.totalParameters,
|
|
1868
|
+
dependencyCount: dependencies.length
|
|
1869
|
+
});
|
|
1870
|
+
if (verboseStats.skippedDecorators.length > 0) this._logger.debug(LogCategory.AST_ANALYSIS, "Skipped decorators", {
|
|
1871
|
+
skippedDecorators: verboseStats.skippedDecorators,
|
|
1872
|
+
className
|
|
1873
|
+
});
|
|
1830
1874
|
}
|
|
1831
1875
|
};
|
|
1832
1876
|
|
|
@@ -2015,7 +2059,7 @@ program.action(async (filePaths = [], options) => {
|
|
|
2015
2059
|
}
|
|
2016
2060
|
if (cliOptions.entry && cliOptions.entry.length > 0) {
|
|
2017
2061
|
if (cliOptions.verbose) console.log(`🔍 Filtering graph by entry points: ${cliOptions.entry.join(", ")}`);
|
|
2018
|
-
graph = filterGraph(graph, cliOptions);
|
|
2062
|
+
graph = filterGraph(graph, cliOptions, logger);
|
|
2019
2063
|
if (cliOptions.verbose) console.log(`✅ Filtered graph: ${graph.nodes.length} nodes, ${graph.edges.length} edges`);
|
|
2020
2064
|
}
|
|
2021
2065
|
let formatter;
|