codesearch-mcp 0.2.3-alpha → 0.4.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -5
- package/LICENSE +661 -21
- package/README.md +76 -40
- package/dist/app.js +17 -38
- package/dist/src/ast/CallExtractor.d.ts +8 -0
- package/dist/src/ast/CallExtractor.js +68 -0
- package/dist/src/ast/GraphqlNameExtractor.d.ts +22 -0
- package/dist/src/ast/GraphqlNameExtractor.js +240 -0
- package/dist/src/ast/ImportEdgeExtractor.d.ts +11 -0
- package/dist/src/ast/ImportEdgeExtractor.js +129 -0
- package/dist/src/ast/IndexedNodeResolver.d.ts +14 -0
- package/dist/src/ast/IndexedNodeResolver.js +217 -0
- package/dist/src/ast/MemberExtractor.d.ts +8 -0
- package/dist/src/ast/MemberExtractor.js +129 -0
- package/dist/src/ast/TopLevelSymbolExtractor.d.ts +5 -0
- package/dist/src/ast/TopLevelSymbolExtractor.js +86 -0
- package/dist/src/ast/graphql-grammar.d.ts +24 -0
- package/dist/src/ast/graphql-grammar.js +93 -0
- package/dist/src/ast/node-helpers.d.ts +11 -0
- package/dist/src/ast/node-helpers.js +55 -0
- package/dist/src/chunking/AstChunker.d.ts +12 -0
- package/dist/src/chunking/AstChunker.js +30 -0
- package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
- package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
- package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
- package/dist/src/chunking/FragmentMerger.d.ts +11 -0
- package/dist/src/chunking/FragmentMerger.js +61 -0
- package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
- package/dist/src/chunking/ImportOnlyFilter.js +14 -0
- package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
- package/dist/src/chunking/LangchainTextSplitter.js +133 -0
- package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
- package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
- package/dist/src/chunking/RunPacker.d.ts +12 -0
- package/dist/src/chunking/RunPacker.js +84 -0
- package/dist/src/chunking/create-chunker.d.ts +3 -0
- package/dist/src/chunking/create-chunker.js +15 -0
- package/dist/src/chunking/interfaces.d.ts +27 -0
- package/dist/src/commands/ConfigCommand.js +1 -9
- package/dist/src/commands/EvalCommand.d.ts +10 -5
- package/dist/src/commands/EvalCommand.js +60 -149
- package/dist/src/commands/IndexCommand.d.ts +10 -4
- package/dist/src/commands/IndexCommand.js +52 -146
- package/dist/src/commands/McpCommand.d.ts +6 -3
- package/dist/src/commands/McpCommand.js +19 -20
- package/dist/src/commands/WebCommand.d.ts +9 -3
- package/dist/src/commands/WebCommand.js +17 -11
- package/dist/src/commands/eval/baseline-comparison.d.ts +6 -0
- package/dist/src/commands/eval/baseline-comparison.js +54 -0
- package/dist/src/commands/eval/eval-args.d.ts +6 -0
- package/dist/src/commands/eval/eval-args.js +71 -0
- package/dist/src/commands/eval/eval-corpora.d.ts +6 -0
- package/dist/src/commands/eval/eval-corpora.js +76 -0
- package/dist/src/commands/eval/eval-metadata.d.ts +17 -0
- package/dist/src/commands/eval/eval-metadata.js +75 -0
- package/dist/src/commands/eval/index-inspector.d.ts +3 -0
- package/dist/src/commands/eval/index-inspector.js +21 -0
- package/dist/src/commands/eval/staged-search.d.ts +3 -0
- package/dist/src/commands/eval/staged-search.js +20 -0
- package/dist/src/commands/eval/suite-loader.d.ts +8 -0
- package/dist/src/commands/eval/suite-loader.js +52 -0
- package/dist/src/commands/index/backend-probe.d.ts +2 -0
- package/dist/src/commands/index/backend-probe.js +15 -0
- package/dist/src/commands/index/embedding-setup.d.ts +3 -0
- package/dist/src/commands/index/embedding-setup.js +16 -0
- package/dist/src/commands/index/index-summary.d.ts +2 -0
- package/dist/src/commands/index/index-summary.js +22 -0
- package/dist/src/commands/index/legacy-ollama-guard.d.ts +3 -0
- package/dist/src/commands/index/legacy-ollama-guard.js +23 -0
- package/dist/src/commands/index/prune-step.d.ts +3 -0
- package/dist/src/commands/index/prune-step.js +22 -0
- package/dist/src/commands/index/scan-targets.d.ts +8 -0
- package/dist/src/commands/index/scan-targets.js +107 -0
- package/dist/src/config-error.d.ts +0 -4
- package/dist/src/config-error.js +0 -4
- package/dist/src/config-store.d.ts +1 -55
- package/dist/src/config-store.js +11 -73
- package/dist/src/config.d.ts +9 -18
- package/dist/src/config.js +36 -35
- package/dist/src/constants.d.ts +17 -28
- package/dist/src/constants.js +35 -29
- package/dist/src/container.d.ts +10 -2
- package/dist/src/container.js +19 -8
- package/dist/src/db/drizzle-types.d.ts +7 -0
- package/dist/src/db/drizzle-types.js +2 -0
- package/dist/src/db/index.d.ts +1 -0
- package/dist/src/db/schema-steps.d.ts +8 -0
- package/dist/src/db/schema-steps.js +105 -0
- package/dist/src/db/schema.d.ts +17 -0
- package/dist/src/db/schema.js +1 -6
- package/dist/src/declarations/DeclarationTree.d.ts +19 -0
- package/dist/src/declarations/DeclarationTree.js +66 -0
- package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
- package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
- package/dist/src/declarations/SourceLines.d.ts +37 -0
- package/dist/src/declarations/SourceLines.js +415 -0
- package/dist/src/dependency-traversal.d.ts +0 -6
- package/dist/src/dependency-traversal.js +0 -6
- package/dist/src/enriched-prompt.d.ts +0 -6
- package/dist/src/enriched-prompt.js +0 -6
- package/dist/src/error-record.d.ts +12 -0
- package/dist/src/error-record.js +248 -0
- package/dist/src/eval-corpus.d.ts +16 -0
- package/dist/src/eval-corpus.js +169 -0
- package/dist/src/eval-results.d.ts +6 -3
- package/dist/src/eval-results.js +323 -26
- package/dist/src/eval-vector-index.d.ts +5 -0
- package/dist/src/eval-vector-index.js +60 -0
- package/dist/src/file-utils.d.ts +0 -5
- package/dist/src/file-utils.js +0 -5
- package/dist/src/graph.d.ts +0 -25
- package/dist/src/graph.js +0 -28
- package/dist/src/help.js +9 -1
- package/dist/src/hnsw-settings.d.ts +3 -0
- package/dist/src/hnsw-settings.js +24 -0
- package/dist/src/index-args.d.ts +13 -0
- package/dist/src/index-args.js +48 -0
- package/dist/src/indexing/AnalysisBatcher.d.ts +12 -0
- package/dist/src/indexing/AnalysisBatcher.js +28 -0
- package/dist/src/indexing/ChunkEmbedder.d.ts +24 -0
- package/dist/src/indexing/ChunkEmbedder.js +37 -0
- package/dist/src/indexing/ConsoleScanReporter.d.ts +10 -0
- package/dist/src/indexing/ConsoleScanReporter.js +73 -0
- package/dist/src/indexing/EmbeddingSchemaPreparer.d.ts +14 -0
- package/dist/src/indexing/EmbeddingSchemaPreparer.js +66 -0
- package/dist/src/indexing/FileIngestor.d.ts +22 -0
- package/dist/src/indexing/FileIngestor.js +116 -0
- package/dist/src/indexing/IndexStore.d.ts +24 -0
- package/dist/src/indexing/IndexStore.js +69 -0
- package/dist/src/indexing/StaleFilePruner.d.ts +8 -0
- package/dist/src/indexing/StaleFilePruner.js +55 -0
- package/dist/src/indexing/analysis-rows.d.ts +4 -0
- package/dist/src/indexing/analysis-rows.js +65 -0
- package/dist/src/indexing/project-notices.d.ts +2 -0
- package/dist/src/indexing/project-notices.js +63 -0
- package/dist/src/interfaces/IAstParser.d.ts +4 -40
- package/dist/src/interfaces/ICallGraph.d.ts +28 -0
- package/dist/src/interfaces/ICallGraph.js +2 -0
- package/dist/src/interfaces/IChunkSearch.d.ts +42 -0
- package/dist/src/interfaces/IChunkSearch.js +2 -0
- package/dist/src/interfaces/IChunker.d.ts +3 -12
- package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
- package/dist/src/interfaces/ICorpusInspector.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +8 -24
- package/dist/src/interfaces/IDependencyGraph.d.ts +24 -0
- package/dist/src/interfaces/IDependencyGraph.js +2 -0
- package/dist/src/interfaces/IEmbedder.d.ts +0 -9
- package/dist/src/interfaces/IIndexIdentityGuard.d.ts +3 -0
- package/dist/src/interfaces/IIndexIdentityGuard.js +2 -0
- package/dist/src/interfaces/IIndexedNodeResolver.d.ts +10 -0
- package/dist/src/interfaces/IIndexedNodeResolver.js +2 -0
- package/dist/src/interfaces/ILogger.d.ts +1 -11
- package/dist/src/interfaces/IMcpReadiness.d.ts +12 -0
- package/dist/src/interfaces/IMcpReadiness.js +2 -0
- package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
- package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
- package/dist/src/interfaces/IProjectCatalog.d.ts +13 -0
- package/dist/src/interfaces/IProjectCatalog.js +2 -0
- package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
- package/dist/src/interfaces/IProjectRoots.d.ts +3 -0
- package/dist/src/interfaces/IProjectRoots.js +2 -0
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +153 -12
- package/dist/src/interfaces/IScanReporter.d.ts +13 -0
- package/dist/src/interfaces/IScanReporter.js +2 -0
- package/dist/src/interfaces/ISymbolLookup.d.ts +49 -0
- package/dist/src/interfaces/ISymbolLookup.js +2 -0
- package/dist/src/interfaces/IToolPaths.d.ts +62 -0
- package/dist/src/interfaces/IToolPaths.js +2 -0
- package/dist/src/interfaces/IWorkspaceFiles.d.ts +28 -0
- package/dist/src/interfaces/IWorkspaceFiles.js +2 -0
- package/dist/src/interfaces/SymbolKind.d.ts +1 -0
- package/dist/src/interfaces/SymbolKind.js +2 -0
- package/dist/src/interfaces/index.d.ts +12 -1
- package/dist/src/interrupt.js +0 -2
- package/dist/src/owner-path.d.ts +9 -10
- package/dist/src/owner-path.js +11 -23
- package/dist/src/project-scope.d.ts +1 -12
- package/dist/src/project-scope.js +5 -12
- package/dist/src/project-selector.d.ts +0 -5
- package/dist/src/project-selector.js +0 -11
- package/dist/src/retrieval-eval.d.ts +15 -6
- package/dist/src/retrieval-eval.js +388 -59
- package/dist/src/search/CallGraph.d.ts +9 -0
- package/dist/src/search/CallGraph.js +84 -0
- package/dist/src/search/ChunkSearch.d.ts +23 -0
- package/dist/src/search/ChunkSearch.js +115 -0
- package/dist/src/search/DependencyGraph.d.ts +19 -0
- package/dist/src/search/DependencyGraph.js +180 -0
- package/dist/src/search/IndexIdentityGuard.d.ts +11 -0
- package/dist/src/search/IndexIdentityGuard.js +37 -0
- package/dist/src/search/ProjectCatalog.d.ts +11 -0
- package/dist/src/search/ProjectCatalog.js +38 -0
- package/dist/src/search/SymbolLookup.d.ts +24 -0
- package/dist/src/search/SymbolLookup.js +97 -0
- package/dist/src/search/chunks/CandidateFetcher.d.ts +19 -0
- package/dist/src/search/chunks/CandidateFetcher.js +40 -0
- package/dist/src/search/chunks/IterativeScanGate.d.ts +10 -0
- package/dist/src/search/chunks/IterativeScanGate.js +42 -0
- package/dist/src/search/chunks/RerankStage.d.ts +15 -0
- package/dist/src/search/chunks/RerankStage.js +42 -0
- package/dist/src/search/chunks/search-metrics.d.ts +18 -0
- package/dist/src/search/chunks/search-metrics.js +32 -0
- package/dist/src/search/chunks/search-plan.d.ts +18 -0
- package/dist/src/search/chunks/search-plan.js +19 -0
- package/dist/src/search/create-search-services.d.ts +26 -0
- package/dist/src/search/create-search-services.js +29 -0
- package/dist/src/search/index-paths.d.ts +1 -0
- package/dist/src/search/index-paths.js +5 -0
- package/dist/src/search/result-shaping.d.ts +4 -0
- package/dist/src/search/result-shaping.js +19 -0
- package/dist/src/search/symbol-sql.d.ts +13 -0
- package/dist/src/search/symbol-sql.js +66 -0
- package/dist/src/search/symbols/SymbolGrepFallback.d.ts +19 -0
- package/dist/src/search/symbols/SymbolGrepFallback.js +106 -0
- package/dist/src/search/symbols/SymbolIndexQuery.d.ts +14 -0
- package/dist/src/search/symbols/SymbolIndexQuery.js +52 -0
- package/dist/src/search/symbols/SymbolTextSearch.d.ts +13 -0
- package/dist/src/search/symbols/SymbolTextSearch.js +84 -0
- package/dist/src/search/symbols/TargetColumnGate.d.ts +7 -0
- package/dist/src/search/symbols/TargetColumnGate.js +23 -0
- package/dist/src/search/symbols/find-symbol-constants.d.ts +4 -0
- package/dist/src/search/symbols/find-symbol-constants.js +8 -0
- package/dist/src/search/symbols/grep-args.d.ts +3 -0
- package/dist/src/search/symbols/grep-args.js +7 -0
- package/dist/src/search/symbols/grep-patterns.d.ts +4 -0
- package/dist/src/search/symbols/grep-patterns.js +60 -0
- package/dist/src/services/AstParserService.d.ts +10 -106
- package/dist/src/services/AstParserService.js +37 -1068
- package/dist/src/services/DatabaseService.d.ts +10 -17
- package/dist/src/services/DatabaseService.js +71 -115
- package/dist/src/services/EmbedderFactory.d.ts +0 -4
- package/dist/src/services/EmbedderFactory.js +0 -5
- package/dist/src/services/IndexerService.d.ts +7 -26
- package/dist/src/services/IndexerService.js +38 -286
- package/dist/src/services/InfinityEmbedder.d.ts +0 -21
- package/dist/src/services/InfinityEmbedder.js +0 -15
- package/dist/src/services/LoggerService.d.ts +7 -5
- package/dist/src/services/LoggerService.js +37 -41
- package/dist/src/services/McpReadiness.d.ts +18 -0
- package/dist/src/services/McpReadiness.js +73 -0
- package/dist/src/services/McpServer.d.ts +50 -19
- package/dist/src/services/McpServer.js +132 -62
- package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
- package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
- package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
- package/dist/src/services/NxMonorepoResolver.js +5 -56
- package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
- package/dist/src/services/OpenAICompatibleEmbedder.js +0 -14
- package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
- package/dist/src/services/ProjectDiscoveryService.js +0 -4
- package/dist/src/services/VoyageEmbedder.d.ts +0 -6
- package/dist/src/services/VoyageEmbedder.js +0 -6
- package/dist/src/services/WebServerService.d.ts +5 -56
- package/dist/src/services/WebServerService.js +25 -63
- package/dist/src/services/http-retry.d.ts +0 -7
- package/dist/src/services/http-retry.js +1 -10
- package/dist/src/services/infinity-provisioning.d.ts +0 -74
- package/dist/src/services/infinity-provisioning.js +5 -88
- package/dist/src/services/legacy-ollama-index.d.ts +0 -16
- package/dist/src/services/legacy-ollama-index.js +0 -23
- package/dist/src/services/reranker.d.ts +0 -9
- package/dist/src/services/reranker.js +46 -12
- package/dist/src/setup.d.ts +24 -24
- package/dist/src/setup.js +205 -103
- package/dist/src/symbol-name.d.ts +1 -12
- package/dist/src/symbol-name.js +16 -25
- package/dist/src/symbol-rows.d.ts +0 -14
- package/dist/src/symbol-rows.js +0 -14
- package/dist/src/tool-call-log.d.ts +2 -0
- package/dist/src/tool-call-log.js +24 -0
- package/dist/src/tools/FindSymbolTool.d.ts +17 -7
- package/dist/src/tools/FindSymbolTool.js +54 -35
- package/dist/src/tools/GetCallersTool.d.ts +16 -8
- package/dist/src/tools/GetCallersTool.js +65 -35
- package/dist/src/tools/GetDependenciesTool.d.ts +16 -8
- package/dist/src/tools/GetDependenciesTool.js +36 -29
- package/dist/src/tools/GetDependentsTool.d.ts +20 -8
- package/dist/src/tools/GetDependentsTool.js +48 -34
- package/dist/src/tools/ListFilesTool.d.ts +16 -8
- package/dist/src/tools/ListFilesTool.js +20 -26
- package/dist/src/tools/ListProjectsTool.d.ts +11 -7
- package/dist/src/tools/ListProjectsTool.js +13 -11
- package/dist/src/tools/McpTool.d.ts +17 -0
- package/dist/src/tools/McpTool.js +28 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +16 -8
- package/dist/src/tools/ReadFileRangeTool.js +23 -25
- package/dist/src/tools/ReadFileTool.d.ts +15 -8
- package/dist/src/tools/ReadFileTool.js +19 -24
- package/dist/src/tools/SearchCodebaseTool.d.ts +22 -10
- package/dist/src/tools/SearchCodebaseTool.js +71 -50
- package/dist/src/tools/format-helpers.d.ts +9 -9
- package/dist/src/tools/format-helpers.js +146 -28
- package/dist/src/tools/tool-args.d.ts +38 -0
- package/dist/src/tools/tool-args.js +95 -0
- package/dist/src/web/dashboard.d.ts +0 -8
- package/dist/src/web/dashboard.js +7 -10
- package/dist/src/workspace/GrepRunner.d.ts +15 -0
- package/dist/src/workspace/GrepRunner.js +104 -0
- package/dist/src/workspace/ProjectRoots.d.ts +22 -0
- package/dist/src/workspace/ProjectRoots.js +102 -0
- package/dist/src/workspace/ToolPaths.d.ts +31 -0
- package/dist/src/workspace/ToolPaths.js +312 -0
- package/dist/src/workspace/WorkspaceFiles.d.ts +19 -0
- package/dist/src/workspace/WorkspaceFiles.js +285 -0
- package/dist/src/workspace/fs-access.d.ts +9 -0
- package/dist/src/workspace/fs-access.js +47 -0
- package/dist/src/workspace/path-helpers.d.ts +3 -0
- package/dist/src/workspace/path-helpers.js +43 -0
- package/package.json +5 -4
- package/dist/src/interfaces/ISearcher.d.ts +0 -35
- package/dist/src/services/AstChunker.d.ts +0 -42
- package/dist/src/services/AstChunker.js +0 -158
- package/dist/src/services/SearcherService.d.ts +0 -342
- package/dist/src/services/SearcherService.js +0 -1199
- package/docs/web-dashboard.md +0 -206
- /package/dist/src/{interfaces/ISearcher.js → chunking/interfaces.js} +0 -0
|
@@ -37,29 +37,36 @@ exports.AstParserService = void 0;
|
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const ts_morph_1 = require("ts-morph");
|
|
40
|
+
const DeclarationTreeBuilder_1 = require("../declarations/DeclarationTreeBuilder");
|
|
41
|
+
const DeclarationTree_1 = require("../declarations/DeclarationTree");
|
|
40
42
|
const LoggerService_1 = require("./LoggerService");
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
43
|
+
const IndexedNodeResolver_1 = require("../ast/IndexedNodeResolver");
|
|
44
|
+
const ImportEdgeExtractor_1 = require("../ast/ImportEdgeExtractor");
|
|
45
|
+
const TopLevelSymbolExtractor_1 = require("../ast/TopLevelSymbolExtractor");
|
|
46
|
+
const MemberExtractor_1 = require("../ast/MemberExtractor");
|
|
47
|
+
const CallExtractor_1 = require("../ast/CallExtractor");
|
|
48
|
+
const GraphqlNameExtractor_1 = require("../ast/GraphqlNameExtractor");
|
|
47
49
|
const FALLBACK_OPTS = {
|
|
48
50
|
skipAddingFilesFromTsConfig: true,
|
|
49
51
|
skipFileDependencyResolution: false,
|
|
50
52
|
compilerOptions: { allowJs: true, noEmit: true },
|
|
51
53
|
};
|
|
52
54
|
const MORPH_CACHE_MAX = 10;
|
|
53
|
-
const VALID_SEGMENT = /^#?[\w$]+$/;
|
|
54
55
|
class AstParserService {
|
|
55
56
|
logger;
|
|
56
|
-
deps;
|
|
57
57
|
morphProjects = new Map();
|
|
58
|
+
declarationBuilder = new DeclarationTreeBuilder_1.DeclarationTreeBuilder();
|
|
59
|
+
resolver = new IndexedNodeResolver_1.IndexedNodeResolver();
|
|
60
|
+
edges;
|
|
61
|
+
topLevel = new TopLevelSymbolExtractor_1.TopLevelSymbolExtractor();
|
|
62
|
+
members = new MemberExtractor_1.MemberExtractor(this.resolver);
|
|
63
|
+
calls = new CallExtractor_1.CallExtractor(this.resolver);
|
|
64
|
+
graphql;
|
|
58
65
|
constructor(logger = LoggerService_1.LoggerService.silent(), deps = {}) {
|
|
59
66
|
this.logger = logger;
|
|
60
|
-
this.
|
|
67
|
+
this.edges = new ImportEdgeExtractor_1.ImportEdgeExtractor(logger);
|
|
68
|
+
this.graphql = new GraphqlNameExtractor_1.GraphqlNameExtractor(this.resolver, logger, deps.resolveDeclaration);
|
|
61
69
|
}
|
|
62
|
-
// ── public interface ────────────────────────────────────────────────────────
|
|
63
70
|
async analyzeProject(filePaths, serviceName, projectRoot = process.cwd(), opts = {}) {
|
|
64
71
|
const t0 = Date.now();
|
|
65
72
|
const skipDependencyResolution = opts.skipDependencyResolution ?? false;
|
|
@@ -76,7 +83,7 @@ class AstParserService {
|
|
|
76
83
|
morphProject.addSourceFileAtPath(filePath);
|
|
77
84
|
}
|
|
78
85
|
catch (err) {
|
|
79
|
-
this.logger.warn('AstParser: failed to add source file', { filePath,
|
|
86
|
+
this.logger.warn('AstParser: failed to add source file', { filePath, err });
|
|
80
87
|
analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] });
|
|
81
88
|
}
|
|
82
89
|
}
|
|
@@ -84,7 +91,6 @@ class AstParserService {
|
|
|
84
91
|
const filePath = sourceFile.getFilePath();
|
|
85
92
|
analysisMap.set(filePath, this.analyzeSourceFile(sourceFile, filePath, serviceName));
|
|
86
93
|
}
|
|
87
|
-
// Release source file memory before returning
|
|
88
94
|
for (const sourceFile of morphProject.getSourceFiles()) {
|
|
89
95
|
morphProject.removeSourceFile(sourceFile);
|
|
90
96
|
}
|
|
@@ -120,7 +126,7 @@ class AstParserService {
|
|
|
120
126
|
sourceFile = morphProject.addSourceFileAtPath(filePath);
|
|
121
127
|
}
|
|
122
128
|
catch (err) {
|
|
123
|
-
this.logger.warn('AstParser: analyzeFile failed to add source file', { filePath,
|
|
129
|
+
this.logger.warn('AstParser: analyzeFile failed to add source file', { filePath, err, ms: Date.now() - t0 });
|
|
124
130
|
return { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] };
|
|
125
131
|
}
|
|
126
132
|
const result = this.analyzeSourceFile(sourceFile, filePath, serviceName);
|
|
@@ -130,64 +136,46 @@ class AstParserService {
|
|
|
130
136
|
});
|
|
131
137
|
return result;
|
|
132
138
|
}
|
|
133
|
-
// Kept for test compatibility (extract-import-edges.test.ts).
|
|
134
139
|
async extractImportEdges(filePath, serviceName, projectRoot) {
|
|
135
140
|
const { edges } = await this.analyzeFile(filePath, serviceName, projectRoot);
|
|
136
141
|
return edges;
|
|
137
142
|
}
|
|
138
143
|
analyzeSourceFile(sourceFile, filePath, serviceName) {
|
|
139
144
|
try {
|
|
140
|
-
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
141
|
-
const symbolCandidates = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
145
|
+
const edges = this.edges.extractEdges(sourceFile, filePath, serviceName);
|
|
146
|
+
const symbolCandidates = this.topLevel.extractSymbols(sourceFile, filePath, serviceName);
|
|
142
147
|
const symbols = symbolCandidates.map(c => c.entry);
|
|
143
|
-
// Member extraction (classes anywhere, class/interface/object-literal
|
|
144
|
-
// members, namespace-level declarations) is isolated in its own try: a
|
|
145
|
-
// bug there must never change a file's top-level symbols, chunks or
|
|
146
|
-
// embedding prompt (see the `symbol-index-members` retrieval-invariance
|
|
147
|
-
// requirement).
|
|
148
148
|
let memberCandidates = [];
|
|
149
149
|
try {
|
|
150
|
-
memberCandidates = this.extractMembers(sourceFile, filePath, serviceName);
|
|
150
|
+
memberCandidates = this.members.extractMembers(sourceFile, filePath, serviceName);
|
|
151
151
|
}
|
|
152
152
|
catch (err) {
|
|
153
|
-
this.logger.warn('AstParser: member extraction failed', { filePath,
|
|
153
|
+
this.logger.warn('AstParser: member extraction failed', { filePath, err });
|
|
154
154
|
memberCandidates = [];
|
|
155
155
|
}
|
|
156
156
|
const members = memberCandidates.map(c => c.entry);
|
|
157
|
-
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
158
|
-
const
|
|
159
|
-
// GraphQL-named rows are isolated like members: a bug here must never
|
|
160
|
-
// change a file's symbols, members, chunks, ownerPath or call graph.
|
|
161
|
-
// Runs after member extraction (decision 6 of `graphql-symbols`) — a
|
|
162
|
-
// GraphQL row is written only when the decorated declaration's own row
|
|
163
|
-
// was written to code_symbol.
|
|
157
|
+
const calls = this.calls.extractCalls(sourceFile, filePath, serviceName);
|
|
158
|
+
const declarations = this.buildDeclarations(sourceFile, filePath, symbolCandidates, memberCandidates);
|
|
164
159
|
let graphqlNames = [];
|
|
165
160
|
try {
|
|
166
|
-
graphqlNames = this.extractGraphqlNames(sourceFile, filePath, serviceName, [...symbolCandidates, ...memberCandidates]);
|
|
161
|
+
graphqlNames = this.graphql.extractGraphqlNames(sourceFile, filePath, serviceName, [...symbolCandidates, ...memberCandidates]);
|
|
167
162
|
}
|
|
168
163
|
catch (err) {
|
|
169
|
-
this.logger.warn('AstParser: GraphQL extraction failed', { filePath,
|
|
164
|
+
this.logger.warn('AstParser: GraphQL extraction failed', { filePath, err });
|
|
170
165
|
graphqlNames = [];
|
|
171
166
|
}
|
|
172
|
-
return { edges, symbols, members, calls,
|
|
167
|
+
return { edges, symbols, members, calls, declarations, graphqlNames };
|
|
173
168
|
}
|
|
174
169
|
catch (err) {
|
|
175
|
-
this.logger.warn('AstParser: failed to analyze file', { filePath,
|
|
170
|
+
this.logger.warn('AstParser: failed to analyze file', { filePath, err });
|
|
176
171
|
return { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] };
|
|
177
172
|
}
|
|
178
173
|
}
|
|
179
|
-
// ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
|
|
180
174
|
createMorphProject(projectRoot, skipFileDependencyResolution = false, rootTsConfigPath) {
|
|
181
175
|
const t0 = Date.now();
|
|
182
|
-
// Determine which tsconfig to use:
|
|
183
|
-
// 1. Project-local tsconfig.json
|
|
184
|
-
// 2. Root tsconfig (e.g. tsconfig.base.json for Nx monorepos)
|
|
185
|
-
// 3. Fallback options
|
|
186
176
|
const localTsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
187
177
|
const hasLocalTsconfig = fs.existsSync(localTsconfigPath);
|
|
188
178
|
const hasRootTsconfig = rootTsConfigPath ? fs.existsSync(rootTsConfigPath) : false;
|
|
189
|
-
// For monorepos: prefer the root tsconfig.base.json which contains
|
|
190
|
-
// workspace-wide path aliases (e.g. @scope/* → libs/*/src)
|
|
191
179
|
const effectiveTsconfig = hasRootTsconfig ? rootTsConfigPath : (hasLocalTsconfig ? localTsconfigPath : null);
|
|
192
180
|
this.logger.debug('AstParser: tsconfig lookup', {
|
|
193
181
|
projectRoot,
|
|
@@ -219,7 +207,7 @@ class AstParserService {
|
|
|
219
207
|
catch (err) {
|
|
220
208
|
this.logger.warn('AstParser: tsconfig load failed, using fallback', {
|
|
221
209
|
tsconfigPath: effectiveTsconfig ?? localTsconfigPath,
|
|
222
|
-
|
|
210
|
+
err,
|
|
223
211
|
ms: Date.now() - t0,
|
|
224
212
|
});
|
|
225
213
|
project = new ts_morph_1.Project(FALLBACK_OPTS);
|
|
@@ -228,7 +216,6 @@ class AstParserService {
|
|
|
228
216
|
}
|
|
229
217
|
getMorphProject(projectRoot) {
|
|
230
218
|
if (this.morphProjects.has(projectRoot)) {
|
|
231
|
-
// Move to end = most recently used
|
|
232
219
|
const cached = this.morphProjects.get(projectRoot);
|
|
233
220
|
this.morphProjects.delete(projectRoot);
|
|
234
221
|
this.morphProjects.set(projectRoot, cached);
|
|
@@ -236,7 +223,6 @@ class AstParserService {
|
|
|
236
223
|
return cached;
|
|
237
224
|
}
|
|
238
225
|
if (this.morphProjects.size >= MORPH_CACHE_MAX) {
|
|
239
|
-
// Evict least recently used (first entry in insertion-order Map)
|
|
240
226
|
const lruKey = this.morphProjects.keys().next().value;
|
|
241
227
|
this.morphProjects.delete(lruKey);
|
|
242
228
|
this.logger.debug('AstParser: evicted LRU project', { lruKey });
|
|
@@ -246,1036 +232,19 @@ class AstParserService {
|
|
|
246
232
|
this.morphProjects.set(projectRoot, project);
|
|
247
233
|
return project;
|
|
248
234
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const usageMap = this.buildUsageMap(sourceFile);
|
|
253
|
-
for (const decl of sourceFile.getImportDeclarations()) {
|
|
254
|
-
const namedImports = decl.getNamedImports();
|
|
255
|
-
const defaultImport = decl.getDefaultImport();
|
|
256
|
-
const namespaceImport = decl.getNamespaceImport();
|
|
257
|
-
const hasClause = Boolean(defaultImport || namespaceImport || namedImports.length > 0);
|
|
258
|
-
const resolved = decl.getModuleSpecifierSourceFile();
|
|
259
|
-
let toFile = resolved ? resolved.getFilePath() : null;
|
|
260
|
-
// A bindingless specifier never resolves through ts-morph's bound symbol
|
|
261
|
-
// (nothing is bound), even for a real project file — fall back to
|
|
262
|
-
// TypeScript's own resolver and map the result back onto the project.
|
|
263
|
-
if (!toFile && !hasClause) {
|
|
264
|
-
toFile = this.resolveSideEffectModuleSpecifier(decl, sourceFile, filePath);
|
|
265
|
-
}
|
|
266
|
-
if (!toFile) {
|
|
267
|
-
const specifier = decl.getModuleSpecifierValue();
|
|
268
|
-
const isCodeImport = !/\.(json|css|scss|less|svg|png|jpg|gif|ico|woff2?|ttf|eot)(\?.*)?$/.test(specifier);
|
|
269
|
-
if (specifier.startsWith('.') && isCodeImport) {
|
|
270
|
-
this.logger.debug('AstParser: unresolved import', { specifier, file: filePath });
|
|
271
|
-
}
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
if (toFile.includes('/node_modules/'))
|
|
275
|
-
continue;
|
|
276
|
-
if (!hasClause) {
|
|
277
|
-
edges.push({
|
|
278
|
-
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
279
|
-
toFile, toSymbol: null, depType: 'import-side-effect',
|
|
280
|
-
});
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
if (defaultImport) {
|
|
284
|
-
const fromSymbol = this.resolveFromSymbol(defaultImport.getText(), usageMap);
|
|
285
|
-
edges.push({
|
|
286
|
-
project: serviceName, fromFile: filePath, fromSymbol,
|
|
287
|
-
toFile, toSymbol: null, depType: 'import-default',
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (namespaceImport) {
|
|
291
|
-
const fromSymbol = this.resolveFromSymbol(namespaceImport.getText(), usageMap);
|
|
292
|
-
edges.push({
|
|
293
|
-
project: serviceName, fromFile: filePath, fromSymbol,
|
|
294
|
-
toFile, toSymbol: null, depType: 'import-namespace',
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
for (const named of namedImports) {
|
|
298
|
-
const localName = named.getAliasNode()?.getText() ?? named.getName();
|
|
299
|
-
const fromSymbol = this.resolveFromSymbol(localName, usageMap);
|
|
300
|
-
edges.push({
|
|
301
|
-
project: serviceName, fromFile: filePath, fromSymbol,
|
|
302
|
-
toFile, toSymbol: named.getName(), depType: 'import',
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
for (const decl of sourceFile.getExportDeclarations()) {
|
|
307
|
-
const resolved = decl.getModuleSpecifierSourceFile();
|
|
308
|
-
if (!resolved)
|
|
309
|
-
continue;
|
|
310
|
-
const toFile = resolved.getFilePath();
|
|
311
|
-
if (toFile.includes('/node_modules/'))
|
|
312
|
-
continue;
|
|
313
|
-
const namedExports = decl.getNamedExports();
|
|
314
|
-
if (namedExports.length > 0) {
|
|
315
|
-
for (const named of namedExports) {
|
|
316
|
-
edges.push({
|
|
317
|
-
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
318
|
-
toFile, toSymbol: named.getName(), depType: 'reexport',
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
edges.push({
|
|
324
|
-
project: serviceName, fromFile: filePath, fromSymbol: null,
|
|
325
|
-
toFile, toSymbol: null, depType: 'reexport-namespace',
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return edges;
|
|
330
|
-
}
|
|
331
|
-
/** Manual fallback for a bindingless import specifier (design.md decision 2) —
|
|
332
|
-
* ts-morph's `getModuleSpecifierSourceFile()` is `null` for these even when
|
|
333
|
-
* the target is a real project file, since nothing is bound to resolve through. */
|
|
334
|
-
resolveSideEffectModuleSpecifier(decl, sourceFile, filePath) {
|
|
335
|
-
const specifier = decl.getModuleSpecifierValue();
|
|
336
|
-
const compilerOptions = sourceFile.getProject().getCompilerOptions();
|
|
337
|
-
const result = ts_morph_1.ts.resolveModuleName(specifier, filePath, compilerOptions, ts_morph_1.ts.sys);
|
|
338
|
-
const resolvedFileName = result.resolvedModule?.resolvedFileName;
|
|
339
|
-
if (!resolvedFileName)
|
|
340
|
-
return null;
|
|
341
|
-
return sourceFile.getProject().getSourceFile(resolvedFileName)?.getFilePath() ?? null;
|
|
342
|
-
}
|
|
343
|
-
resolveFromSymbol(localName, usageMap) {
|
|
344
|
-
const users = usageMap.get(localName);
|
|
345
|
-
return users?.length === 1 ? users[0] : null;
|
|
346
|
-
}
|
|
347
|
-
buildUsageMap(sourceFile) {
|
|
348
|
-
const map = new Map();
|
|
349
|
-
const containers = [
|
|
350
|
-
...sourceFile.getClasses().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
|
|
351
|
-
...sourceFile.getFunctions().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
|
|
352
|
-
// include top-level variable declarations so arrow function exports are tracked for fromSymbol
|
|
353
|
-
...sourceFile.getVariableStatements()
|
|
354
|
-
.flatMap(stmt => stmt.getDeclarations())
|
|
355
|
-
.flatMap(n => {
|
|
356
|
-
const name = n.getName();
|
|
357
|
-
return name ? [{ name, node: n }] : [];
|
|
358
|
-
}),
|
|
359
|
-
];
|
|
360
|
-
for (const { name, node } of containers) {
|
|
361
|
-
for (const id of node.getDescendantsOfKind(ts_morph_1.SyntaxKind.Identifier)) {
|
|
362
|
-
const text = id.getText();
|
|
363
|
-
const existing = map.get(text);
|
|
364
|
-
if (existing) {
|
|
365
|
-
if (!existing.includes(name))
|
|
366
|
-
existing.push(name);
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
map.set(text, [name]);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return map;
|
|
374
|
-
}
|
|
375
|
-
// ── private: position / signature helpers ───────────────────────────────────
|
|
376
|
-
getPos(sourceFile, node) {
|
|
377
|
-
const { line, character } = sourceFile.compilerNode
|
|
378
|
-
.getLineAndCharacterOfPosition(node.getStart());
|
|
379
|
-
return { line: line + 1, col: character + 1 };
|
|
380
|
-
}
|
|
381
|
-
/** Trimmed text of the source line containing `node`'s start — the signature
|
|
382
|
-
* rule for every kind (name node for members, the position node for
|
|
383
|
-
* constructors/anonymous default classes). */
|
|
384
|
-
lineTextAt(sourceLines, sourceFile, node) {
|
|
385
|
-
const { line } = this.getPos(sourceFile, node);
|
|
386
|
-
return (sourceLines[line - 1] ?? node.getText().split('\n')[0]).trim();
|
|
387
|
-
}
|
|
388
|
-
// ── private: top-level symbol extraction (feeds the chunker + prompt) ──────
|
|
389
|
-
extractSymbols(sourceFile, filePath, serviceName) {
|
|
390
|
-
const candidates = [];
|
|
391
|
-
const sourceLines = sourceFile.getFullText().split('\n');
|
|
392
|
-
const lineAt = (node) => this.lineTextAt(sourceLines, sourceFile, node);
|
|
393
|
-
for (const cls of sourceFile.getClasses()) {
|
|
394
|
-
const name = cls.getName();
|
|
395
|
-
if (!name)
|
|
396
|
-
continue;
|
|
397
|
-
const nameNode = cls.getNameNode() ?? cls;
|
|
398
|
-
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
399
|
-
candidates.push({
|
|
400
|
-
declNode: cls,
|
|
401
|
-
entry: {
|
|
402
|
-
project: serviceName, filePath, name, kind: 'class',
|
|
403
|
-
signature: lineAt(nameNode),
|
|
404
|
-
line, endLine: cls.getEndLineNumber(), col,
|
|
405
|
-
container: null, isStatic: false,
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
for (const fn of sourceFile.getFunctions()) {
|
|
410
|
-
const name = fn.getName();
|
|
411
|
-
if (!name)
|
|
412
|
-
continue;
|
|
413
|
-
const nameNode = fn.getNameNode() ?? fn;
|
|
414
|
-
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
415
|
-
candidates.push({
|
|
416
|
-
declNode: fn,
|
|
417
|
-
entry: {
|
|
418
|
-
project: serviceName, filePath, name, kind: 'function',
|
|
419
|
-
signature: lineAt(nameNode),
|
|
420
|
-
line, endLine: fn.getEndLineNumber(), col,
|
|
421
|
-
container: null, isStatic: false, hasBody: fn.getBody() !== undefined,
|
|
422
|
-
},
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
for (const iface of sourceFile.getInterfaces()) {
|
|
426
|
-
const nameNode = iface.getNameNode();
|
|
427
|
-
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
428
|
-
candidates.push({
|
|
429
|
-
declNode: iface,
|
|
430
|
-
entry: {
|
|
431
|
-
project: serviceName, filePath, name: iface.getName(), kind: 'interface',
|
|
432
|
-
signature: lineAt(nameNode),
|
|
433
|
-
line, endLine: iface.getEndLineNumber(), col,
|
|
434
|
-
container: null, isStatic: false,
|
|
435
|
-
},
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
for (const ta of sourceFile.getTypeAliases()) {
|
|
439
|
-
const nameNode = ta.getNameNode();
|
|
440
|
-
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
441
|
-
candidates.push({
|
|
442
|
-
declNode: ta,
|
|
443
|
-
entry: {
|
|
444
|
-
project: serviceName, filePath, name: ta.getName(), kind: 'type',
|
|
445
|
-
signature: lineAt(nameNode),
|
|
446
|
-
line, endLine: ta.getEndLineNumber(), col,
|
|
447
|
-
container: null, isStatic: false,
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
for (const stmt of sourceFile.getVariableStatements()) {
|
|
452
|
-
for (const decl of stmt.getDeclarations()) {
|
|
453
|
-
const nameNode = decl.getNameNode();
|
|
454
|
-
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
455
|
-
candidates.push({
|
|
456
|
-
// Owner ranges use the declarator's own span, not the statement's —
|
|
457
|
-
// each declarator of `const a = 1, b = () => {…}` owns only its lines.
|
|
458
|
-
declNode: decl,
|
|
459
|
-
entry: {
|
|
460
|
-
project: serviceName, filePath, name: decl.getName(), kind: 'variable',
|
|
461
|
-
signature: lineAt(nameNode),
|
|
462
|
-
line, endLine: stmt.getEndLineNumber(), col,
|
|
463
|
-
container: null, isStatic: false,
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return candidates;
|
|
469
|
-
}
|
|
470
|
-
// ── private: member extraction (classes anywhere, members, namespace body) ─
|
|
471
|
-
extractMembers(sourceFile, filePath, serviceName) {
|
|
472
|
-
const candidates = [];
|
|
473
|
-
const sourceLines = sourceFile.getFullText().split('\n');
|
|
474
|
-
const lineAt = (node) => this.lineTextAt(sourceLines, sourceFile, node);
|
|
475
|
-
const push = (declNode, posNode, name, kind, container, isStatic, hasBody) => {
|
|
476
|
-
const { line, col } = this.getPos(sourceFile, posNode);
|
|
477
|
-
candidates.push({
|
|
478
|
-
declNode,
|
|
479
|
-
entry: {
|
|
480
|
-
project: serviceName, filePath, name, kind,
|
|
481
|
-
signature: lineAt(posNode),
|
|
482
|
-
line, endLine: declNode.getEndLineNumber(), col,
|
|
483
|
-
container: container.length ? container.join('.') : null,
|
|
484
|
-
isStatic, hasBody,
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
|
-
};
|
|
488
|
-
// Classes anywhere in the file — nested, expression, anonymous default.
|
|
489
|
-
for (const cls of [
|
|
490
|
-
...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration),
|
|
491
|
-
...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassExpression),
|
|
492
|
-
]) {
|
|
493
|
-
// Named top-level classes are already handled by extractSymbols; an
|
|
494
|
-
// anonymous top-level class (`export default class {}`) is not — it
|
|
495
|
-
// must still be collected here.
|
|
496
|
-
if (ts_morph_1.Node.isClassDeclaration(cls) && cls.getName() && ts_morph_1.Node.isSourceFile(cls.getParent()))
|
|
497
|
-
continue;
|
|
498
|
-
const described = this.describeIndexedNode(cls);
|
|
499
|
-
if (!described)
|
|
500
|
-
continue;
|
|
501
|
-
const posNode = (ts_morph_1.Node.isClassDeclaration(cls) ? cls.getNameNode() : undefined) ?? cls;
|
|
502
|
-
push(cls, posNode, described.name, 'class', described.container, false, true);
|
|
503
|
-
}
|
|
504
|
-
// Class / object-literal methods, accessors, constructors, function properties.
|
|
505
|
-
for (const m of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)) {
|
|
506
|
-
const described = this.describeIndexedNode(m);
|
|
507
|
-
if (!described)
|
|
508
|
-
continue;
|
|
509
|
-
push(m, m.getNameNode(), described.name, 'method', described.container, m.isStatic(), m.getBody() !== undefined);
|
|
510
|
-
}
|
|
511
|
-
for (const a of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.GetAccessor)) {
|
|
512
|
-
const described = this.describeIndexedNode(a);
|
|
513
|
-
if (!described)
|
|
514
|
-
continue;
|
|
515
|
-
push(a, a.getNameNode(), described.name, 'get_accessor', described.container, a.isStatic(), true);
|
|
516
|
-
}
|
|
517
|
-
for (const a of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.SetAccessor)) {
|
|
518
|
-
const described = this.describeIndexedNode(a);
|
|
519
|
-
if (!described)
|
|
520
|
-
continue;
|
|
521
|
-
push(a, a.getNameNode(), described.name, 'set_accessor', described.container, a.isStatic(), true);
|
|
522
|
-
}
|
|
523
|
-
for (const c of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.Constructor)) {
|
|
524
|
-
const described = this.describeIndexedNode(c);
|
|
525
|
-
if (!described)
|
|
526
|
-
continue;
|
|
527
|
-
const kw = c.getFirstChildByKind(ts_morph_1.SyntaxKind.ConstructorKeyword) ?? c;
|
|
528
|
-
push(c, kw, described.name, 'constructor', described.container, false, c.getBody() !== undefined);
|
|
529
|
-
}
|
|
530
|
-
for (const p of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyDeclaration)) {
|
|
531
|
-
const described = this.describeIndexedNode(p);
|
|
532
|
-
if (!described)
|
|
533
|
-
continue;
|
|
534
|
-
push(p, p.getNameNode(), described.name, 'function_property', described.container, p.isStatic(), true);
|
|
535
|
-
}
|
|
536
|
-
for (const p of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
537
|
-
const described = this.describeIndexedNode(p);
|
|
538
|
-
if (!described)
|
|
539
|
-
continue;
|
|
540
|
-
push(p, p.getNameNode(), described.name, 'function_property', described.container, false, true);
|
|
541
|
-
}
|
|
542
|
-
// Interface / type-literal method and function-typed property signatures.
|
|
543
|
-
for (const s of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodSignature)) {
|
|
544
|
-
const described = this.describeIndexedNode(s);
|
|
545
|
-
if (!described)
|
|
546
|
-
continue;
|
|
547
|
-
push(s, s.getNameNode(), described.name, 'method_signature', described.container, false, false);
|
|
548
|
-
}
|
|
549
|
-
for (const s of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertySignature)) {
|
|
550
|
-
const described = this.describeIndexedNode(s);
|
|
551
|
-
if (!described)
|
|
552
|
-
continue;
|
|
553
|
-
push(s, s.getNameNode(), described.name, 'method_signature', described.container, false, false);
|
|
554
|
-
}
|
|
555
|
-
// Declarations directly in a namespace body.
|
|
556
|
-
for (const fn of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.FunctionDeclaration)) {
|
|
557
|
-
if (!ts_morph_1.Node.isModuleBlock(fn.getParent()))
|
|
558
|
-
continue;
|
|
559
|
-
const described = this.describeIndexedNode(fn);
|
|
560
|
-
if (!described)
|
|
561
|
-
continue;
|
|
562
|
-
const nameNode = fn.getNameNode();
|
|
563
|
-
if (!nameNode)
|
|
564
|
-
continue;
|
|
565
|
-
push(fn, nameNode, described.name, 'function', described.container, false, fn.getBody() !== undefined);
|
|
566
|
-
}
|
|
567
|
-
for (const iface of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.InterfaceDeclaration)) {
|
|
568
|
-
if (!ts_morph_1.Node.isModuleBlock(iface.getParent()))
|
|
569
|
-
continue;
|
|
570
|
-
const described = this.describeIndexedNode(iface);
|
|
571
|
-
if (!described)
|
|
572
|
-
continue;
|
|
573
|
-
push(iface, iface.getNameNode(), described.name, 'interface', described.container, false, true);
|
|
574
|
-
}
|
|
575
|
-
for (const ta of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.TypeAliasDeclaration)) {
|
|
576
|
-
if (!ts_morph_1.Node.isModuleBlock(ta.getParent()))
|
|
577
|
-
continue;
|
|
578
|
-
const described = this.describeIndexedNode(ta);
|
|
579
|
-
if (!described)
|
|
580
|
-
continue;
|
|
581
|
-
push(ta, ta.getNameNode(), described.name, 'type', described.container, false, true);
|
|
582
|
-
}
|
|
583
|
-
for (const stmt of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableStatement)) {
|
|
584
|
-
if (!ts_morph_1.Node.isModuleBlock(stmt.getParent()))
|
|
585
|
-
continue;
|
|
586
|
-
for (const decl of stmt.getDeclarations()) {
|
|
587
|
-
const described = this.describeIndexedNode(decl);
|
|
588
|
-
if (!described)
|
|
589
|
-
continue;
|
|
590
|
-
push(decl, decl.getNameNode(), described.name, 'variable', described.container, false, true);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return candidates;
|
|
594
|
-
}
|
|
595
|
-
/** Chunk-ownership ranges from every indexed declaration (top-level symbols +
|
|
596
|
-
* members) except `method_signature`. Each range spans the declaration node's
|
|
597
|
-
* own first-to-last line — decorator-inclusive, comment-exclusive, and (for a
|
|
598
|
-
* variable) the declarator alone, never the whole statement. */
|
|
599
|
-
buildOwnerRanges(candidates) {
|
|
600
|
-
return candidates
|
|
601
|
-
.filter(c => c.entry.kind !== 'method_signature')
|
|
602
|
-
.map(c => ({
|
|
603
|
-
name: c.entry.name,
|
|
604
|
-
container: c.entry.container,
|
|
605
|
-
line: c.declNode.getStartLineNumber(),
|
|
606
|
-
endLine: c.declNode.getEndLineNumber(),
|
|
607
|
-
}));
|
|
608
|
-
}
|
|
609
|
-
// ── private: GraphQL-named rows (graphql-symbols) ───────────────────────────
|
|
610
|
-
/** Local name → the name exported by `@nestjs/graphql`, for every named import
|
|
611
|
-
* of that module specifier (aliases followed). Recognition is import-based,
|
|
612
|
-
* not type-checker-based, so `skipFileDependencyResolution` batches agree. */
|
|
613
|
-
buildGraphqlImportMap(sourceFile) {
|
|
614
|
-
const map = new Map();
|
|
615
|
-
for (const decl of sourceFile.getImportDeclarations()) {
|
|
616
|
-
if (decl.getModuleSpecifierValue() !== GRAPHQL_MODULE)
|
|
617
|
-
continue;
|
|
618
|
-
for (const named of decl.getNamedImports()) {
|
|
619
|
-
const exportedName = named.getName();
|
|
620
|
-
const localName = named.getAliasNode()?.getText() ?? exportedName;
|
|
621
|
-
map.set(localName, exportedName);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return map;
|
|
625
|
-
}
|
|
626
|
-
/** The identifier naming a decorator's expression (`@Foo` or the callee of `@Foo(...)`). */
|
|
627
|
-
static decoratorIdentifier(dec) {
|
|
628
|
-
const expr = dec.getExpression();
|
|
629
|
-
if (ts_morph_1.Node.isCallExpression(expr)) {
|
|
630
|
-
const callee = expr.getExpression();
|
|
631
|
-
return ts_morph_1.Node.isIdentifier(callee) ? callee : undefined;
|
|
632
|
-
}
|
|
633
|
-
return ts_morph_1.Node.isIdentifier(expr) ? expr : undefined;
|
|
634
|
-
}
|
|
635
|
-
/** `dec` recognised against `importMap` — its exported `@nestjs/graphql` name and its call arguments (`[]` for a bare `@Foo`). */
|
|
636
|
-
recognizedDecorator(dec, importMap) {
|
|
637
|
-
const idNode = AstParserService.decoratorIdentifier(dec);
|
|
638
|
-
if (!idNode)
|
|
639
|
-
return null;
|
|
640
|
-
const exported = importMap.get(idNode.getText());
|
|
641
|
-
if (!exported)
|
|
642
|
-
return null;
|
|
643
|
-
const expr = dec.getExpression();
|
|
644
|
-
const args = ts_morph_1.Node.isCallExpression(expr) ? expr.getArguments() : [];
|
|
645
|
-
return { name: exported, args };
|
|
646
|
-
}
|
|
647
|
-
static literalStringValue(node) {
|
|
648
|
-
if (ts_morph_1.Node.isStringLiteral(node) || ts_morph_1.Node.isNoSubstitutionTemplateLiteral(node))
|
|
649
|
-
return node.getLiteralValue();
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
static checkGraphqlGrammar(name) {
|
|
653
|
-
return GRAPHQL_NAME_RE.test(name) ? { ok: true, name } : { ok: false, reason: 'invalid-name', value: name };
|
|
654
|
-
}
|
|
655
|
-
/** `true` when any decorator argument is an (unwrapped) options object
|
|
656
|
-
* literal with `isAbstract: true` — an abstract base type (`PaginatedType`
|
|
657
|
-
* pattern) that NestJS never registers in the actual GraphQL schema. */
|
|
658
|
-
static isAbstractGraphqlType(args) {
|
|
659
|
-
return args.some((arg) => {
|
|
660
|
-
if (!ts_morph_1.Node.isObjectLiteralExpression(arg))
|
|
661
|
-
return false;
|
|
662
|
-
const prop = arg.getProperties().find((p) => {
|
|
663
|
-
if (!ts_morph_1.Node.isPropertyAssignment(p))
|
|
664
|
-
return false;
|
|
665
|
-
const nameNode = p.getNameNode();
|
|
666
|
-
if (ts_morph_1.Node.isIdentifier(nameNode))
|
|
667
|
-
return nameNode.getText() === 'isAbstract';
|
|
668
|
-
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
669
|
-
return nameNode.getLiteralValue() === 'isAbstract';
|
|
670
|
-
return false;
|
|
671
|
-
});
|
|
672
|
-
const init = prop?.getInitializer();
|
|
673
|
-
return init !== undefined && ts_morph_1.Node.isTrueLiteral(init);
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
/** `obj`'s own `name` property (`PropertyAssignment` or `ShorthandPropertyAssignment`), if any. */
|
|
677
|
-
static findNameProperty(obj) {
|
|
678
|
-
return obj.getProperties().find((p) => {
|
|
679
|
-
if (!ts_morph_1.Node.isPropertyAssignment(p) && !ts_morph_1.Node.isShorthandPropertyAssignment(p))
|
|
680
|
-
return false;
|
|
681
|
-
const nameNode = p.getNameNode();
|
|
682
|
-
if (ts_morph_1.Node.isIdentifier(nameNode))
|
|
683
|
-
return nameNode.getText() === 'name';
|
|
684
|
-
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
685
|
-
return nameNode.getLiteralValue() === 'name';
|
|
686
|
-
return false;
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
/** Strips parens/`as`/`satisfies`/non-null wrappers (any nesting) to find the
|
|
690
|
-
* underlying node — used only to classify an argument's *shape* (type
|
|
691
|
-
* function vs. options object vs. literal vs. other), never to read a name
|
|
692
|
-
* value through the wrapper (decision 4 of `graphql-symbols` forbids that). */
|
|
693
|
-
static peekThroughWrapper(node) {
|
|
694
|
-
let current = node;
|
|
695
|
-
let wrapped = false;
|
|
696
|
-
while (true) {
|
|
697
|
-
if (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current) || ts_morph_1.Node.isNonNullExpression(current)) {
|
|
698
|
-
current = current.getExpression();
|
|
699
|
-
wrapped = true;
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
break;
|
|
703
|
-
}
|
|
704
|
-
return { inner: current, wrapped };
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* GraphQL name from a decorator's arguments (design decision 4 of
|
|
708
|
-
* `graphql-symbols`) — syntactic, no wrapper looked through at any step.
|
|
709
|
-
* `allowOptionsName` gates step 2 (the `name` option — `Query`/`Mutation`/
|
|
710
|
-
* `Subscription`/`ResolveField` only); `fallbackName` is the TS name used at
|
|
711
|
-
* step 3.
|
|
712
|
-
*/
|
|
713
|
-
deriveGraphqlName(args, allowOptionsName, fallbackName) {
|
|
714
|
-
if (args.length > 0) {
|
|
715
|
-
const first = args[0];
|
|
716
|
-
const { inner, wrapped } = AstParserService.peekThroughWrapper(first);
|
|
717
|
-
const literal = AstParserService.literalStringValue(inner);
|
|
718
|
-
if (literal !== null) {
|
|
719
|
-
// A wrapped literal is still "a wrapped ... argument", an anything-else
|
|
720
|
-
// node (decision 4) — reading its value through the wrapper is exactly
|
|
721
|
-
// what "no wrapper is looked through" forbids.
|
|
722
|
-
if (wrapped)
|
|
723
|
-
return { ok: false, reason: 'non-literal-name' };
|
|
724
|
-
return AstParserService.checkGraphqlGrammar(literal);
|
|
725
|
-
}
|
|
726
|
-
const isTypeFnOrOptions = ts_morph_1.Node.isArrowFunction(inner) || ts_morph_1.Node.isFunctionExpression(inner) || ts_morph_1.Node.isObjectLiteralExpression(inner);
|
|
727
|
-
if (!isTypeFnOrOptions)
|
|
728
|
-
return { ok: false, reason: 'non-literal-name' };
|
|
729
|
-
// Type function / options object, wrapped or not — this is a routing
|
|
730
|
-
// decision only (go to step 2/3); nothing is read from it here, so its
|
|
731
|
-
// wrapper doesn't matter yet.
|
|
732
|
-
}
|
|
733
|
-
if (allowOptionsName) {
|
|
734
|
-
for (const arg of args) {
|
|
735
|
-
const { inner, wrapped } = AstParserService.peekThroughWrapper(arg);
|
|
736
|
-
if (!ts_morph_1.Node.isObjectLiteralExpression(inner))
|
|
737
|
-
continue;
|
|
738
|
-
// A wrapped options object cannot be trusted without looking through
|
|
739
|
-
// the wrapper, which decision 4 forbids — treat it as non-literal
|
|
740
|
-
// rather than silently skipping to the next argument or to step 3.
|
|
741
|
-
if (wrapped)
|
|
742
|
-
return { ok: false, reason: 'non-literal-name' };
|
|
743
|
-
const nameProp = AstParserService.findNameProperty(inner);
|
|
744
|
-
if (!nameProp)
|
|
745
|
-
continue;
|
|
746
|
-
if (ts_morph_1.Node.isShorthandPropertyAssignment(nameProp))
|
|
747
|
-
return { ok: false, reason: 'non-literal-name' };
|
|
748
|
-
const init = nameProp.getInitializer();
|
|
749
|
-
const value = init ? AstParserService.literalStringValue(init) : null;
|
|
750
|
-
if (value !== null)
|
|
751
|
-
return AstParserService.checkGraphqlGrammar(value);
|
|
752
|
-
return { ok: false, reason: 'non-literal-name' };
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return AstParserService.checkGraphqlGrammar(fallbackName);
|
|
756
|
-
}
|
|
757
|
-
logGraphqlSkip(filePath, declaration, decorator, reason, value) {
|
|
758
|
-
const fields = { filePath, declaration, decorator, reason };
|
|
759
|
-
if (value !== undefined)
|
|
760
|
-
fields.value = value;
|
|
761
|
-
this.logger.debug('AstParser: graphql row skipped', fields);
|
|
762
|
-
}
|
|
763
|
-
/** `declNode`'s qualified TypeScript name for diagnostics, independent of
|
|
764
|
-
* whether its own `code_symbol` row was actually written. */
|
|
765
|
-
declarationNameFor(node, fallback) {
|
|
766
|
-
const described = this.describeIndexedNode(node);
|
|
767
|
-
return described ? this.qualifiedName(described) : fallback;
|
|
768
|
-
}
|
|
769
|
-
/** The identifier named by a `@Resolver` argument: directly, or as an arrow
|
|
770
|
-
* function's expression body; or the sole identifier of a single-element
|
|
771
|
-
* array literal in either position (list-returning resolver). */
|
|
772
|
-
static extractResolverIdentifier(argNode) {
|
|
773
|
-
let target = argNode;
|
|
774
|
-
if (ts_morph_1.Node.isArrowFunction(target)) {
|
|
775
|
-
const body = target.getBody();
|
|
776
|
-
if (ts_morph_1.Node.isBlock(body))
|
|
777
|
-
return null;
|
|
778
|
-
target = body;
|
|
779
|
-
}
|
|
780
|
-
if (ts_morph_1.Node.isIdentifier(target))
|
|
781
|
-
return target;
|
|
782
|
-
if (ts_morph_1.Node.isArrayLiteralExpression(target)) {
|
|
783
|
-
const elements = target.getElements();
|
|
784
|
-
if (elements.length === 1 && ts_morph_1.Node.isIdentifier(elements[0]))
|
|
785
|
-
return elements[0];
|
|
786
|
-
}
|
|
787
|
-
return null;
|
|
788
|
-
}
|
|
789
|
-
defaultResolveDeclaration(identifier) {
|
|
790
|
-
const sym = identifier.getSymbol();
|
|
791
|
-
if (!sym)
|
|
792
|
-
return undefined;
|
|
793
|
-
const firstDecl = sym.getDeclarations()?.[0];
|
|
794
|
-
const resolved = firstDecl && ts_morph_1.Node.isImportSpecifier(firstDecl) ? (sym.getAliasedSymbol() ?? sym) : sym;
|
|
795
|
-
return resolved.getDeclarations()?.[0];
|
|
796
|
-
}
|
|
797
|
-
/** The GraphQL type name of a `@ResolveField`'s resolved parent class
|
|
798
|
-
* (design decision 5, second half): its recognised `@ObjectType` (else
|
|
799
|
-
* `@InterfaceType`) decorator, or its class name when it carries no
|
|
800
|
-
* decorator spelled `ObjectType`/`InterfaceType` at all, recognised or not. */
|
|
801
|
-
classGraphqlTypeName(declClass) {
|
|
802
|
-
const declImportMap = this.buildGraphqlImportMap(declClass.getSourceFile());
|
|
803
|
-
const decorators = declClass.getDecorators();
|
|
804
|
-
const hasAnyTypeSpelledDecorator = decorators.some((d) => {
|
|
805
|
-
const idNode = AstParserService.decoratorIdentifier(d);
|
|
806
|
-
const text = idNode?.getText();
|
|
807
|
-
return text === 'ObjectType' || text === 'InterfaceType';
|
|
808
|
-
});
|
|
809
|
-
const recognized = decorators
|
|
810
|
-
.map(d => this.recognizedDecorator(d, declImportMap))
|
|
811
|
-
.find(r => r?.name === 'ObjectType')
|
|
812
|
-
?? decorators.map(d => this.recognizedDecorator(d, declImportMap)).find(r => r?.name === 'InterfaceType');
|
|
813
|
-
if (recognized) {
|
|
814
|
-
const fallbackName = declClass.getName() ?? (declClass.isDefaultExport() ? 'default' : '');
|
|
815
|
-
const nameResult = this.deriveGraphqlName(recognized.args, false, fallbackName);
|
|
816
|
-
return nameResult.ok ? { ok: true, name: nameResult.name } : { ok: false };
|
|
817
|
-
}
|
|
818
|
-
// Carries a type-spelled decorator that isn't recognised (e.g. imported
|
|
819
|
-
// through a barrel) — its GraphQL name is unreadable; the class-name
|
|
820
|
-
// fallback never applies to it (it may differ from the unreadable name).
|
|
821
|
-
if (hasAnyTypeSpelledDecorator)
|
|
822
|
-
return { ok: false };
|
|
823
|
-
const name = declClass.getName();
|
|
824
|
-
if (!name)
|
|
825
|
-
return { ok: false };
|
|
826
|
-
return GRAPHQL_NAME_RE.test(name) ? { ok: true, name } : { ok: false, value: name };
|
|
827
|
-
}
|
|
828
|
-
/** The parent type of a `@ResolveField` — the enclosing class's recognised
|
|
829
|
-
* `@Resolver` argument (design decision 5, first half). */
|
|
830
|
-
resolveResolverParentType(cls, importMap) {
|
|
831
|
-
const resolverDec = cls.getDecorators()
|
|
832
|
-
.map(d => this.recognizedDecorator(d, importMap))
|
|
833
|
-
.find(r => r?.name === 'Resolver');
|
|
834
|
-
if (!resolverDec || resolverDec.args.length === 0)
|
|
835
|
-
return { ok: false };
|
|
836
|
-
const argNode = resolverDec.args[0];
|
|
837
|
-
const literal = AstParserService.literalStringValue(argNode);
|
|
838
|
-
if (literal !== null) {
|
|
839
|
-
return GRAPHQL_NAME_RE.test(literal) ? { ok: true, name: literal } : { ok: false, value: literal };
|
|
840
|
-
}
|
|
841
|
-
const identifier = AstParserService.extractResolverIdentifier(argNode);
|
|
842
|
-
if (!identifier)
|
|
843
|
-
return { ok: false };
|
|
844
|
-
const resolve = this.deps.resolveDeclaration ?? this.defaultResolveDeclaration.bind(this);
|
|
845
|
-
const declaration = resolve(identifier);
|
|
846
|
-
if (!declaration || !ts_morph_1.Node.isClassDeclaration(declaration))
|
|
847
|
-
return { ok: false };
|
|
848
|
-
return this.classGraphqlTypeName(declaration);
|
|
849
|
-
}
|
|
850
|
-
extractClassTypeRows(cls, importMap, ownByDeclNode, filePath, serviceName, entries) {
|
|
851
|
-
const own = ownByDeclNode.get(cls);
|
|
852
|
-
const declaration = this.declarationNameFor(cls, own?.entry.name ?? cls.getName() ?? 'default');
|
|
853
|
-
const seenNames = new Set();
|
|
854
|
-
for (const dec of cls.getDecorators()) {
|
|
855
|
-
const rec = this.recognizedDecorator(dec, importMap);
|
|
856
|
-
if (!rec || !GRAPHQL_TYPE_DECORATORS.has(rec.name))
|
|
857
|
-
continue;
|
|
858
|
-
if (AstParserService.isAbstractGraphqlType(rec.args)) {
|
|
859
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, 'abstract-type');
|
|
860
|
-
continue;
|
|
861
|
-
}
|
|
862
|
-
if (!own) {
|
|
863
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, 'no-symbol-row');
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
const nameResult = this.deriveGraphqlName(rec.args, false, own.entry.name);
|
|
867
|
-
if (!nameResult.ok) {
|
|
868
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
|
|
869
|
-
continue;
|
|
870
|
-
}
|
|
871
|
-
if (seenNames.has(nameResult.name)) {
|
|
872
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, 'duplicate', own.entry.name);
|
|
873
|
-
continue;
|
|
874
|
-
}
|
|
875
|
-
seenNames.add(nameResult.name);
|
|
876
|
-
entries.push({
|
|
877
|
-
project: serviceName, filePath, name: nameResult.name, kind: 'graphql_type',
|
|
878
|
-
signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
|
|
879
|
-
container: null, isStatic: false, target: declaration,
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
extractResolveFieldRow(method, rec, own, declaration, importMap, filePath, serviceName, entries) {
|
|
884
|
-
if (!own) {
|
|
885
|
-
this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'no-symbol-row');
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
const cls = method.getParent();
|
|
889
|
-
if (!cls || !ts_morph_1.Node.isClassDeclaration(cls)) {
|
|
890
|
-
this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent');
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
const parent = this.resolveResolverParentType(cls, importMap);
|
|
894
|
-
if (!parent.ok) {
|
|
895
|
-
this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent', parent.value);
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
const nameResult = this.deriveGraphqlName(rec.args, true, own.entry.name);
|
|
899
|
-
if (!nameResult.ok) {
|
|
900
|
-
this.logGraphqlSkip(filePath, declaration, 'ResolveField', nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
entries.push({
|
|
904
|
-
project: serviceName, filePath, name: nameResult.name, kind: 'graphql_field',
|
|
905
|
-
signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
|
|
906
|
-
container: parent.name, isStatic: own.entry.isStatic, target: declaration,
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
extractMethodOperationRows(method, importMap, ownByDeclNode, filePath, serviceName, entries) {
|
|
910
|
-
const own = ownByDeclNode.get(method);
|
|
911
|
-
const declaration = this.declarationNameFor(method, own?.entry.name ?? AstParserService.memberName(method.getNameNode()) ?? '<anonymous>');
|
|
912
|
-
for (const dec of method.getDecorators()) {
|
|
913
|
-
const rec = this.recognizedDecorator(dec, importMap);
|
|
914
|
-
if (!rec)
|
|
915
|
-
continue;
|
|
916
|
-
if (rec.name === 'ResolveField') {
|
|
917
|
-
// Isolated per field: a throw resolving this field's parent (e.g. the
|
|
918
|
-
// injected test seam) degrades to "no row for that field" and never
|
|
919
|
-
// trips the file-level catch in `analyzeSourceFile`.
|
|
920
|
-
try {
|
|
921
|
-
this.extractResolveFieldRow(method, rec, own, declaration, importMap, filePath, serviceName, entries);
|
|
922
|
-
}
|
|
923
|
-
catch (err) {
|
|
924
|
-
this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent');
|
|
925
|
-
}
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
const kind = GRAPHQL_OPERATION_KIND[rec.name];
|
|
929
|
-
if (!kind)
|
|
930
|
-
continue; // recognised but not an operation decorator (wrong declaration kind) — ignored, no log
|
|
931
|
-
if (!own) {
|
|
932
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, 'no-symbol-row');
|
|
933
|
-
continue;
|
|
934
|
-
}
|
|
935
|
-
const nameResult = this.deriveGraphqlName(rec.args, true, own.entry.name);
|
|
936
|
-
if (!nameResult.ok) {
|
|
937
|
-
this.logGraphqlSkip(filePath, declaration, rec.name, nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
|
|
938
|
-
continue;
|
|
939
|
-
}
|
|
940
|
-
entries.push({
|
|
941
|
-
project: serviceName, filePath, name: nameResult.name, kind,
|
|
942
|
-
signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
|
|
943
|
-
container: rec.name, isStatic: own.entry.isStatic, target: declaration,
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
/** GraphQL-named rows for every recognised `@nestjs/graphql` decorator in
|
|
948
|
-
* the file (`graphql-symbols`). `ownRows` is `symbols ∪ members` — the
|
|
949
|
-
* declarations' own `code_symbol` candidates, checked so a GraphQL row is
|
|
950
|
-
* never written without its decorated declaration's own row. */
|
|
951
|
-
extractGraphqlNames(sourceFile, filePath, serviceName, ownRows) {
|
|
952
|
-
const importMap = this.buildGraphqlImportMap(sourceFile);
|
|
953
|
-
if (importMap.size === 0)
|
|
954
|
-
return [];
|
|
955
|
-
const ownByDeclNode = new Map();
|
|
956
|
-
for (const c of ownRows)
|
|
957
|
-
ownByDeclNode.set(c.declNode, c);
|
|
958
|
-
const entries = [];
|
|
959
|
-
for (const cls of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration)) {
|
|
960
|
-
this.extractClassTypeRows(cls, importMap, ownByDeclNode, filePath, serviceName, entries);
|
|
961
|
-
}
|
|
962
|
-
for (const method of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)) {
|
|
963
|
-
this.extractMethodOperationRows(method, importMap, ownByDeclNode, filePath, serviceName, entries);
|
|
964
|
-
}
|
|
965
|
-
return entries;
|
|
966
|
-
}
|
|
967
|
-
// ── private: shared container resolution ────────────────────────────────────
|
|
968
|
-
static isValidSegment(s) {
|
|
969
|
-
return VALID_SEGMENT.test(s);
|
|
970
|
-
}
|
|
971
|
-
static memberName(nameNode) {
|
|
972
|
-
if (!nameNode)
|
|
973
|
-
return null;
|
|
974
|
-
if (ts_morph_1.Node.isPrivateIdentifier(nameNode))
|
|
975
|
-
return nameNode.getText();
|
|
976
|
-
if (ts_morph_1.Node.isIdentifier(nameNode))
|
|
977
|
-
return nameNode.getText();
|
|
978
|
-
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
979
|
-
return nameNode.getLiteralValue();
|
|
980
|
-
if (ts_morph_1.Node.isNumericLiteral(nameNode))
|
|
981
|
-
return nameNode.getText();
|
|
982
|
-
if (ts_morph_1.Node.isComputedPropertyName(nameNode))
|
|
983
|
-
return nameNode.getText().replace(/\s+/g, '');
|
|
984
|
-
return null;
|
|
985
|
-
}
|
|
986
|
-
static unwrapExpr(node) {
|
|
987
|
-
let current = node;
|
|
988
|
-
while (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current)) {
|
|
989
|
-
current = current.getExpression();
|
|
990
|
-
}
|
|
991
|
-
return current;
|
|
992
|
-
}
|
|
993
|
-
static unwrapParent(node) {
|
|
994
|
-
let current = node.getParent();
|
|
995
|
-
while (current && (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current))) {
|
|
996
|
-
current = current.getParent();
|
|
997
|
-
}
|
|
998
|
-
return current;
|
|
999
|
-
}
|
|
1000
|
-
isModuleOrNamespaceLevelVariable(varDecl) {
|
|
1001
|
-
// VariableDeclaration -> VariableDeclarationList -> VariableStatement -> (SourceFile | ModuleBlock)
|
|
1002
|
-
const stmt = varDecl.getParent()?.getParent();
|
|
1003
|
-
const stmtParent = stmt?.getParent();
|
|
1004
|
-
return ts_morph_1.Node.isSourceFile(stmtParent) || ts_morph_1.Node.isModuleBlock(stmtParent);
|
|
1005
|
-
}
|
|
1006
|
-
classExpressionRoot(classExpr) {
|
|
1007
|
-
const parent = AstParserService.unwrapParent(classExpr);
|
|
1008
|
-
if (!parent)
|
|
1009
|
-
return null;
|
|
1010
|
-
if (ts_morph_1.Node.isVariableDeclaration(parent)) {
|
|
1011
|
-
const name = parent.getName();
|
|
1012
|
-
return name ? { name, namingParent: parent } : null;
|
|
1013
|
-
}
|
|
1014
|
-
if (ts_morph_1.Node.isExportAssignment(parent) && !parent.isExportEquals()) {
|
|
1015
|
-
return { name: 'default', namingParent: parent };
|
|
1016
|
-
}
|
|
1017
|
-
return null;
|
|
1018
|
-
}
|
|
1019
|
-
objectLiteralRootValid(obj) {
|
|
1020
|
-
const parent = AstParserService.unwrapParent(obj);
|
|
1021
|
-
if (!parent)
|
|
1022
|
-
return false;
|
|
1023
|
-
if (ts_morph_1.Node.isPropertyAssignment(parent))
|
|
1024
|
-
return true;
|
|
1025
|
-
if (ts_morph_1.Node.isVariableDeclaration(parent))
|
|
1026
|
-
return this.isModuleOrNamespaceLevelVariable(parent);
|
|
1027
|
-
if (ts_morph_1.Node.isExportAssignment(parent) && !parent.isExportEquals())
|
|
1028
|
-
return true;
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
isTopLevelOrNamespaceDirect(node) {
|
|
1032
|
-
return ts_morph_1.Node.isSourceFile(node) || ts_morph_1.Node.isModuleBlock(node);
|
|
1033
|
-
}
|
|
1034
|
-
/** One step of the ancestor walk used by `containerSegments`: does `node`
|
|
1035
|
-
* contribute a named segment, pass through transparently, or disqualify
|
|
1036
|
-
* (exclude) everything nested inside it? */
|
|
1037
|
-
walkStep(node) {
|
|
1038
|
-
if (ts_morph_1.Node.isModuleDeclaration(node)) {
|
|
1039
|
-
if (node.compilerNode.flags & ts_morph_1.NodeFlags.GlobalAugmentation)
|
|
1040
|
-
return { kind: 'exclude' };
|
|
1041
|
-
const nameNode = node.getNameNode();
|
|
1042
|
-
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
1043
|
-
return { kind: 'exclude' };
|
|
1044
|
-
return { kind: 'segment', text: nameNode.getText() };
|
|
1045
|
-
}
|
|
1046
|
-
if (ts_morph_1.Node.isClassDeclaration(node)) {
|
|
1047
|
-
const name = node.getName();
|
|
1048
|
-
if (name)
|
|
1049
|
-
return { kind: 'segment', text: name };
|
|
1050
|
-
if (node.isDefaultExport())
|
|
1051
|
-
return { kind: 'segment', text: 'default' };
|
|
1052
|
-
return { kind: 'exclude' };
|
|
1053
|
-
}
|
|
1054
|
-
if (ts_morph_1.Node.isClassExpression(node)) {
|
|
1055
|
-
const root = this.classExpressionRoot(node);
|
|
1056
|
-
if (!root)
|
|
1057
|
-
return { kind: 'exclude' };
|
|
1058
|
-
return { kind: 'segment', text: root.name, skipTo: root.namingParent };
|
|
1059
|
-
}
|
|
1060
|
-
if (ts_morph_1.Node.isFunctionDeclaration(node)) {
|
|
1061
|
-
const name = node.getName();
|
|
1062
|
-
return name ? { kind: 'segment', text: name } : { kind: 'transparent' };
|
|
1063
|
-
}
|
|
1064
|
-
if (ts_morph_1.Node.isMethodDeclaration(node) || ts_morph_1.Node.isGetAccessorDeclaration(node) || ts_morph_1.Node.isSetAccessorDeclaration(node)) {
|
|
1065
|
-
const name = AstParserService.memberName(node.getNameNode());
|
|
1066
|
-
return name !== null ? { kind: 'segment', text: name } : { kind: 'exclude' };
|
|
1067
|
-
}
|
|
1068
|
-
if (ts_morph_1.Node.isConstructorDeclaration(node)) {
|
|
1069
|
-
return { kind: 'segment', text: 'constructor' };
|
|
1070
|
-
}
|
|
1071
|
-
if (ts_morph_1.Node.isPropertyDeclaration(node) || ts_morph_1.Node.isPropertyAssignment(node)) {
|
|
1072
|
-
const name = AstParserService.memberName(node.getNameNode());
|
|
1073
|
-
return name !== null ? { kind: 'segment', text: name } : { kind: 'exclude' };
|
|
1074
|
-
}
|
|
1075
|
-
if (ts_morph_1.Node.isInterfaceDeclaration(node)) {
|
|
1076
|
-
return { kind: 'segment', text: node.getName() };
|
|
1077
|
-
}
|
|
1078
|
-
if (ts_morph_1.Node.isTypeAliasDeclaration(node)) {
|
|
1079
|
-
return { kind: 'segment', text: node.getName() };
|
|
1080
|
-
}
|
|
1081
|
-
if (ts_morph_1.Node.isVariableDeclaration(node)) {
|
|
1082
|
-
return { kind: 'segment', text: node.getName() };
|
|
1083
|
-
}
|
|
1084
|
-
if (ts_morph_1.Node.isObjectLiteralExpression(node)) {
|
|
1085
|
-
return this.objectLiteralRootValid(node) ? { kind: 'transparent' } : { kind: 'exclude' };
|
|
1086
|
-
}
|
|
1087
|
-
return { kind: 'transparent' };
|
|
1088
|
-
}
|
|
1089
|
-
/** The dotted container path of `node`'s enclosing named scopes, outermost
|
|
1090
|
-
* first, or null when any contributing ancestor is invalid (a computed /
|
|
1091
|
-
* string-literal segment, a string-named module, `declare global`, or an
|
|
1092
|
-
* unrooted class expression / object literal). */
|
|
1093
|
-
containerSegments(node) {
|
|
1094
|
-
const segments = [];
|
|
1095
|
-
let current = node.getParent();
|
|
1096
|
-
while (current) {
|
|
1097
|
-
const step = this.walkStep(current);
|
|
1098
|
-
if (step.kind === 'exclude')
|
|
1099
|
-
return null;
|
|
1100
|
-
if (step.kind === 'segment') {
|
|
1101
|
-
if (!AstParserService.isValidSegment(step.text))
|
|
1102
|
-
return null;
|
|
1103
|
-
segments.unshift(step.text);
|
|
1104
|
-
current = step.skipTo ? step.skipTo.getParent() : current.getParent();
|
|
1105
|
-
continue;
|
|
1106
|
-
}
|
|
1107
|
-
current = current.getParent();
|
|
1108
|
-
}
|
|
1109
|
-
return segments;
|
|
1110
|
-
}
|
|
1111
|
-
/** If `node` is a declaration this service indexes (top-level symbol, member,
|
|
1112
|
-
* or collected class), its own name + container. Otherwise null. Shared by
|
|
1113
|
-
* member extraction, ownership ranges and the call graph so all three name a
|
|
1114
|
-
* declaration identically. */
|
|
1115
|
-
describeIndexedNode(node) {
|
|
1116
|
-
if (ts_morph_1.Node.isClassDeclaration(node)) {
|
|
1117
|
-
const name = node.getName() ?? (node.isDefaultExport() ? 'default' : null);
|
|
1118
|
-
if (name === null)
|
|
1119
|
-
return null;
|
|
1120
|
-
const container = this.containerSegments(node);
|
|
1121
|
-
return container === null ? null : { name, container };
|
|
1122
|
-
}
|
|
1123
|
-
if (ts_morph_1.Node.isClassExpression(node)) {
|
|
1124
|
-
const root = this.classExpressionRoot(node);
|
|
1125
|
-
if (!root)
|
|
1126
|
-
return null;
|
|
1127
|
-
const container = this.containerSegments(root.namingParent);
|
|
1128
|
-
return container === null ? null : { name: root.name, container };
|
|
1129
|
-
}
|
|
1130
|
-
if (ts_morph_1.Node.isFunctionDeclaration(node)) {
|
|
1131
|
-
const name = node.getName();
|
|
1132
|
-
if (!name || !this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
1133
|
-
return null;
|
|
1134
|
-
const container = this.containerSegments(node);
|
|
1135
|
-
return container === null ? null : { name, container };
|
|
1136
|
-
}
|
|
1137
|
-
if (ts_morph_1.Node.isMethodDeclaration(node) || ts_morph_1.Node.isGetAccessorDeclaration(node) || ts_morph_1.Node.isSetAccessorDeclaration(node)) {
|
|
1138
|
-
const name = AstParserService.memberName(node.getNameNode());
|
|
1139
|
-
if (name === null)
|
|
1140
|
-
return null;
|
|
1141
|
-
const container = this.containerSegments(node);
|
|
1142
|
-
return container === null ? null : { name, container };
|
|
1143
|
-
}
|
|
1144
|
-
if (ts_morph_1.Node.isConstructorDeclaration(node)) {
|
|
1145
|
-
const container = this.containerSegments(node);
|
|
1146
|
-
return container === null ? null : { name: 'constructor', container };
|
|
1147
|
-
}
|
|
1148
|
-
if (ts_morph_1.Node.isPropertyDeclaration(node) || ts_morph_1.Node.isPropertyAssignment(node)) {
|
|
1149
|
-
const init = node.getInitializer();
|
|
1150
|
-
if (!init)
|
|
1151
|
-
return null;
|
|
1152
|
-
const unwrapped = AstParserService.unwrapExpr(init);
|
|
1153
|
-
if (!(ts_morph_1.Node.isArrowFunction(unwrapped) || ts_morph_1.Node.isFunctionExpression(unwrapped)))
|
|
1154
|
-
return null;
|
|
1155
|
-
const name = AstParserService.memberName(node.getNameNode());
|
|
1156
|
-
if (name === null)
|
|
1157
|
-
return null;
|
|
1158
|
-
const container = this.containerSegments(node);
|
|
1159
|
-
return container === null ? null : { name, container };
|
|
1160
|
-
}
|
|
1161
|
-
if (ts_morph_1.Node.isInterfaceDeclaration(node)) {
|
|
1162
|
-
if (!this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
1163
|
-
return null;
|
|
1164
|
-
const container = this.containerSegments(node);
|
|
1165
|
-
return container === null ? null : { name: node.getName(), container };
|
|
1166
|
-
}
|
|
1167
|
-
if (ts_morph_1.Node.isTypeAliasDeclaration(node)) {
|
|
1168
|
-
if (!this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
1169
|
-
return null;
|
|
1170
|
-
const container = this.containerSegments(node);
|
|
1171
|
-
return container === null ? null : { name: node.getName(), container };
|
|
1172
|
-
}
|
|
1173
|
-
if (ts_morph_1.Node.isVariableDeclaration(node)) {
|
|
1174
|
-
if (!this.isModuleOrNamespaceLevelVariable(node))
|
|
1175
|
-
return null;
|
|
1176
|
-
const container = this.containerSegments(node);
|
|
1177
|
-
return container === null ? null : { name: node.getName(), container };
|
|
1178
|
-
}
|
|
1179
|
-
if (ts_morph_1.Node.isMethodSignature(node) || ts_morph_1.Node.isPropertySignature(node)) {
|
|
1180
|
-
if (ts_morph_1.Node.isPropertySignature(node)) {
|
|
1181
|
-
const typeNode = node.getTypeNode();
|
|
1182
|
-
if (!typeNode || !ts_morph_1.Node.isFunctionTypeNode(typeNode))
|
|
1183
|
-
return null;
|
|
1184
|
-
}
|
|
1185
|
-
const parent = node.getParent();
|
|
1186
|
-
let validParent = false;
|
|
1187
|
-
if (ts_morph_1.Node.isInterfaceDeclaration(parent)) {
|
|
1188
|
-
validParent = true;
|
|
1189
|
-
}
|
|
1190
|
-
else if (ts_morph_1.Node.isTypeLiteral(parent)) {
|
|
1191
|
-
const aliasParent = parent.getParent();
|
|
1192
|
-
validParent = ts_morph_1.Node.isTypeAliasDeclaration(aliasParent) && aliasParent.getTypeNode() === parent;
|
|
1193
|
-
}
|
|
1194
|
-
if (!validParent)
|
|
1195
|
-
return null;
|
|
1196
|
-
const name = AstParserService.memberName(node.getNameNode());
|
|
1197
|
-
if (name === null)
|
|
1198
|
-
return null;
|
|
1199
|
-
const container = this.containerSegments(node);
|
|
1200
|
-
return container === null ? null : { name, container };
|
|
1201
|
-
}
|
|
1202
|
-
return null;
|
|
1203
|
-
}
|
|
1204
|
-
qualifiedName(described) {
|
|
1205
|
-
return described.container.length ? `${described.container.join('.')}.${described.name}` : described.name;
|
|
1206
|
-
}
|
|
1207
|
-
/** The qualified name of the nearest ancestor of `node` that is itself an
|
|
1208
|
-
* indexed declaration (method, accessor, constructor, function property,
|
|
1209
|
-
* namespace/top-level function, or module-/namespace-level variable). */
|
|
1210
|
-
findFromSymbol(node) {
|
|
1211
|
-
let current = node.getParent();
|
|
1212
|
-
while (current) {
|
|
1213
|
-
const described = this.describeIndexedNode(current);
|
|
1214
|
-
if (described)
|
|
1215
|
-
return this.qualifiedName(described);
|
|
1216
|
-
current = current.getParent();
|
|
235
|
+
buildDeclarations(sourceFile, filePath, symbolCandidates, memberCandidates) {
|
|
236
|
+
try {
|
|
237
|
+
return this.declarationBuilder.build(sourceFile, [...symbolCandidates, ...memberCandidates]);
|
|
1217
238
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
// ── private: call graph extraction ────────────────────────────────────────
|
|
1221
|
-
extractCalls(sourceFile, filePath, serviceName) {
|
|
1222
|
-
const calls = [];
|
|
1223
|
-
for (const callExpr of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)) {
|
|
1224
|
-
const expr = callExpr.getExpression();
|
|
1225
|
-
let calleeName = null;
|
|
1226
|
-
let calleeFile = null;
|
|
1227
|
-
let calleeDecl;
|
|
239
|
+
catch (err) {
|
|
240
|
+
this.logger.warn('AstParser: declaration tree build failed', { filePath, err });
|
|
1228
241
|
try {
|
|
1229
|
-
|
|
1230
|
-
calleeName = expr.getName();
|
|
1231
|
-
const sym = expr.getNameNode().getSymbol();
|
|
1232
|
-
const decl = sym?.getDeclarations()?.[0];
|
|
1233
|
-
const declPath = decl?.getSourceFile().getFilePath();
|
|
1234
|
-
if (declPath && !declPath.includes('/node_modules/')) {
|
|
1235
|
-
calleeFile = declPath;
|
|
1236
|
-
calleeDecl = decl;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
else if (ts_morph_1.Node.isIdentifier(expr)) {
|
|
1240
|
-
calleeName = expr.getText();
|
|
1241
|
-
const sym = expr.getSymbol();
|
|
1242
|
-
if (sym) {
|
|
1243
|
-
// Imported identifiers resolve to ImportSpecifier (same file); follow the alias.
|
|
1244
|
-
const firstDecl = sym.getDeclarations()?.[0];
|
|
1245
|
-
const resolved = firstDecl && ts_morph_1.Node.isImportSpecifier(firstDecl)
|
|
1246
|
-
? (sym.getAliasedSymbol() ?? sym)
|
|
1247
|
-
: sym;
|
|
1248
|
-
const decl = resolved.getDeclarations()?.[0];
|
|
1249
|
-
const declPath = decl?.getSourceFile().getFilePath();
|
|
1250
|
-
if (declPath && !declPath.includes('/node_modules/')) {
|
|
1251
|
-
calleeFile = declPath;
|
|
1252
|
-
calleeDecl = decl;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
242
|
+
return this.declarationBuilder.flat(symbolCandidates);
|
|
1256
243
|
}
|
|
1257
244
|
catch {
|
|
1258
|
-
|
|
245
|
+
return DeclarationTree_1.DeclarationTree.EMPTY;
|
|
1259
246
|
}
|
|
1260
|
-
if (!calleeName || !calleeFile)
|
|
1261
|
-
continue;
|
|
1262
|
-
const described = calleeDecl ? this.describeIndexedNode(calleeDecl) : null;
|
|
1263
|
-
const calleeSymbol = described ? this.qualifiedName(described) : null;
|
|
1264
|
-
const fromSymbol = this.findFromSymbol(callExpr);
|
|
1265
|
-
const pos = sourceFile.compilerNode
|
|
1266
|
-
.getLineAndCharacterOfPosition(callExpr.getStart());
|
|
1267
|
-
calls.push({
|
|
1268
|
-
project: serviceName,
|
|
1269
|
-
fromFile: filePath,
|
|
1270
|
-
fromSymbol,
|
|
1271
|
-
calleeName,
|
|
1272
|
-
calleeFile,
|
|
1273
|
-
calleeSymbol,
|
|
1274
|
-
line: pos.line + 1,
|
|
1275
|
-
col: pos.character + 1,
|
|
1276
|
-
});
|
|
1277
247
|
}
|
|
1278
|
-
return calls;
|
|
1279
248
|
}
|
|
1280
249
|
}
|
|
1281
250
|
exports.AstParserService = AstParserService;
|