codesearch-mcp 0.1.0-alpha → 0.2.1-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 +39 -0
- package/README.md +146 -41
- package/dist/app.js +4 -19
- package/dist/src/commands/IndexCommand.js +68 -7
- package/dist/src/config-error.d.ts +7 -0
- package/dist/src/config-error.js +14 -0
- package/dist/src/config-store.d.ts +30 -12
- package/dist/src/config-store.js +81 -26
- package/dist/src/config.d.ts +5 -4
- package/dist/src/config.js +14 -16
- package/dist/src/constants.d.ts +8 -2
- package/dist/src/constants.js +8 -5
- package/dist/src/db/index.d.ts +1 -1
- package/dist/src/db/index.js +2 -1
- package/dist/src/db/schema.d.ts +75 -0
- package/dist/src/db/schema.js +10 -1
- package/dist/src/enriched-prompt.d.ts +8 -0
- package/dist/src/enriched-prompt.js +87 -0
- package/dist/src/help.js +1 -1
- package/dist/src/interfaces/IAstParser.d.ts +18 -2
- package/dist/src/interfaces/IDatabase.d.ts +31 -0
- package/dist/src/interfaces/ILogger.d.ts +7 -0
- package/dist/src/interfaces/ISearcher.d.ts +3 -0
- package/dist/src/services/AstChunker.d.ts +11 -0
- package/dist/src/services/AstChunker.js +44 -0
- package/dist/src/services/AstParserService.d.ts +39 -7
- package/dist/src/services/AstParserService.js +504 -104
- package/dist/src/services/DatabaseService.d.ts +17 -0
- package/dist/src/services/DatabaseService.js +130 -2
- package/dist/src/services/EmbedderFactory.d.ts +1 -1
- package/dist/src/services/EmbedderFactory.js +2 -5
- package/dist/src/services/IndexerService.d.ts +4 -14
- package/dist/src/services/IndexerService.js +54 -156
- package/dist/src/services/InfinityEmbedder.d.ts +14 -55
- package/dist/src/services/InfinityEmbedder.js +30 -141
- package/dist/src/services/LoggerService.d.ts +3 -1
- package/dist/src/services/LoggerService.js +17 -3
- package/dist/src/services/McpServer.js +3 -3
- package/dist/src/services/SearcherService.d.ts +39 -4
- package/dist/src/services/SearcherService.js +254 -81
- package/dist/src/services/WebServerService.d.ts +41 -3
- package/dist/src/services/WebServerService.js +90 -2
- package/dist/src/services/infinity-provisioning.d.ts +115 -0
- package/dist/src/services/infinity-provisioning.js +516 -0
- package/dist/src/services/legacy-ollama-index.d.ts +24 -0
- package/dist/src/services/legacy-ollama-index.js +61 -0
- package/dist/src/services/reranker.d.ts +3 -3
- package/dist/src/services/reranker.js +3 -3
- package/dist/src/setup.d.ts +37 -0
- package/dist/src/setup.js +111 -157
- package/dist/src/symbol-name.d.ts +44 -0
- package/dist/src/symbol-name.js +68 -0
- package/dist/src/symbol-rows.d.ts +12 -0
- package/dist/src/symbol-rows.js +38 -0
- package/dist/src/tools/FindSymbolTool.js +30 -7
- package/dist/src/tools/GetCallersTool.js +20 -5
- package/docker/docker-compose.yml +5 -15
- package/docs/architecture.svg +103 -0
- package/docs/web-dashboard.md +36 -17
- package/package.json +1 -6
- package/dist/src/commands/BackfillCommand.d.ts +0 -7
- package/dist/src/commands/BackfillCommand.js +0 -37
- package/dist/src/services/OllamaEmbedder.d.ts +0 -25
- package/dist/src/services/OllamaEmbedder.js +0 -111
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AstParserService = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
|
+
const typescript_1 = require("typescript");
|
|
39
40
|
const ts_morph_1 = require("ts-morph");
|
|
40
41
|
const LoggerService_1 = require("./LoggerService");
|
|
41
42
|
const FALLBACK_OPTS = {
|
|
@@ -44,6 +45,7 @@ const FALLBACK_OPTS = {
|
|
|
44
45
|
compilerOptions: { allowJs: true, noEmit: true },
|
|
45
46
|
};
|
|
46
47
|
const MORPH_CACHE_MAX = 10;
|
|
48
|
+
const VALID_SEGMENT = /^#?[\w$]+$/;
|
|
47
49
|
class AstParserService {
|
|
48
50
|
logger;
|
|
49
51
|
morphProjects = new Map();
|
|
@@ -60,7 +62,7 @@ class AstParserService {
|
|
|
60
62
|
const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution, rootTsConfigPath);
|
|
61
63
|
for (const filePath of filePaths) {
|
|
62
64
|
if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
|
|
63
|
-
analysisMap.set(filePath, { edges: [], symbols: [], calls: [] });
|
|
65
|
+
analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [] });
|
|
64
66
|
continue;
|
|
65
67
|
}
|
|
66
68
|
try {
|
|
@@ -68,22 +70,12 @@ class AstParserService {
|
|
|
68
70
|
}
|
|
69
71
|
catch (err) {
|
|
70
72
|
this.logger.warn('AstParser: failed to add source file', { filePath, reason: err.message });
|
|
71
|
-
analysisMap.set(filePath, { edges: [], symbols: [], calls: [] });
|
|
73
|
+
analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [] });
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
for (const sourceFile of morphProject.getSourceFiles()) {
|
|
75
77
|
const filePath = sourceFile.getFilePath();
|
|
76
|
-
|
|
77
|
-
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
78
|
-
const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
79
|
-
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
80
|
-
const ownerRanges = this.extractOwnerRanges(sourceFile);
|
|
81
|
-
analysisMap.set(filePath, { edges, symbols, calls, ownerRanges });
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
this.logger.warn('AstParser: failed to analyze file', { filePath, reason: err.message });
|
|
85
|
-
analysisMap.set(filePath, { edges: [], symbols: [], calls: [] });
|
|
86
|
-
}
|
|
78
|
+
analysisMap.set(filePath, this.analyzeSourceFile(sourceFile, filePath, serviceName));
|
|
87
79
|
}
|
|
88
80
|
// Release source file memory before returning
|
|
89
81
|
for (const sourceFile of morphProject.getSourceFiles()) {
|
|
@@ -110,7 +102,7 @@ class AstParserService {
|
|
|
110
102
|
this.logger.debug('AstParser: analyzeFile start', { filePath, serviceName, projectRoot });
|
|
111
103
|
if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
|
|
112
104
|
this.logger.debug('AstParser: analyzeFile skip (non-TS)', { filePath, ms: Date.now() - t0 });
|
|
113
|
-
return { edges: [], symbols: [], calls: [] };
|
|
105
|
+
return { edges: [], symbols: [], calls: [], members: [] };
|
|
114
106
|
}
|
|
115
107
|
const morphProject = this.getMorphProject(projectRoot);
|
|
116
108
|
const existing = morphProject.getSourceFile(filePath);
|
|
@@ -122,21 +114,48 @@ class AstParserService {
|
|
|
122
114
|
}
|
|
123
115
|
catch (err) {
|
|
124
116
|
this.logger.warn('AstParser: analyzeFile failed to add source file', { filePath, reason: err.message, ms: Date.now() - t0 });
|
|
125
|
-
return { edges: [], symbols: [], calls: [] };
|
|
117
|
+
return { edges: [], symbols: [], calls: [], members: [] };
|
|
126
118
|
}
|
|
127
|
-
const
|
|
128
|
-
const symbols = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
129
|
-
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
130
|
-
const ownerRanges = this.extractOwnerRanges(sourceFile);
|
|
119
|
+
const result = this.analyzeSourceFile(sourceFile, filePath, serviceName);
|
|
131
120
|
morphProject.removeSourceFile(sourceFile);
|
|
132
|
-
this.logger.debug('AstParser: analyzeFile done', {
|
|
133
|
-
|
|
121
|
+
this.logger.debug('AstParser: analyzeFile done', {
|
|
122
|
+
filePath, edges: result.edges.length, symbols: result.symbols.length, members: result.members.length, calls: result.calls.length, ms: Date.now() - t0,
|
|
123
|
+
});
|
|
124
|
+
return result;
|
|
134
125
|
}
|
|
135
126
|
// Kept for test compatibility (extract-import-edges.test.ts).
|
|
136
127
|
async extractImportEdges(filePath, serviceName, projectRoot) {
|
|
137
128
|
const { edges } = await this.analyzeFile(filePath, serviceName, projectRoot);
|
|
138
129
|
return edges;
|
|
139
130
|
}
|
|
131
|
+
analyzeSourceFile(sourceFile, filePath, serviceName) {
|
|
132
|
+
try {
|
|
133
|
+
const edges = this.extractEdges(sourceFile, filePath, serviceName);
|
|
134
|
+
const symbolCandidates = this.extractSymbols(sourceFile, filePath, serviceName);
|
|
135
|
+
const symbols = symbolCandidates.map(c => c.entry);
|
|
136
|
+
// Member extraction (classes anywhere, class/interface/object-literal
|
|
137
|
+
// members, namespace-level declarations) is isolated in its own try: a
|
|
138
|
+
// bug there must never change a file's top-level symbols, chunks or
|
|
139
|
+
// embedding prompt (see the `symbol-index-members` retrieval-invariance
|
|
140
|
+
// requirement).
|
|
141
|
+
let memberCandidates = [];
|
|
142
|
+
try {
|
|
143
|
+
memberCandidates = this.extractMembers(sourceFile, filePath, serviceName);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
this.logger.warn('AstParser: member extraction failed', { filePath, reason: err.message });
|
|
147
|
+
memberCandidates = [];
|
|
148
|
+
}
|
|
149
|
+
const members = memberCandidates.map(c => c.entry);
|
|
150
|
+
const calls = this.extractCalls(sourceFile, filePath, serviceName);
|
|
151
|
+
const ownerRanges = this.buildOwnerRanges([...symbolCandidates, ...memberCandidates]);
|
|
152
|
+
return { edges, symbols, members, calls, ownerRanges };
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.logger.warn('AstParser: failed to analyze file', { filePath, reason: err.message });
|
|
156
|
+
return { edges: [], symbols: [], calls: [], members: [] };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
140
159
|
// ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
|
|
141
160
|
createMorphProject(projectRoot, skipFileDependencyResolution = false, rootTsConfigPath) {
|
|
142
161
|
const t0 = Date.now();
|
|
@@ -307,96 +326,492 @@ class AstParserService {
|
|
|
307
326
|
}
|
|
308
327
|
return map;
|
|
309
328
|
}
|
|
310
|
-
// ── private:
|
|
329
|
+
// ── private: position / signature helpers ───────────────────────────────────
|
|
330
|
+
getPos(sourceFile, node) {
|
|
331
|
+
const { line, character } = sourceFile.compilerNode
|
|
332
|
+
.getLineAndCharacterOfPosition(node.getStart());
|
|
333
|
+
return { line: line + 1, col: character + 1 };
|
|
334
|
+
}
|
|
335
|
+
/** Trimmed text of the source line containing `node`'s start — the signature
|
|
336
|
+
* rule for every kind (name node for members, the position node for
|
|
337
|
+
* constructors/anonymous default classes). */
|
|
338
|
+
lineTextAt(sourceLines, sourceFile, node) {
|
|
339
|
+
const { line } = this.getPos(sourceFile, node);
|
|
340
|
+
return (sourceLines[line - 1] ?? node.getText().split('\n')[0]).trim();
|
|
341
|
+
}
|
|
342
|
+
// ── private: top-level symbol extraction (feeds the chunker + prompt) ──────
|
|
311
343
|
extractSymbols(sourceFile, filePath, serviceName) {
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
return found?.trim() ?? text.split('\n')[0].trim();
|
|
316
|
-
};
|
|
317
|
-
const getPos = (node) => {
|
|
318
|
-
const { line, character } = sourceFile.compilerNode
|
|
319
|
-
.getLineAndCharacterOfPosition(node.getStart());
|
|
320
|
-
return { line: line + 1, col: character + 1 };
|
|
321
|
-
};
|
|
344
|
+
const candidates = [];
|
|
345
|
+
const sourceLines = sourceFile.getFullText().split('\n');
|
|
346
|
+
const lineAt = (node) => this.lineTextAt(sourceLines, sourceFile, node);
|
|
322
347
|
for (const cls of sourceFile.getClasses()) {
|
|
323
348
|
const name = cls.getName();
|
|
324
349
|
if (!name)
|
|
325
350
|
continue;
|
|
326
|
-
const nameNode = cls.getNameNode();
|
|
327
|
-
const { line, col } =
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
351
|
+
const nameNode = cls.getNameNode() ?? cls;
|
|
352
|
+
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
353
|
+
candidates.push({
|
|
354
|
+
declNode: cls,
|
|
355
|
+
entry: {
|
|
356
|
+
project: serviceName, filePath, name, kind: 'class',
|
|
357
|
+
signature: lineAt(nameNode),
|
|
358
|
+
line, endLine: cls.getEndLineNumber(), col,
|
|
359
|
+
container: null, isStatic: false,
|
|
360
|
+
},
|
|
332
361
|
});
|
|
333
362
|
}
|
|
334
363
|
for (const fn of sourceFile.getFunctions()) {
|
|
335
364
|
const name = fn.getName();
|
|
336
365
|
if (!name)
|
|
337
366
|
continue;
|
|
338
|
-
const nameNode = fn.getNameNode();
|
|
339
|
-
const { line, col } =
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
367
|
+
const nameNode = fn.getNameNode() ?? fn;
|
|
368
|
+
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
369
|
+
candidates.push({
|
|
370
|
+
declNode: fn,
|
|
371
|
+
entry: {
|
|
372
|
+
project: serviceName, filePath, name, kind: 'function',
|
|
373
|
+
signature: lineAt(nameNode),
|
|
374
|
+
line, endLine: fn.getEndLineNumber(), col,
|
|
375
|
+
container: null, isStatic: false, hasBody: fn.getBody() !== undefined,
|
|
376
|
+
},
|
|
344
377
|
});
|
|
345
378
|
}
|
|
346
379
|
for (const iface of sourceFile.getInterfaces()) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
380
|
+
const nameNode = iface.getNameNode();
|
|
381
|
+
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
382
|
+
candidates.push({
|
|
383
|
+
declNode: iface,
|
|
384
|
+
entry: {
|
|
385
|
+
project: serviceName, filePath, name: iface.getName(), kind: 'interface',
|
|
386
|
+
signature: lineAt(nameNode),
|
|
387
|
+
line, endLine: iface.getEndLineNumber(), col,
|
|
388
|
+
container: null, isStatic: false,
|
|
389
|
+
},
|
|
352
390
|
});
|
|
353
391
|
}
|
|
354
392
|
for (const ta of sourceFile.getTypeAliases()) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
393
|
+
const nameNode = ta.getNameNode();
|
|
394
|
+
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
395
|
+
candidates.push({
|
|
396
|
+
declNode: ta,
|
|
397
|
+
entry: {
|
|
398
|
+
project: serviceName, filePath, name: ta.getName(), kind: 'type',
|
|
399
|
+
signature: lineAt(nameNode),
|
|
400
|
+
line, endLine: ta.getEndLineNumber(), col,
|
|
401
|
+
container: null, isStatic: false,
|
|
402
|
+
},
|
|
360
403
|
});
|
|
361
404
|
}
|
|
362
405
|
for (const stmt of sourceFile.getVariableStatements()) {
|
|
363
406
|
for (const decl of stmt.getDeclarations()) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
407
|
+
const nameNode = decl.getNameNode();
|
|
408
|
+
const { line, col } = this.getPos(sourceFile, nameNode);
|
|
409
|
+
candidates.push({
|
|
410
|
+
// Owner ranges use the declarator's own span, not the statement's —
|
|
411
|
+
// each declarator of `const a = 1, b = () => {…}` owns only its lines.
|
|
412
|
+
declNode: decl,
|
|
413
|
+
entry: {
|
|
414
|
+
project: serviceName, filePath, name: decl.getName(), kind: 'variable',
|
|
415
|
+
signature: lineAt(nameNode),
|
|
416
|
+
line, endLine: stmt.getEndLineNumber(), col,
|
|
417
|
+
container: null, isStatic: false,
|
|
418
|
+
},
|
|
369
419
|
});
|
|
370
420
|
}
|
|
371
421
|
}
|
|
372
|
-
return
|
|
422
|
+
return candidates;
|
|
373
423
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
424
|
+
// ── private: member extraction (classes anywhere, members, namespace body) ─
|
|
425
|
+
extractMembers(sourceFile, filePath, serviceName) {
|
|
426
|
+
const candidates = [];
|
|
427
|
+
const sourceLines = sourceFile.getFullText().split('\n');
|
|
428
|
+
const lineAt = (node) => this.lineTextAt(sourceLines, sourceFile, node);
|
|
429
|
+
const push = (declNode, posNode, name, kind, container, isStatic, hasBody) => {
|
|
430
|
+
const { line, col } = this.getPos(sourceFile, posNode);
|
|
431
|
+
candidates.push({
|
|
432
|
+
declNode,
|
|
433
|
+
entry: {
|
|
434
|
+
project: serviceName, filePath, name, kind,
|
|
435
|
+
signature: lineAt(posNode),
|
|
436
|
+
line, endLine: declNode.getEndLineNumber(), col,
|
|
437
|
+
container: container.length ? container.join('.') : null,
|
|
438
|
+
isStatic, hasBody,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
// Classes anywhere in the file — nested, expression, anonymous default.
|
|
443
|
+
for (const cls of [
|
|
444
|
+
...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration),
|
|
445
|
+
...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassExpression),
|
|
446
|
+
]) {
|
|
447
|
+
// Named top-level classes are already handled by extractSymbols; an
|
|
448
|
+
// anonymous top-level class (`export default class {}`) is not — it
|
|
449
|
+
// must still be collected here.
|
|
450
|
+
if (ts_morph_1.Node.isClassDeclaration(cls) && cls.getName() && ts_morph_1.Node.isSourceFile(cls.getParent()))
|
|
384
451
|
continue;
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
452
|
+
const described = this.describeIndexedNode(cls);
|
|
453
|
+
if (!described)
|
|
454
|
+
continue;
|
|
455
|
+
const posNode = (ts_morph_1.Node.isClassDeclaration(cls) ? cls.getNameNode() : undefined) ?? cls;
|
|
456
|
+
push(cls, posNode, described.name, 'class', described.container, false, true);
|
|
457
|
+
}
|
|
458
|
+
// Class / object-literal methods, accessors, constructors, function properties.
|
|
459
|
+
for (const m of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)) {
|
|
460
|
+
const described = this.describeIndexedNode(m);
|
|
461
|
+
if (!described)
|
|
462
|
+
continue;
|
|
463
|
+
push(m, m.getNameNode(), described.name, 'method', described.container, m.isStatic(), m.getBody() !== undefined);
|
|
464
|
+
}
|
|
465
|
+
for (const a of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.GetAccessor)) {
|
|
466
|
+
const described = this.describeIndexedNode(a);
|
|
467
|
+
if (!described)
|
|
468
|
+
continue;
|
|
469
|
+
push(a, a.getNameNode(), described.name, 'get_accessor', described.container, a.isStatic(), true);
|
|
470
|
+
}
|
|
471
|
+
for (const a of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.SetAccessor)) {
|
|
472
|
+
const described = this.describeIndexedNode(a);
|
|
473
|
+
if (!described)
|
|
474
|
+
continue;
|
|
475
|
+
push(a, a.getNameNode(), described.name, 'set_accessor', described.container, a.isStatic(), true);
|
|
476
|
+
}
|
|
477
|
+
for (const c of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.Constructor)) {
|
|
478
|
+
const described = this.describeIndexedNode(c);
|
|
479
|
+
if (!described)
|
|
480
|
+
continue;
|
|
481
|
+
const kw = c.getFirstChildByKind(typescript_1.SyntaxKind.ConstructorKeyword) ?? c;
|
|
482
|
+
push(c, kw, described.name, 'constructor', described.container, false, c.getBody() !== undefined);
|
|
483
|
+
}
|
|
484
|
+
for (const p of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyDeclaration)) {
|
|
485
|
+
const described = this.describeIndexedNode(p);
|
|
486
|
+
if (!described)
|
|
487
|
+
continue;
|
|
488
|
+
push(p, p.getNameNode(), described.name, 'function_property', described.container, p.isStatic(), true);
|
|
489
|
+
}
|
|
490
|
+
for (const p of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertyAssignment)) {
|
|
491
|
+
const described = this.describeIndexedNode(p);
|
|
492
|
+
if (!described)
|
|
493
|
+
continue;
|
|
494
|
+
push(p, p.getNameNode(), described.name, 'function_property', described.container, false, true);
|
|
495
|
+
}
|
|
496
|
+
// Interface / type-literal method and function-typed property signatures.
|
|
497
|
+
for (const s of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodSignature)) {
|
|
498
|
+
const described = this.describeIndexedNode(s);
|
|
499
|
+
if (!described)
|
|
500
|
+
continue;
|
|
501
|
+
push(s, s.getNameNode(), described.name, 'method_signature', described.container, false, false);
|
|
502
|
+
}
|
|
503
|
+
for (const s of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.PropertySignature)) {
|
|
504
|
+
const described = this.describeIndexedNode(s);
|
|
505
|
+
if (!described)
|
|
506
|
+
continue;
|
|
507
|
+
push(s, s.getNameNode(), described.name, 'method_signature', described.container, false, false);
|
|
508
|
+
}
|
|
509
|
+
// Declarations directly in a namespace body.
|
|
510
|
+
for (const fn of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.FunctionDeclaration)) {
|
|
511
|
+
if (!ts_morph_1.Node.isModuleBlock(fn.getParent()))
|
|
512
|
+
continue;
|
|
513
|
+
const described = this.describeIndexedNode(fn);
|
|
514
|
+
if (!described)
|
|
515
|
+
continue;
|
|
516
|
+
const nameNode = fn.getNameNode();
|
|
517
|
+
if (!nameNode)
|
|
518
|
+
continue;
|
|
519
|
+
push(fn, nameNode, described.name, 'function', described.container, false, fn.getBody() !== undefined);
|
|
520
|
+
}
|
|
521
|
+
for (const iface of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.InterfaceDeclaration)) {
|
|
522
|
+
if (!ts_morph_1.Node.isModuleBlock(iface.getParent()))
|
|
523
|
+
continue;
|
|
524
|
+
const described = this.describeIndexedNode(iface);
|
|
525
|
+
if (!described)
|
|
526
|
+
continue;
|
|
527
|
+
push(iface, iface.getNameNode(), described.name, 'interface', described.container, false, true);
|
|
528
|
+
}
|
|
529
|
+
for (const ta of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.TypeAliasDeclaration)) {
|
|
530
|
+
if (!ts_morph_1.Node.isModuleBlock(ta.getParent()))
|
|
531
|
+
continue;
|
|
532
|
+
const described = this.describeIndexedNode(ta);
|
|
533
|
+
if (!described)
|
|
534
|
+
continue;
|
|
535
|
+
push(ta, ta.getNameNode(), described.name, 'type', described.container, false, true);
|
|
536
|
+
}
|
|
537
|
+
for (const stmt of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.VariableStatement)) {
|
|
538
|
+
if (!ts_morph_1.Node.isModuleBlock(stmt.getParent()))
|
|
539
|
+
continue;
|
|
540
|
+
for (const decl of stmt.getDeclarations()) {
|
|
541
|
+
const described = this.describeIndexedNode(decl);
|
|
542
|
+
if (!described)
|
|
543
|
+
continue;
|
|
544
|
+
push(decl, decl.getNameNode(), described.name, 'variable', described.container, false, true);
|
|
397
545
|
}
|
|
398
546
|
}
|
|
399
|
-
return
|
|
547
|
+
return candidates;
|
|
548
|
+
}
|
|
549
|
+
/** Chunk-ownership ranges from every indexed declaration (top-level symbols +
|
|
550
|
+
* members) except `method_signature`. Each range spans the declaration node's
|
|
551
|
+
* own first-to-last line — decorator-inclusive, comment-exclusive, and (for a
|
|
552
|
+
* variable) the declarator alone, never the whole statement. */
|
|
553
|
+
buildOwnerRanges(candidates) {
|
|
554
|
+
return candidates
|
|
555
|
+
.filter(c => c.entry.kind !== 'method_signature')
|
|
556
|
+
.map(c => ({
|
|
557
|
+
name: c.entry.name,
|
|
558
|
+
container: c.entry.container,
|
|
559
|
+
line: c.declNode.getStartLineNumber(),
|
|
560
|
+
endLine: c.declNode.getEndLineNumber(),
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
563
|
+
// ── private: shared container resolution ────────────────────────────────────
|
|
564
|
+
static isValidSegment(s) {
|
|
565
|
+
return VALID_SEGMENT.test(s);
|
|
566
|
+
}
|
|
567
|
+
static memberName(nameNode) {
|
|
568
|
+
if (!nameNode)
|
|
569
|
+
return null;
|
|
570
|
+
if (ts_morph_1.Node.isPrivateIdentifier(nameNode))
|
|
571
|
+
return nameNode.getText();
|
|
572
|
+
if (ts_morph_1.Node.isIdentifier(nameNode))
|
|
573
|
+
return nameNode.getText();
|
|
574
|
+
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
575
|
+
return nameNode.getLiteralValue();
|
|
576
|
+
if (ts_morph_1.Node.isNumericLiteral(nameNode))
|
|
577
|
+
return nameNode.getText();
|
|
578
|
+
if (ts_morph_1.Node.isComputedPropertyName(nameNode))
|
|
579
|
+
return nameNode.getText().replace(/\s+/g, '');
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
static unwrapExpr(node) {
|
|
583
|
+
let current = node;
|
|
584
|
+
while (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current)) {
|
|
585
|
+
current = current.getExpression();
|
|
586
|
+
}
|
|
587
|
+
return current;
|
|
588
|
+
}
|
|
589
|
+
static unwrapParent(node) {
|
|
590
|
+
let current = node.getParent();
|
|
591
|
+
while (current && (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current))) {
|
|
592
|
+
current = current.getParent();
|
|
593
|
+
}
|
|
594
|
+
return current;
|
|
595
|
+
}
|
|
596
|
+
isModuleOrNamespaceLevelVariable(varDecl) {
|
|
597
|
+
// VariableDeclaration -> VariableDeclarationList -> VariableStatement -> (SourceFile | ModuleBlock)
|
|
598
|
+
const stmt = varDecl.getParent()?.getParent();
|
|
599
|
+
const stmtParent = stmt?.getParent();
|
|
600
|
+
return ts_morph_1.Node.isSourceFile(stmtParent) || ts_morph_1.Node.isModuleBlock(stmtParent);
|
|
601
|
+
}
|
|
602
|
+
classExpressionRoot(classExpr) {
|
|
603
|
+
const parent = AstParserService.unwrapParent(classExpr);
|
|
604
|
+
if (!parent)
|
|
605
|
+
return null;
|
|
606
|
+
if (ts_morph_1.Node.isVariableDeclaration(parent)) {
|
|
607
|
+
const name = parent.getName();
|
|
608
|
+
return name ? { name, namingParent: parent } : null;
|
|
609
|
+
}
|
|
610
|
+
if (ts_morph_1.Node.isExportAssignment(parent) && !parent.isExportEquals()) {
|
|
611
|
+
return { name: 'default', namingParent: parent };
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
objectLiteralRootValid(obj) {
|
|
616
|
+
const parent = AstParserService.unwrapParent(obj);
|
|
617
|
+
if (!parent)
|
|
618
|
+
return false;
|
|
619
|
+
if (ts_morph_1.Node.isPropertyAssignment(parent))
|
|
620
|
+
return true;
|
|
621
|
+
if (ts_morph_1.Node.isVariableDeclaration(parent))
|
|
622
|
+
return this.isModuleOrNamespaceLevelVariable(parent);
|
|
623
|
+
if (ts_morph_1.Node.isExportAssignment(parent) && !parent.isExportEquals())
|
|
624
|
+
return true;
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
isTopLevelOrNamespaceDirect(node) {
|
|
628
|
+
return ts_morph_1.Node.isSourceFile(node) || ts_morph_1.Node.isModuleBlock(node);
|
|
629
|
+
}
|
|
630
|
+
/** One step of the ancestor walk used by `containerSegments`: does `node`
|
|
631
|
+
* contribute a named segment, pass through transparently, or disqualify
|
|
632
|
+
* (exclude) everything nested inside it? */
|
|
633
|
+
walkStep(node) {
|
|
634
|
+
if (ts_morph_1.Node.isModuleDeclaration(node)) {
|
|
635
|
+
if (node.compilerNode.flags & typescript_1.NodeFlags.GlobalAugmentation)
|
|
636
|
+
return { kind: 'exclude' };
|
|
637
|
+
const nameNode = node.getNameNode();
|
|
638
|
+
if (ts_morph_1.Node.isStringLiteral(nameNode))
|
|
639
|
+
return { kind: 'exclude' };
|
|
640
|
+
return { kind: 'segment', text: nameNode.getText() };
|
|
641
|
+
}
|
|
642
|
+
if (ts_morph_1.Node.isClassDeclaration(node)) {
|
|
643
|
+
const name = node.getName();
|
|
644
|
+
if (name)
|
|
645
|
+
return { kind: 'segment', text: name };
|
|
646
|
+
if (node.isDefaultExport())
|
|
647
|
+
return { kind: 'segment', text: 'default' };
|
|
648
|
+
return { kind: 'exclude' };
|
|
649
|
+
}
|
|
650
|
+
if (ts_morph_1.Node.isClassExpression(node)) {
|
|
651
|
+
const root = this.classExpressionRoot(node);
|
|
652
|
+
if (!root)
|
|
653
|
+
return { kind: 'exclude' };
|
|
654
|
+
return { kind: 'segment', text: root.name, skipTo: root.namingParent };
|
|
655
|
+
}
|
|
656
|
+
if (ts_morph_1.Node.isFunctionDeclaration(node)) {
|
|
657
|
+
const name = node.getName();
|
|
658
|
+
return name ? { kind: 'segment', text: name } : { kind: 'transparent' };
|
|
659
|
+
}
|
|
660
|
+
if (ts_morph_1.Node.isMethodDeclaration(node) || ts_morph_1.Node.isGetAccessorDeclaration(node) || ts_morph_1.Node.isSetAccessorDeclaration(node)) {
|
|
661
|
+
const name = AstParserService.memberName(node.getNameNode());
|
|
662
|
+
return name !== null ? { kind: 'segment', text: name } : { kind: 'exclude' };
|
|
663
|
+
}
|
|
664
|
+
if (ts_morph_1.Node.isConstructorDeclaration(node)) {
|
|
665
|
+
return { kind: 'segment', text: 'constructor' };
|
|
666
|
+
}
|
|
667
|
+
if (ts_morph_1.Node.isPropertyDeclaration(node) || ts_morph_1.Node.isPropertyAssignment(node)) {
|
|
668
|
+
const name = AstParserService.memberName(node.getNameNode());
|
|
669
|
+
return name !== null ? { kind: 'segment', text: name } : { kind: 'exclude' };
|
|
670
|
+
}
|
|
671
|
+
if (ts_morph_1.Node.isInterfaceDeclaration(node)) {
|
|
672
|
+
return { kind: 'segment', text: node.getName() };
|
|
673
|
+
}
|
|
674
|
+
if (ts_morph_1.Node.isTypeAliasDeclaration(node)) {
|
|
675
|
+
return { kind: 'segment', text: node.getName() };
|
|
676
|
+
}
|
|
677
|
+
if (ts_morph_1.Node.isVariableDeclaration(node)) {
|
|
678
|
+
return { kind: 'segment', text: node.getName() };
|
|
679
|
+
}
|
|
680
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(node)) {
|
|
681
|
+
return this.objectLiteralRootValid(node) ? { kind: 'transparent' } : { kind: 'exclude' };
|
|
682
|
+
}
|
|
683
|
+
return { kind: 'transparent' };
|
|
684
|
+
}
|
|
685
|
+
/** The dotted container path of `node`'s enclosing named scopes, outermost
|
|
686
|
+
* first, or null when any contributing ancestor is invalid (a computed /
|
|
687
|
+
* string-literal segment, a string-named module, `declare global`, or an
|
|
688
|
+
* unrooted class expression / object literal). */
|
|
689
|
+
containerSegments(node) {
|
|
690
|
+
const segments = [];
|
|
691
|
+
let current = node.getParent();
|
|
692
|
+
while (current) {
|
|
693
|
+
const step = this.walkStep(current);
|
|
694
|
+
if (step.kind === 'exclude')
|
|
695
|
+
return null;
|
|
696
|
+
if (step.kind === 'segment') {
|
|
697
|
+
if (!AstParserService.isValidSegment(step.text))
|
|
698
|
+
return null;
|
|
699
|
+
segments.unshift(step.text);
|
|
700
|
+
current = step.skipTo ? step.skipTo.getParent() : current.getParent();
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
current = current.getParent();
|
|
704
|
+
}
|
|
705
|
+
return segments;
|
|
706
|
+
}
|
|
707
|
+
/** If `node` is a declaration this service indexes (top-level symbol, member,
|
|
708
|
+
* or collected class), its own name + container. Otherwise null. Shared by
|
|
709
|
+
* member extraction, ownership ranges and the call graph so all three name a
|
|
710
|
+
* declaration identically. */
|
|
711
|
+
describeIndexedNode(node) {
|
|
712
|
+
if (ts_morph_1.Node.isClassDeclaration(node)) {
|
|
713
|
+
const name = node.getName() ?? (node.isDefaultExport() ? 'default' : null);
|
|
714
|
+
if (name === null)
|
|
715
|
+
return null;
|
|
716
|
+
const container = this.containerSegments(node);
|
|
717
|
+
return container === null ? null : { name, container };
|
|
718
|
+
}
|
|
719
|
+
if (ts_morph_1.Node.isClassExpression(node)) {
|
|
720
|
+
const root = this.classExpressionRoot(node);
|
|
721
|
+
if (!root)
|
|
722
|
+
return null;
|
|
723
|
+
const container = this.containerSegments(root.namingParent);
|
|
724
|
+
return container === null ? null : { name: root.name, container };
|
|
725
|
+
}
|
|
726
|
+
if (ts_morph_1.Node.isFunctionDeclaration(node)) {
|
|
727
|
+
const name = node.getName();
|
|
728
|
+
if (!name || !this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
729
|
+
return null;
|
|
730
|
+
const container = this.containerSegments(node);
|
|
731
|
+
return container === null ? null : { name, container };
|
|
732
|
+
}
|
|
733
|
+
if (ts_morph_1.Node.isMethodDeclaration(node) || ts_morph_1.Node.isGetAccessorDeclaration(node) || ts_morph_1.Node.isSetAccessorDeclaration(node)) {
|
|
734
|
+
const name = AstParserService.memberName(node.getNameNode());
|
|
735
|
+
if (name === null)
|
|
736
|
+
return null;
|
|
737
|
+
const container = this.containerSegments(node);
|
|
738
|
+
return container === null ? null : { name, container };
|
|
739
|
+
}
|
|
740
|
+
if (ts_morph_1.Node.isConstructorDeclaration(node)) {
|
|
741
|
+
const container = this.containerSegments(node);
|
|
742
|
+
return container === null ? null : { name: 'constructor', container };
|
|
743
|
+
}
|
|
744
|
+
if (ts_morph_1.Node.isPropertyDeclaration(node) || ts_morph_1.Node.isPropertyAssignment(node)) {
|
|
745
|
+
const init = node.getInitializer();
|
|
746
|
+
if (!init)
|
|
747
|
+
return null;
|
|
748
|
+
const unwrapped = AstParserService.unwrapExpr(init);
|
|
749
|
+
if (!(ts_morph_1.Node.isArrowFunction(unwrapped) || ts_morph_1.Node.isFunctionExpression(unwrapped)))
|
|
750
|
+
return null;
|
|
751
|
+
const name = AstParserService.memberName(node.getNameNode());
|
|
752
|
+
if (name === null)
|
|
753
|
+
return null;
|
|
754
|
+
const container = this.containerSegments(node);
|
|
755
|
+
return container === null ? null : { name, container };
|
|
756
|
+
}
|
|
757
|
+
if (ts_morph_1.Node.isInterfaceDeclaration(node)) {
|
|
758
|
+
if (!this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
759
|
+
return null;
|
|
760
|
+
const container = this.containerSegments(node);
|
|
761
|
+
return container === null ? null : { name: node.getName(), container };
|
|
762
|
+
}
|
|
763
|
+
if (ts_morph_1.Node.isTypeAliasDeclaration(node)) {
|
|
764
|
+
if (!this.isTopLevelOrNamespaceDirect(node.getParent()))
|
|
765
|
+
return null;
|
|
766
|
+
const container = this.containerSegments(node);
|
|
767
|
+
return container === null ? null : { name: node.getName(), container };
|
|
768
|
+
}
|
|
769
|
+
if (ts_morph_1.Node.isVariableDeclaration(node)) {
|
|
770
|
+
if (!this.isModuleOrNamespaceLevelVariable(node))
|
|
771
|
+
return null;
|
|
772
|
+
const container = this.containerSegments(node);
|
|
773
|
+
return container === null ? null : { name: node.getName(), container };
|
|
774
|
+
}
|
|
775
|
+
if (ts_morph_1.Node.isMethodSignature(node) || ts_morph_1.Node.isPropertySignature(node)) {
|
|
776
|
+
if (ts_morph_1.Node.isPropertySignature(node)) {
|
|
777
|
+
const typeNode = node.getTypeNode();
|
|
778
|
+
if (!typeNode || !ts_morph_1.Node.isFunctionTypeNode(typeNode))
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
const parent = node.getParent();
|
|
782
|
+
let validParent = false;
|
|
783
|
+
if (ts_morph_1.Node.isInterfaceDeclaration(parent)) {
|
|
784
|
+
validParent = true;
|
|
785
|
+
}
|
|
786
|
+
else if (ts_morph_1.Node.isTypeLiteral(parent)) {
|
|
787
|
+
const aliasParent = parent.getParent();
|
|
788
|
+
validParent = ts_morph_1.Node.isTypeAliasDeclaration(aliasParent) && aliasParent.getTypeNode() === parent;
|
|
789
|
+
}
|
|
790
|
+
if (!validParent)
|
|
791
|
+
return null;
|
|
792
|
+
const name = AstParserService.memberName(node.getNameNode());
|
|
793
|
+
if (name === null)
|
|
794
|
+
return null;
|
|
795
|
+
const container = this.containerSegments(node);
|
|
796
|
+
return container === null ? null : { name, container };
|
|
797
|
+
}
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
qualifiedName(described) {
|
|
801
|
+
return described.container.length ? `${described.container.join('.')}.${described.name}` : described.name;
|
|
802
|
+
}
|
|
803
|
+
/** The qualified name of the nearest ancestor of `node` that is itself an
|
|
804
|
+
* indexed declaration (method, accessor, constructor, function property,
|
|
805
|
+
* namespace/top-level function, or module-/namespace-level variable). */
|
|
806
|
+
findFromSymbol(node) {
|
|
807
|
+
let current = node.getParent();
|
|
808
|
+
while (current) {
|
|
809
|
+
const described = this.describeIndexedNode(current);
|
|
810
|
+
if (described)
|
|
811
|
+
return this.qualifiedName(described);
|
|
812
|
+
current = current.getParent();
|
|
813
|
+
}
|
|
814
|
+
return null;
|
|
400
815
|
}
|
|
401
816
|
// ── private: call graph extraction ────────────────────────────────────────
|
|
402
817
|
extractCalls(sourceFile, filePath, serviceName) {
|
|
@@ -405,7 +820,7 @@ class AstParserService {
|
|
|
405
820
|
const expr = callExpr.getExpression();
|
|
406
821
|
let calleeName = null;
|
|
407
822
|
let calleeFile = null;
|
|
408
|
-
let
|
|
823
|
+
let calleeDecl;
|
|
409
824
|
try {
|
|
410
825
|
if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
|
|
411
826
|
calleeName = expr.getName();
|
|
@@ -414,7 +829,7 @@ class AstParserService {
|
|
|
414
829
|
const declPath = decl?.getSourceFile().getFilePath();
|
|
415
830
|
if (declPath && !declPath.includes('/node_modules/')) {
|
|
416
831
|
calleeFile = declPath;
|
|
417
|
-
|
|
832
|
+
calleeDecl = decl;
|
|
418
833
|
}
|
|
419
834
|
}
|
|
420
835
|
else if (ts_morph_1.Node.isIdentifier(expr)) {
|
|
@@ -430,7 +845,7 @@ class AstParserService {
|
|
|
430
845
|
const declPath = decl?.getSourceFile().getFilePath();
|
|
431
846
|
if (declPath && !declPath.includes('/node_modules/')) {
|
|
432
847
|
calleeFile = declPath;
|
|
433
|
-
|
|
848
|
+
calleeDecl = decl;
|
|
434
849
|
}
|
|
435
850
|
}
|
|
436
851
|
}
|
|
@@ -440,7 +855,9 @@ class AstParserService {
|
|
|
440
855
|
}
|
|
441
856
|
if (!calleeName || !calleeFile)
|
|
442
857
|
continue;
|
|
443
|
-
const
|
|
858
|
+
const described = calleeDecl ? this.describeIndexedNode(calleeDecl) : null;
|
|
859
|
+
const calleeSymbol = described ? this.qualifiedName(described) : null;
|
|
860
|
+
const fromSymbol = this.findFromSymbol(callExpr);
|
|
444
861
|
const pos = sourceFile.compilerNode
|
|
445
862
|
.getLineAndCharacterOfPosition(callExpr.getStart());
|
|
446
863
|
calls.push({
|
|
@@ -456,22 +873,5 @@ class AstParserService {
|
|
|
456
873
|
}
|
|
457
874
|
return calls;
|
|
458
875
|
}
|
|
459
|
-
getContainerName(node) {
|
|
460
|
-
let current = node.getParent();
|
|
461
|
-
while (current) {
|
|
462
|
-
if (ts_morph_1.Node.isMethodDeclaration(current) || ts_morph_1.Node.isFunctionDeclaration(current)) {
|
|
463
|
-
return current.getName() ?? null;
|
|
464
|
-
}
|
|
465
|
-
if (ts_morph_1.Node.isArrowFunction(current) || ts_morph_1.Node.isFunctionExpression(current)) {
|
|
466
|
-
const parent = current.getParent();
|
|
467
|
-
if (ts_morph_1.Node.isVariableDeclaration(parent))
|
|
468
|
-
return parent.getName();
|
|
469
|
-
if (ts_morph_1.Node.isPropertyDeclaration(parent))
|
|
470
|
-
return parent.getName();
|
|
471
|
-
}
|
|
472
|
-
current = current.getParent();
|
|
473
|
-
}
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
876
|
}
|
|
477
877
|
exports.AstParserService = AstParserService;
|