ast-mcp-server 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +63 -14
- package/SECURITY.md +31 -0
- package/dist/cli.js +0 -0
- package/dist/index.js +129 -10
- package/dist/server.js +25 -2
- package/dist/services/agent-setup.js +36 -117
- package/dist/services/agent-targets.js +122 -0
- package/dist/services/context-builder.js +262 -0
- package/dist/services/diagnostics.js +10 -5
- package/dist/services/file-fingerprints.js +104 -0
- package/dist/services/file-snapshot.js +103 -0
- package/dist/services/impact.js +334 -0
- package/dist/services/operations.js +343 -254
- package/dist/services/outline.js +94 -21
- package/dist/services/pagination.js +10 -4
- package/dist/services/project-operation-scheduler.js +475 -0
- package/dist/services/project-status.js +134 -52
- package/dist/services/project-watcher.js +230 -0
- package/dist/services/project.js +646 -66
- package/dist/services/public-errors.js +255 -0
- package/dist/services/references.js +54 -0
- package/dist/services/relationships.js +1198 -0
- package/dist/services/request-context.js +42 -0
- package/dist/services/runtime-activity.js +65 -0
- package/dist/services/runtime-logger.js +50 -0
- package/dist/services/runtime-policy.js +47 -0
- package/dist/services/setup-wizard.js +4 -7
- package/dist/services/shutdown.js +93 -0
- package/dist/services/symbol-index-limits.js +5 -0
- package/dist/services/symbol-index-order.js +13 -0
- package/dist/services/symbol-index-policy.js +91 -0
- package/dist/services/symbol-index-sqlite.js +850 -0
- package/dist/services/symbol-index.js +447 -0
- package/dist/services/symbols.js +430 -47
- package/dist/services/test-candidates.js +209 -0
- package/dist/services/workspace.js +18 -7
- package/dist/tools/apply_operation.js +8 -4
- package/dist/tools/explore.js +176 -0
- package/dist/tools/find_references.js +17 -55
- package/dist/tools/get_diagnostics.js +10 -7
- package/dist/tools/get_file.js +107 -0
- package/dist/tools/get_impact.js +134 -0
- package/dist/tools/get_operation_preview.js +7 -4
- package/dist/tools/get_outline.js +8 -6
- package/dist/tools/get_project_status.js +77 -8
- package/dist/tools/get_symbol_source.js +8 -6
- package/dist/tools/list_files.js +11 -6
- package/dist/tools/rename_symbol.js +6 -4
- package/dist/tools/replace_symbol_body.js +6 -4
- package/dist/tools/result.js +27 -17
- package/dist/tools/scaffold_class.js +6 -4
- package/dist/tools/search_symbols.js +15 -63
- package/docs/support.md +80 -0
- package/package.json +18 -6
- package/skills/structural-code-editing/SKILL.md +42 -8
- package/benchmark/README.md +0 -129
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Node, SyntaxKind, } from "ts-morph";
|
|
3
|
+
import { createSourceLocation, isFreshnessCause, isSnapshotState, } from "./read-contracts.js";
|
|
4
|
+
import { collectSymbols, containingSymbol, findLocatedSymbol, forEachLocatedSymbol, } from "./symbols.js";
|
|
5
|
+
import { NO_REQUEST_CONTEXT } from "./request-context.js";
|
|
6
|
+
export const RELATIONSHIP_EDGE_KINDS = Object.freeze([
|
|
7
|
+
"reference",
|
|
8
|
+
"import",
|
|
9
|
+
"export",
|
|
10
|
+
"extends",
|
|
11
|
+
"implements",
|
|
12
|
+
"call",
|
|
13
|
+
"contains",
|
|
14
|
+
]);
|
|
15
|
+
export const RELATIONSHIP_PROVENANCES = Object.freeze(["compiler", "syntax", "heuristic"]);
|
|
16
|
+
export const RELATIONSHIP_CONFIDENCES = Object.freeze(["exact", "high", "medium", "low"]);
|
|
17
|
+
export const RELATIONSHIP_RESOLUTIONS = Object.freeze([
|
|
18
|
+
"resolved",
|
|
19
|
+
"unresolved",
|
|
20
|
+
"ambiguous",
|
|
21
|
+
]);
|
|
22
|
+
function assertNonEmptyString(value, name) {
|
|
23
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
24
|
+
throw new Error(`Relationship ${name} must be a non-empty string.`);
|
|
25
|
+
}
|
|
26
|
+
return value.trim();
|
|
27
|
+
}
|
|
28
|
+
function assertEnum(value, values, name) {
|
|
29
|
+
if (!values.includes(value)) {
|
|
30
|
+
throw new Error(`Relationship ${name} is invalid.`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function normalizeFreshness(input) {
|
|
35
|
+
if (typeof input !== "object" ||
|
|
36
|
+
input === null ||
|
|
37
|
+
!isSnapshotState(input.state) ||
|
|
38
|
+
!Array.isArray(input.causes) ||
|
|
39
|
+
(input.checked_at !== null && typeof input.checked_at !== "string")) {
|
|
40
|
+
throw new Error("Relationship freshness metadata is invalid.");
|
|
41
|
+
}
|
|
42
|
+
const causes = [...new Set(input.causes)];
|
|
43
|
+
if (!causes.every((cause) => isFreshnessCause(cause))) {
|
|
44
|
+
throw new Error("Relationship freshness contains an invalid cause.");
|
|
45
|
+
}
|
|
46
|
+
if (input.state === "fresh" && causes.length > 0) {
|
|
47
|
+
throw new Error("Fresh relationship evidence cannot carry freshness causes.");
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
state: input.state,
|
|
51
|
+
causes: Object.freeze(causes.sort()),
|
|
52
|
+
checked_at: input.checked_at,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function normalizeEndpoint(input) {
|
|
56
|
+
if (typeof input !== "object" || input === null) {
|
|
57
|
+
throw new Error("Relationship endpoint is invalid.");
|
|
58
|
+
}
|
|
59
|
+
const location = createSourceLocation(input.file, input.range ?? { start_line: 1 });
|
|
60
|
+
const symbolPath = assertNonEmptyString(input.symbol_path, "endpoint symbol_path");
|
|
61
|
+
const selector = assertNonEmptyString(input.selector, "endpoint selector");
|
|
62
|
+
const { file, ...range } = location;
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
file,
|
|
65
|
+
symbol_path: symbolPath,
|
|
66
|
+
selector,
|
|
67
|
+
...(input.range === undefined ? {} : { range }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function endpointIdentity(endpoint) {
|
|
71
|
+
return [endpoint.file, endpoint.symbol_path, endpoint.selector];
|
|
72
|
+
}
|
|
73
|
+
export function createRelationshipEdge(input) {
|
|
74
|
+
if (typeof input !== "object" || input === null) {
|
|
75
|
+
throw new Error("Relationship edge input is invalid.");
|
|
76
|
+
}
|
|
77
|
+
const source = normalizeEndpoint(input.source);
|
|
78
|
+
const target = normalizeEndpoint(input.target);
|
|
79
|
+
const kind = assertEnum(input.kind, RELATIONSHIP_EDGE_KINDS, "edge kind");
|
|
80
|
+
const provenance = assertEnum(input.provenance, RELATIONSHIP_PROVENANCES, "provenance");
|
|
81
|
+
const confidence = assertEnum(input.confidence, RELATIONSHIP_CONFIDENCES, "confidence");
|
|
82
|
+
const resolution = assertEnum(input.resolution, RELATIONSHIP_RESOLUTIONS, "resolution");
|
|
83
|
+
const freshness = normalizeFreshness(input.freshness);
|
|
84
|
+
const compilerAuthoritative = provenance === "compiler" &&
|
|
85
|
+
confidence === "exact" &&
|
|
86
|
+
resolution === "resolved" &&
|
|
87
|
+
freshness.state === "fresh";
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
relationship_id: `${kind}:${JSON.stringify(endpointIdentity(source))}->${JSON.stringify(endpointIdentity(target))}`,
|
|
90
|
+
source,
|
|
91
|
+
target,
|
|
92
|
+
kind,
|
|
93
|
+
provenance,
|
|
94
|
+
confidence,
|
|
95
|
+
resolution,
|
|
96
|
+
freshness,
|
|
97
|
+
compiler_authoritative: compilerAuthoritative,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function sourceFileKey(filePath) {
|
|
101
|
+
return path.normalize(filePath);
|
|
102
|
+
}
|
|
103
|
+
function isProjectScopedFile(projectRoot, filePath) {
|
|
104
|
+
const relative = path.relative(projectRoot, filePath);
|
|
105
|
+
const insideProject = relative === "" ||
|
|
106
|
+
(!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
107
|
+
return insideProject && !relative.split(path.sep).includes("node_modules");
|
|
108
|
+
}
|
|
109
|
+
function projectRelativeFile(projectRoot, filePath) {
|
|
110
|
+
return path.relative(projectRoot, filePath).split(path.sep).join("/");
|
|
111
|
+
}
|
|
112
|
+
function nodeRange(node) {
|
|
113
|
+
const sourceFile = node.getSourceFile();
|
|
114
|
+
const start = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
115
|
+
const end = sourceFile.getLineAndColumnAtPos(Math.max(node.getStart(), node.getEnd() - 1));
|
|
116
|
+
return {
|
|
117
|
+
start_line: start.line,
|
|
118
|
+
start_column: start.column,
|
|
119
|
+
end_line: end.line,
|
|
120
|
+
end_column: end.column,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function symbolEndpoint(symbol, projectRoot) {
|
|
124
|
+
return {
|
|
125
|
+
file: projectRelativeFile(projectRoot, symbol.node.getSourceFile().getFilePath()),
|
|
126
|
+
symbol_path: symbol.symbolPath,
|
|
127
|
+
selector: `${symbol.symbolPath}@${symbol.line}`,
|
|
128
|
+
range: nodeRange(symbol.node),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function moduleEndpoint(sourceFile, projectRoot, anchor) {
|
|
132
|
+
return {
|
|
133
|
+
file: projectRelativeFile(projectRoot, sourceFile.getFilePath()),
|
|
134
|
+
symbol_path: "<module>",
|
|
135
|
+
selector: "<module>@1",
|
|
136
|
+
...(anchor ? { range: nodeRange(anchor) } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function declarationsForSymbol(symbol) {
|
|
140
|
+
if (!symbol)
|
|
141
|
+
return [];
|
|
142
|
+
const resolved = symbol.getAliasedSymbol() ?? symbol;
|
|
143
|
+
return resolved.getDeclarations();
|
|
144
|
+
}
|
|
145
|
+
function declarationsForType(type) {
|
|
146
|
+
return declarationsForSymbol(type.getSymbol()).concat(declarationsForSymbol(type.getAliasSymbol()));
|
|
147
|
+
}
|
|
148
|
+
function symbolFromNameNode(node) {
|
|
149
|
+
if (!Node.isIdentifier(node) && !Node.isStringLiteral(node))
|
|
150
|
+
return undefined;
|
|
151
|
+
return node.getSymbol();
|
|
152
|
+
}
|
|
153
|
+
function addEdge(state, source, target, kind) {
|
|
154
|
+
const edge = createRelationshipEdge({
|
|
155
|
+
source,
|
|
156
|
+
target,
|
|
157
|
+
kind,
|
|
158
|
+
provenance: "compiler",
|
|
159
|
+
confidence: "exact",
|
|
160
|
+
resolution: "resolved",
|
|
161
|
+
freshness: state.freshness,
|
|
162
|
+
});
|
|
163
|
+
state.edges.set(edge.relationship_id, edge);
|
|
164
|
+
}
|
|
165
|
+
function projectLocatedDeclarations(state, declarations, requestContext) {
|
|
166
|
+
const unique = new Set();
|
|
167
|
+
for (const declaration of declarations) {
|
|
168
|
+
requestContext.checkpoint();
|
|
169
|
+
const sourceFile = declaration.getSourceFile();
|
|
170
|
+
if (!state.sourceFiles.has(sourceFileKey(sourceFile.getFilePath())))
|
|
171
|
+
continue;
|
|
172
|
+
const located = state.locatedSymbols.get(declaration);
|
|
173
|
+
if (located)
|
|
174
|
+
unique.add(located);
|
|
175
|
+
}
|
|
176
|
+
return [...unique];
|
|
177
|
+
}
|
|
178
|
+
function addReferenceEdges(state, requestContext) {
|
|
179
|
+
for (const target of new Set(state.locatedSymbols.values())) {
|
|
180
|
+
requestContext.checkpoint();
|
|
181
|
+
if (!Node.isReferenceFindable(target.node))
|
|
182
|
+
continue;
|
|
183
|
+
for (const reference of target.node.findReferencesAsNodes()) {
|
|
184
|
+
requestContext.checkpoint();
|
|
185
|
+
const sourceFile = reference.getSourceFile();
|
|
186
|
+
if (!state.sourceFiles.has(sourceFileKey(sourceFile.getFilePath())))
|
|
187
|
+
continue;
|
|
188
|
+
const source = containingSymbol(sourceFile, reference);
|
|
189
|
+
if (!source || source.node === target.node)
|
|
190
|
+
continue;
|
|
191
|
+
addEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "reference");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function addHeritageEdges(state, requestContext) {
|
|
196
|
+
for (const source of new Set(state.locatedSymbols.values())) {
|
|
197
|
+
requestContext.checkpoint();
|
|
198
|
+
if (Node.isClassDeclaration(source.node)) {
|
|
199
|
+
const base = source.node.getExtends();
|
|
200
|
+
if (base) {
|
|
201
|
+
for (const target of projectLocatedDeclarations(state, declarationsForType(base.getType()), requestContext)) {
|
|
202
|
+
addEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "extends");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const implemented of source.node.getImplements()) {
|
|
206
|
+
requestContext.checkpoint();
|
|
207
|
+
for (const target of projectLocatedDeclarations(state, declarationsForType(implemented.getType()), requestContext)) {
|
|
208
|
+
addEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "implements");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (Node.isInterfaceDeclaration(source.node)) {
|
|
213
|
+
for (const base of source.node.getBaseTypes()) {
|
|
214
|
+
requestContext.checkpoint();
|
|
215
|
+
for (const target of projectLocatedDeclarations(state, declarationsForType(base), requestContext)) {
|
|
216
|
+
addEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "extends");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function addModuleEdges(state, requestContext) {
|
|
223
|
+
for (const sourceFile of state.sourceFiles.values()) {
|
|
224
|
+
requestContext.checkpoint();
|
|
225
|
+
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
226
|
+
requestContext.checkpoint();
|
|
227
|
+
const targetFile = declaration.getModuleSpecifierSourceFile();
|
|
228
|
+
if (!targetFile || !state.sourceFiles.has(sourceFileKey(targetFile.getFilePath())))
|
|
229
|
+
continue;
|
|
230
|
+
const source = moduleEndpoint(sourceFile, state.projectRoot, declaration);
|
|
231
|
+
let emitted = false;
|
|
232
|
+
for (const specifier of declaration.getNamedImports()) {
|
|
233
|
+
const targets = projectLocatedDeclarations(state, declarationsForSymbol(symbolFromNameNode(specifier.getNameNode())), requestContext);
|
|
234
|
+
for (const target of targets) {
|
|
235
|
+
addEdge(state, source, symbolEndpoint(target, state.projectRoot), "import");
|
|
236
|
+
emitted = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const identifier of [declaration.getDefaultImport(), declaration.getNamespaceImport()]) {
|
|
240
|
+
if (!identifier)
|
|
241
|
+
continue;
|
|
242
|
+
for (const target of projectLocatedDeclarations(state, declarationsForSymbol(identifier.getSymbol()), requestContext)) {
|
|
243
|
+
addEdge(state, source, symbolEndpoint(target, state.projectRoot), "import");
|
|
244
|
+
emitted = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!emitted)
|
|
248
|
+
addEdge(state, source, moduleEndpoint(targetFile, state.projectRoot), "import");
|
|
249
|
+
}
|
|
250
|
+
for (const declaration of sourceFile.getExportDeclarations()) {
|
|
251
|
+
requestContext.checkpoint();
|
|
252
|
+
const targetFile = declaration.getModuleSpecifierSourceFile();
|
|
253
|
+
const source = moduleEndpoint(sourceFile, state.projectRoot, declaration);
|
|
254
|
+
let emitted = false;
|
|
255
|
+
for (const specifier of declaration.getNamedExports()) {
|
|
256
|
+
const declarations = [...specifier.getLocalTargetDeclarations()];
|
|
257
|
+
if (targetFile) {
|
|
258
|
+
declarations.push(...(targetFile.getExportedDeclarations().get(specifier.getName()) ?? []));
|
|
259
|
+
}
|
|
260
|
+
for (const target of projectLocatedDeclarations(state, declarations, requestContext)) {
|
|
261
|
+
addEdge(state, source, symbolEndpoint(target, state.projectRoot), "export");
|
|
262
|
+
emitted = true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!emitted && declaration.getNamedExports().length === 0 && targetFile) {
|
|
266
|
+
addEdge(state, source, moduleEndpoint(targetFile, state.projectRoot), "export");
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const DEFAULT_COMPILER_RELATIONSHIP_WORK_ITEMS = 100_000;
|
|
272
|
+
class ScopedRelationshipLimitReached extends Error {
|
|
273
|
+
constructor() {
|
|
274
|
+
super("Scoped compiler relationship discovery reached its edge limit.");
|
|
275
|
+
this.name = "ScopedRelationshipLimitReached";
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
class ScopedRelationshipWorkLimitReached extends Error {
|
|
279
|
+
constructor() {
|
|
280
|
+
super("Scoped compiler relationship discovery reached its work limit.");
|
|
281
|
+
this.name = "ScopedRelationshipWorkLimitReached";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function consumeRelationshipWork(state, budget) {
|
|
285
|
+
state.requestContext.checkpoint();
|
|
286
|
+
if (budget.remaining <= 0) {
|
|
287
|
+
budget.limitReached = true;
|
|
288
|
+
throw new ScopedRelationshipWorkLimitReached();
|
|
289
|
+
}
|
|
290
|
+
budget.remaining -= 1;
|
|
291
|
+
budget.consumed += 1;
|
|
292
|
+
}
|
|
293
|
+
function consumeScopedWork(state, collector) {
|
|
294
|
+
consumeRelationshipWork(state, collector.workBudget);
|
|
295
|
+
}
|
|
296
|
+
function reserveScopedWork(state, collector, count) {
|
|
297
|
+
state.requestContext.checkpoint();
|
|
298
|
+
const budget = collector.workBudget;
|
|
299
|
+
if (count > budget.remaining) {
|
|
300
|
+
budget.consumed += budget.remaining;
|
|
301
|
+
budget.remaining = 0;
|
|
302
|
+
budget.limitReached = true;
|
|
303
|
+
throw new ScopedRelationshipWorkLimitReached();
|
|
304
|
+
}
|
|
305
|
+
budget.remaining -= count;
|
|
306
|
+
budget.consumed += count;
|
|
307
|
+
}
|
|
308
|
+
function scopedDeclarationsForSymbol(state, collector, symbol) {
|
|
309
|
+
if (!symbol)
|
|
310
|
+
return [];
|
|
311
|
+
consumeScopedWork(state, collector);
|
|
312
|
+
const resolved = symbol.getAliasedSymbol() ?? symbol;
|
|
313
|
+
const declarationCount = resolved.compilerSymbol.declarations?.length ?? 0;
|
|
314
|
+
reserveScopedWork(state, collector, declarationCount);
|
|
315
|
+
const declarations = resolved.getDeclarations();
|
|
316
|
+
if (declarations.length !== declarationCount) {
|
|
317
|
+
throw new Error("Compiler symbol declarations changed during scoped relationship discovery.");
|
|
318
|
+
}
|
|
319
|
+
return declarations;
|
|
320
|
+
}
|
|
321
|
+
function scopedDeclarationsForType(state, collector, type) {
|
|
322
|
+
consumeScopedWork(state, collector);
|
|
323
|
+
const declarations = new Map();
|
|
324
|
+
for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) {
|
|
325
|
+
for (const declaration of scopedDeclarationsForSymbol(state, collector, symbol)) {
|
|
326
|
+
declarations.set(declaration, declaration);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return [...declarations.values()];
|
|
330
|
+
}
|
|
331
|
+
function forEachScopedBaseType(state, collector, node, visit) {
|
|
332
|
+
if (!Node.isClassDeclaration(node) && !Node.isInterfaceDeclaration(node))
|
|
333
|
+
return;
|
|
334
|
+
consumeScopedWork(state, collector);
|
|
335
|
+
const baseTypeCount = node.getType().compilerType.getBaseTypes()?.length ?? 0;
|
|
336
|
+
reserveScopedWork(state, collector, baseTypeCount);
|
|
337
|
+
const baseTypes = node.getBaseTypes();
|
|
338
|
+
if (baseTypes.length !== baseTypeCount) {
|
|
339
|
+
throw new Error("Compiler base types changed during scoped relationship discovery.");
|
|
340
|
+
}
|
|
341
|
+
for (const baseType of baseTypes)
|
|
342
|
+
visit(baseType);
|
|
343
|
+
}
|
|
344
|
+
function forEachScopedImplementedType(state, collector, node, visit) {
|
|
345
|
+
if (!Node.isClassDeclaration(node))
|
|
346
|
+
return;
|
|
347
|
+
consumeScopedWork(state, collector);
|
|
348
|
+
const implementationCount = node.compilerNode.heritageClauses?.find((clause) => clause.token === SyntaxKind.ImplementsKeyword)?.types.length ?? 0;
|
|
349
|
+
reserveScopedWork(state, collector, implementationCount);
|
|
350
|
+
const implementations = node.getImplements();
|
|
351
|
+
if (implementations.length !== implementationCount) {
|
|
352
|
+
throw new Error("Compiler implementations changed during scoped relationship discovery.");
|
|
353
|
+
}
|
|
354
|
+
for (const implementation of implementations) {
|
|
355
|
+
consumeScopedWork(state, collector);
|
|
356
|
+
visit(implementation.getType());
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function scopedSourceFiles(state, collector) {
|
|
360
|
+
const sourceFiles = [];
|
|
361
|
+
if (collector.allowedNeighborFilePaths) {
|
|
362
|
+
for (const filePath of collector.allowedNeighborFilePaths) {
|
|
363
|
+
consumeScopedWork(state, collector);
|
|
364
|
+
const sourceFile = state.project.getSourceFile(filePath);
|
|
365
|
+
if (sourceFile)
|
|
366
|
+
sourceFiles.push(sourceFile);
|
|
367
|
+
}
|
|
368
|
+
return sourceFiles;
|
|
369
|
+
}
|
|
370
|
+
const program = state.project.getProgram().compilerObject;
|
|
371
|
+
const filePaths = [];
|
|
372
|
+
for (const compilerSourceFile of program.getSourceFiles()) {
|
|
373
|
+
consumeScopedWork(state, collector);
|
|
374
|
+
const filePath = compilerSourceFile.fileName;
|
|
375
|
+
if (!isProjectScopedFile(state.projectRoot, filePath))
|
|
376
|
+
continue;
|
|
377
|
+
filePaths.push(filePath);
|
|
378
|
+
}
|
|
379
|
+
filePaths.sort((left, right) => left.localeCompare(right));
|
|
380
|
+
for (const filePath of filePaths) {
|
|
381
|
+
const sourceFile = state.project.getSourceFile(filePath);
|
|
382
|
+
if (sourceFile)
|
|
383
|
+
sourceFiles.push(sourceFile);
|
|
384
|
+
}
|
|
385
|
+
return sourceFiles;
|
|
386
|
+
}
|
|
387
|
+
function forEachScopedSymbol(state, collector, sourceFile, visit) {
|
|
388
|
+
forEachLocatedSymbol(sourceFile, (candidate) => {
|
|
389
|
+
consumeScopedWork(state, collector);
|
|
390
|
+
visit(candidate);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function scopedContainingSymbol(state, collector, sourceFile, node) {
|
|
394
|
+
let selected;
|
|
395
|
+
const nodeStart = node.getStart();
|
|
396
|
+
const nodeEnd = node.getEnd();
|
|
397
|
+
forEachLocatedSymbol(sourceFile, (symbol) => {
|
|
398
|
+
consumeScopedWork(state, collector);
|
|
399
|
+
if (symbol.node.getStart() > nodeStart)
|
|
400
|
+
return false;
|
|
401
|
+
if (symbol.node.getEnd() < nodeEnd)
|
|
402
|
+
return;
|
|
403
|
+
if (!selected) {
|
|
404
|
+
selected = symbol;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const order = (() => {
|
|
408
|
+
const left = symbol;
|
|
409
|
+
const right = selected;
|
|
410
|
+
const leftSize = left.node.getEnd() - left.node.getStart();
|
|
411
|
+
const rightSize = right.node.getEnd() - right.node.getStart();
|
|
412
|
+
return (leftSize - rightSize ||
|
|
413
|
+
left.line - right.line ||
|
|
414
|
+
left.symbolPath.localeCompare(right.symbolPath));
|
|
415
|
+
})();
|
|
416
|
+
if (order < 0)
|
|
417
|
+
selected = symbol;
|
|
418
|
+
});
|
|
419
|
+
return selected;
|
|
420
|
+
}
|
|
421
|
+
function scopedLocatedDeclarations(state, collector, declarations) {
|
|
422
|
+
const unique = new Map();
|
|
423
|
+
for (const declaration of declarations) {
|
|
424
|
+
state.requestContext.checkpoint();
|
|
425
|
+
consumeScopedWork(state, collector);
|
|
426
|
+
const sourceFile = declaration.getSourceFile();
|
|
427
|
+
if (!isProjectScopedFile(state.projectRoot, sourceFile.getFilePath()))
|
|
428
|
+
continue;
|
|
429
|
+
if (state.project.getSourceFile(sourceFile.getFilePath()) !== sourceFile)
|
|
430
|
+
continue;
|
|
431
|
+
forEachLocatedSymbol(sourceFile, (candidate) => {
|
|
432
|
+
consumeScopedWork(state, collector);
|
|
433
|
+
if (candidate.node !== declaration)
|
|
434
|
+
return;
|
|
435
|
+
unique.set(declaration, candidate);
|
|
436
|
+
return false;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return [...unique.values()];
|
|
440
|
+
}
|
|
441
|
+
function addScopedEdge(state, collector, source, target, kind) {
|
|
442
|
+
addScopedResolvedEdge(collector, createRelationshipEdge({
|
|
443
|
+
source,
|
|
444
|
+
target,
|
|
445
|
+
kind,
|
|
446
|
+
provenance: "compiler",
|
|
447
|
+
confidence: "exact",
|
|
448
|
+
resolution: "resolved",
|
|
449
|
+
freshness: state.freshness,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
function createScopedEdge(state, source, target, kind) {
|
|
453
|
+
return createRelationshipEdge({
|
|
454
|
+
source,
|
|
455
|
+
target,
|
|
456
|
+
kind,
|
|
457
|
+
provenance: "compiler",
|
|
458
|
+
confidence: "exact",
|
|
459
|
+
resolution: "resolved",
|
|
460
|
+
freshness: state.freshness,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function scopedIncidentNeighborKey(collector, edge) {
|
|
464
|
+
const sourceKey = endpointKey(edge.source);
|
|
465
|
+
const targetKey = endpointKey(edge.target);
|
|
466
|
+
if (collector.direction !== "incoming" && sourceKey === collector.endpointKey)
|
|
467
|
+
return targetKey;
|
|
468
|
+
if (collector.direction !== "outgoing" && targetKey === collector.endpointKey)
|
|
469
|
+
return sourceKey;
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
function addScopedResolvedEdge(collector, edge) {
|
|
473
|
+
if (!collector.relationshipKinds.has(edge.kind))
|
|
474
|
+
return;
|
|
475
|
+
if (collector.excludedRelationshipIds.has(edge.relationship_id))
|
|
476
|
+
return;
|
|
477
|
+
const neighborKey = scopedIncidentNeighborKey(collector, edge);
|
|
478
|
+
if (!neighborKey || collector.edges.has(edge.relationship_id))
|
|
479
|
+
return;
|
|
480
|
+
if (collector.allowedNeighborKeys && !collector.allowedNeighborKeys.has(neighborKey)) {
|
|
481
|
+
collector.excludedNeighbors = true;
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (collector.edges.size >= collector.maxEdges)
|
|
485
|
+
throw new ScopedRelationshipLimitReached();
|
|
486
|
+
collector.edges.set(edge.relationship_id, edge);
|
|
487
|
+
if (collector.stopAfterFirst)
|
|
488
|
+
throw new ScopedRelationshipLimitReached();
|
|
489
|
+
}
|
|
490
|
+
function scopedEdgeOrder(left, right) {
|
|
491
|
+
return (left.kind.localeCompare(right.kind) ||
|
|
492
|
+
left.source.file.localeCompare(right.source.file) ||
|
|
493
|
+
left.source.selector.localeCompare(right.source.selector) ||
|
|
494
|
+
left.target.file.localeCompare(right.target.file) ||
|
|
495
|
+
left.target.selector.localeCompare(right.target.selector) ||
|
|
496
|
+
left.relationship_id.localeCompare(right.relationship_id));
|
|
497
|
+
}
|
|
498
|
+
function scopedNeighborOrder(rootKey, left, right) {
|
|
499
|
+
const leftEndpoint = endpointKey(left.source) === rootKey ? left.target : left.source;
|
|
500
|
+
const rightEndpoint = endpointKey(right.source) === rootKey ? right.target : right.source;
|
|
501
|
+
return (leftEndpoint.file.localeCompare(rightEndpoint.file) ||
|
|
502
|
+
leftEndpoint.selector.localeCompare(rightEndpoint.selector) ||
|
|
503
|
+
leftEndpoint.symbol_path.localeCompare(rightEndpoint.symbol_path) ||
|
|
504
|
+
scopedEdgeOrder(left, right));
|
|
505
|
+
}
|
|
506
|
+
function createScopedCandidateSet(collector) {
|
|
507
|
+
const candidates = {
|
|
508
|
+
collector,
|
|
509
|
+
indexes: new Map(),
|
|
510
|
+
values: [],
|
|
511
|
+
capacity: collector.stopAfterFirst ? 1 : collector.maxEdges + 1,
|
|
512
|
+
push(candidate) {
|
|
513
|
+
retainScopedCandidate(candidates, candidate);
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
return candidates;
|
|
517
|
+
}
|
|
518
|
+
function swapScopedCandidates(candidates, left, right) {
|
|
519
|
+
[candidates.values[left], candidates.values[right]] = [
|
|
520
|
+
candidates.values[right],
|
|
521
|
+
candidates.values[left],
|
|
522
|
+
];
|
|
523
|
+
candidates.indexes.set(candidates.values[left].relationship_id, left);
|
|
524
|
+
candidates.indexes.set(candidates.values[right].relationship_id, right);
|
|
525
|
+
}
|
|
526
|
+
function retainScopedCandidate(candidates, candidate) {
|
|
527
|
+
if (!candidates.collector.relationshipKinds.has(candidate.kind))
|
|
528
|
+
return;
|
|
529
|
+
if (candidates.collector.excludedRelationshipIds.has(candidate.relationship_id))
|
|
530
|
+
return;
|
|
531
|
+
const neighborKey = scopedIncidentNeighborKey(candidates.collector, candidate);
|
|
532
|
+
if (!neighborKey)
|
|
533
|
+
return;
|
|
534
|
+
if (candidates.collector.allowedNeighborKeys &&
|
|
535
|
+
!candidates.collector.allowedNeighborKeys.has(neighborKey)) {
|
|
536
|
+
candidates.collector.excludedNeighbors = true;
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (candidates.indexes.has(candidate.relationship_id))
|
|
540
|
+
return;
|
|
541
|
+
const order = (left, right) => scopedNeighborOrder(candidates.collector.endpointKey, left, right);
|
|
542
|
+
if (candidates.values.length < candidates.capacity) {
|
|
543
|
+
candidates.values.push(candidate);
|
|
544
|
+
let index = candidates.values.length - 1;
|
|
545
|
+
candidates.indexes.set(candidate.relationship_id, index);
|
|
546
|
+
while (index > 0) {
|
|
547
|
+
const parent = Math.floor((index - 1) / 2);
|
|
548
|
+
if (order(candidates.values[parent], candidates.values[index]) >= 0)
|
|
549
|
+
break;
|
|
550
|
+
swapScopedCandidates(candidates, parent, index);
|
|
551
|
+
index = parent;
|
|
552
|
+
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (order(candidate, candidates.values[0]) >= 0)
|
|
556
|
+
return;
|
|
557
|
+
candidates.indexes.delete(candidates.values[0].relationship_id);
|
|
558
|
+
candidates.values[0] = candidate;
|
|
559
|
+
candidates.indexes.set(candidate.relationship_id, 0);
|
|
560
|
+
let index = 0;
|
|
561
|
+
while (true) {
|
|
562
|
+
const left = index * 2 + 1;
|
|
563
|
+
const right = left + 1;
|
|
564
|
+
if (left >= candidates.values.length)
|
|
565
|
+
return;
|
|
566
|
+
let worst = left;
|
|
567
|
+
if (right < candidates.values.length &&
|
|
568
|
+
order(candidates.values[right], candidates.values[left]) > 0) {
|
|
569
|
+
worst = right;
|
|
570
|
+
}
|
|
571
|
+
if (order(candidates.values[index], candidates.values[worst]) >= 0)
|
|
572
|
+
return;
|
|
573
|
+
swapScopedCandidates(candidates, index, worst);
|
|
574
|
+
index = worst;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function flushScopedCandidates(candidates) {
|
|
578
|
+
candidates.values.sort((left, right) => scopedNeighborOrder(candidates.collector.endpointKey, left, right));
|
|
579
|
+
for (const candidate of candidates.values) {
|
|
580
|
+
addScopedResolvedEdge(candidates.collector, candidate);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function addScopedCandidates(collector, candidates) {
|
|
584
|
+
if (candidates.collector !== collector) {
|
|
585
|
+
throw new Error("Scoped relationship candidates belong to a different collector.");
|
|
586
|
+
}
|
|
587
|
+
flushScopedCandidates(candidates);
|
|
588
|
+
}
|
|
589
|
+
function collectScopedHeritageEdges(state, edges, source, candidates) {
|
|
590
|
+
consumeScopedWork(state, edges);
|
|
591
|
+
const sourceEndpoint = symbolEndpoint(source, state.projectRoot);
|
|
592
|
+
if (Node.isClassDeclaration(source.node)) {
|
|
593
|
+
const base = source.node.getExtends();
|
|
594
|
+
if (base && edges.relationshipKinds.has("extends")) {
|
|
595
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForType(state, edges, base.getType()))) {
|
|
596
|
+
candidates.push(createScopedEdge(state, sourceEndpoint, symbolEndpoint(target, state.projectRoot), "extends"));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (edges.relationshipKinds.has("implements")) {
|
|
600
|
+
forEachScopedImplementedType(state, edges, source.node, (implementedType) => {
|
|
601
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForType(state, edges, implementedType))) {
|
|
602
|
+
candidates.push(createScopedEdge(state, sourceEndpoint, symbolEndpoint(target, state.projectRoot), "implements"));
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (Node.isInterfaceDeclaration(source.node) && edges.relationshipKinds.has("extends")) {
|
|
608
|
+
forEachScopedBaseType(state, edges, source.node, (base) => {
|
|
609
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForType(state, edges, base))) {
|
|
610
|
+
candidates.push(createScopedEdge(state, sourceEndpoint, symbolEndpoint(target, state.projectRoot), "extends"));
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function addScopedHeritageEdges(state, edges, source) {
|
|
616
|
+
const candidates = createScopedCandidateSet(edges);
|
|
617
|
+
collectScopedHeritageEdges(state, edges, source, candidates);
|
|
618
|
+
addScopedCandidates(edges, candidates);
|
|
619
|
+
}
|
|
620
|
+
function forEachNamedImportSpecifier(declaration, visit) {
|
|
621
|
+
const namedBindings = declaration.getImportClause()?.getNamedBindings();
|
|
622
|
+
if (!Node.isNamedImports(namedBindings))
|
|
623
|
+
return;
|
|
624
|
+
namedBindings.forEachChild((specifier) => {
|
|
625
|
+
if (Node.isImportSpecifier(specifier))
|
|
626
|
+
visit(specifier);
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function forEachNamedExportSpecifier(declaration, visit) {
|
|
630
|
+
declaration.forEachChild((namedExports) => {
|
|
631
|
+
if (!Node.isNamedExports(namedExports))
|
|
632
|
+
return;
|
|
633
|
+
namedExports.forEachChild((specifier) => {
|
|
634
|
+
if (Node.isExportSpecifier(specifier))
|
|
635
|
+
visit(specifier);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function addScopedModuleEdges(state, edges, sourceFile) {
|
|
640
|
+
const candidates = createScopedCandidateSet(edges);
|
|
641
|
+
sourceFile.forEachChild((declaration) => {
|
|
642
|
+
consumeScopedWork(state, edges);
|
|
643
|
+
if (Node.isImportDeclaration(declaration) && edges.relationshipKinds.has("import")) {
|
|
644
|
+
const targetFile = declaration.getModuleSpecifierSourceFile();
|
|
645
|
+
if (!targetFile ||
|
|
646
|
+
!isProjectScopedFile(state.projectRoot, targetFile.getFilePath()) ||
|
|
647
|
+
state.project.getSourceFile(targetFile.getFilePath()) !== targetFile)
|
|
648
|
+
return;
|
|
649
|
+
const source = moduleEndpoint(sourceFile, state.projectRoot, declaration);
|
|
650
|
+
let emitted = false;
|
|
651
|
+
forEachNamedImportSpecifier(declaration, (specifier) => {
|
|
652
|
+
consumeScopedWork(state, edges);
|
|
653
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForSymbol(state, edges, symbolFromNameNode(specifier.getNameNode())))) {
|
|
654
|
+
candidates.push(createScopedEdge(state, source, symbolEndpoint(target, state.projectRoot), "import"));
|
|
655
|
+
emitted = true;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
for (const identifier of [declaration.getDefaultImport(), declaration.getNamespaceImport()]) {
|
|
659
|
+
if (!identifier)
|
|
660
|
+
continue;
|
|
661
|
+
consumeScopedWork(state, edges);
|
|
662
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForSymbol(state, edges, identifier.getSymbol()))) {
|
|
663
|
+
candidates.push(createScopedEdge(state, source, symbolEndpoint(target, state.projectRoot), "import"));
|
|
664
|
+
emitted = true;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (!emitted) {
|
|
668
|
+
candidates.push(createScopedEdge(state, source, moduleEndpoint(targetFile, state.projectRoot), "import"));
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (Node.isExportDeclaration(declaration) && edges.relationshipKinds.has("export")) {
|
|
673
|
+
const targetFile = declaration.getModuleSpecifierSourceFile();
|
|
674
|
+
if (targetFile && !isProjectScopedFile(state.projectRoot, targetFile.getFilePath()))
|
|
675
|
+
return;
|
|
676
|
+
const source = moduleEndpoint(sourceFile, state.projectRoot, declaration);
|
|
677
|
+
let emitted = false;
|
|
678
|
+
let hasNamedExports = false;
|
|
679
|
+
forEachNamedExportSpecifier(declaration, (specifier) => {
|
|
680
|
+
hasNamedExports = true;
|
|
681
|
+
consumeScopedWork(state, edges);
|
|
682
|
+
for (const target of scopedLocatedDeclarations(state, edges, scopedDeclarationsForSymbol(state, edges, specifier.getLocalTargetSymbol()))) {
|
|
683
|
+
candidates.push(createScopedEdge(state, source, symbolEndpoint(target, state.projectRoot), "export"));
|
|
684
|
+
emitted = true;
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
if (!emitted && !hasNamedExports && targetFile) {
|
|
688
|
+
candidates.push(createScopedEdge(state, source, moduleEndpoint(targetFile, state.projectRoot), "export"));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
addScopedCandidates(edges, candidates);
|
|
693
|
+
}
|
|
694
|
+
function addScopedOutgoingReferences(state, edges, source) {
|
|
695
|
+
const candidates = createScopedCandidateSet(edges);
|
|
696
|
+
const sourceFile = source.node.getSourceFile();
|
|
697
|
+
source.node.forEachDescendant((reference) => {
|
|
698
|
+
consumeScopedWork(state, edges);
|
|
699
|
+
if (!isCompilerReferenceNode(reference))
|
|
700
|
+
return;
|
|
701
|
+
if (scopedContainingSymbol(state, edges, sourceFile, reference)?.node !== source.node)
|
|
702
|
+
return;
|
|
703
|
+
const declarations = scopedDeclarationsForSymbol(state, edges, compilerReferenceSymbol(reference));
|
|
704
|
+
const declarationOwner = referenceDeclarationNameOwner(reference);
|
|
705
|
+
const referencedDeclarations = declarationOwner
|
|
706
|
+
? compilerDeclarationNameTargets(declarationOwner, declarations)
|
|
707
|
+
: declarations;
|
|
708
|
+
for (const target of scopedLocatedDeclarations(state, edges, referencedDeclarations)) {
|
|
709
|
+
if (target.node === source.node)
|
|
710
|
+
continue;
|
|
711
|
+
candidates.push(createScopedEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "reference"));
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
addScopedCandidates(edges, candidates);
|
|
715
|
+
}
|
|
716
|
+
function isCompilerReferenceNode(node) {
|
|
717
|
+
return (Node.isReferenceFindable(node) ||
|
|
718
|
+
Node.isStringLiteral(node) ||
|
|
719
|
+
(Node.isThisExpression(node) && isStaticThisReference(node)));
|
|
720
|
+
}
|
|
721
|
+
function isStaticThisReference(node) {
|
|
722
|
+
for (let current = node.getParent(); current; current = current.getParent()) {
|
|
723
|
+
if (Node.isArrowFunction(current) ||
|
|
724
|
+
Node.isFunctionExpression(current) ||
|
|
725
|
+
Node.isFunctionDeclaration(current)) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
if (current.getKind() === SyntaxKind.ClassStaticBlockDeclaration)
|
|
729
|
+
return true;
|
|
730
|
+
if (Node.isMethodDeclaration(current) ||
|
|
731
|
+
Node.isGetAccessorDeclaration(current) ||
|
|
732
|
+
Node.isSetAccessorDeclaration(current)) {
|
|
733
|
+
const modifiers = current.compilerNode
|
|
734
|
+
.modifiers;
|
|
735
|
+
return modifiers?.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword) ?? false;
|
|
736
|
+
}
|
|
737
|
+
if (Node.isPropertyDeclaration(current))
|
|
738
|
+
return false;
|
|
739
|
+
if (Node.isClassDeclaration(current) || Node.isClassExpression(current))
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
function compilerReferenceSymbol(node) {
|
|
745
|
+
if (Node.isShorthandPropertyAssignment(node))
|
|
746
|
+
return node.getValueSymbol();
|
|
747
|
+
const parent = node.getParent();
|
|
748
|
+
if (parent && Node.isShorthandPropertyAssignment(parent))
|
|
749
|
+
return parent.getValueSymbol();
|
|
750
|
+
return node.getSymbol();
|
|
751
|
+
}
|
|
752
|
+
function isReferenceDeclaration(node) {
|
|
753
|
+
return (Node.isFunctionDeclaration(node) ||
|
|
754
|
+
Node.isMethodDeclaration(node) ||
|
|
755
|
+
Node.isMethodSignature(node) ||
|
|
756
|
+
Node.isPropertyDeclaration(node) ||
|
|
757
|
+
Node.isPropertySignature(node) ||
|
|
758
|
+
Node.isGetAccessorDeclaration(node) ||
|
|
759
|
+
Node.isSetAccessorDeclaration(node));
|
|
760
|
+
}
|
|
761
|
+
function referenceDeclarationNameOwner(reference) {
|
|
762
|
+
if (isReferenceDeclaration(reference))
|
|
763
|
+
return reference;
|
|
764
|
+
const parent = reference.getParent();
|
|
765
|
+
if (!parent || !isReferenceDeclaration(parent))
|
|
766
|
+
return undefined;
|
|
767
|
+
const nameNode = parent.compilerNode
|
|
768
|
+
.name;
|
|
769
|
+
const referenceNode = reference.compilerNode;
|
|
770
|
+
return nameNode &&
|
|
771
|
+
nameNode.kind === referenceNode.kind &&
|
|
772
|
+
nameNode.pos === referenceNode.pos &&
|
|
773
|
+
nameNode.end === referenceNode.end
|
|
774
|
+
? parent
|
|
775
|
+
: undefined;
|
|
776
|
+
}
|
|
777
|
+
function sameCompilerNode(left, right) {
|
|
778
|
+
return (left.getSourceFile().getFilePath() === right.getSourceFile().getFilePath() &&
|
|
779
|
+
left.getKind() === right.getKind() &&
|
|
780
|
+
left.getStart() === right.getStart() &&
|
|
781
|
+
left.getEnd() === right.getEnd());
|
|
782
|
+
}
|
|
783
|
+
function compilerDeclarationNameTargets(source, declarations) {
|
|
784
|
+
const sourceIndex = declarations.findIndex((declaration) => sameCompilerNode(declaration, source));
|
|
785
|
+
if (sourceIndex <= 0)
|
|
786
|
+
return [];
|
|
787
|
+
return declarations.filter((declaration) => !sameCompilerNode(declaration, source));
|
|
788
|
+
}
|
|
789
|
+
function scopedMemberName(node) {
|
|
790
|
+
if (Node.isMethodDeclaration(node) ||
|
|
791
|
+
Node.isMethodSignature(node) ||
|
|
792
|
+
Node.isPropertyDeclaration(node) ||
|
|
793
|
+
Node.isPropertySignature(node) ||
|
|
794
|
+
Node.isGetAccessorDeclaration(node) ||
|
|
795
|
+
Node.isSetAccessorDeclaration(node)) {
|
|
796
|
+
const nameNode = node.getNameNode();
|
|
797
|
+
if (nameNode.getKind() === SyntaxKind.ComputedPropertyName ||
|
|
798
|
+
nameNode.getKind() === SyntaxKind.PrivateIdentifier ||
|
|
799
|
+
(Node.isModifierable(node) && node.hasModifier(SyntaxKind.PrivateKeyword)) ||
|
|
800
|
+
(Node.isStaticable(node) && node.isStatic())) {
|
|
801
|
+
return undefined;
|
|
802
|
+
}
|
|
803
|
+
return node.getName();
|
|
804
|
+
}
|
|
805
|
+
return undefined;
|
|
806
|
+
}
|
|
807
|
+
function scopedMemberContainer(node) {
|
|
808
|
+
return node.getFirstAncestor((ancestor) => Node.isClassDeclaration(ancestor) || Node.isInterfaceDeclaration(ancestor));
|
|
809
|
+
}
|
|
810
|
+
function scopedBaseContainers(state, collector, container) {
|
|
811
|
+
const discovered = new Set();
|
|
812
|
+
const pending = [container];
|
|
813
|
+
while (pending.length > 0) {
|
|
814
|
+
consumeScopedWork(state, collector);
|
|
815
|
+
const current = pending.pop();
|
|
816
|
+
const discoverType = (type) => {
|
|
817
|
+
for (const declaration of scopedDeclarationsForType(state, collector, type)) {
|
|
818
|
+
if ((Node.isClassDeclaration(declaration) || Node.isInterfaceDeclaration(declaration)) &&
|
|
819
|
+
!discovered.has(declaration)) {
|
|
820
|
+
discovered.add(declaration);
|
|
821
|
+
pending.push(declaration);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
if (Node.isClassDeclaration(current) || Node.isInterfaceDeclaration(current)) {
|
|
826
|
+
forEachScopedBaseType(state, collector, current, discoverType);
|
|
827
|
+
forEachScopedImplementedType(state, collector, current, discoverType);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return discovered;
|
|
831
|
+
}
|
|
832
|
+
function addScopedMemberRelationships(state, edges, target) {
|
|
833
|
+
const targetName = scopedMemberName(target.node);
|
|
834
|
+
const targetContainer = scopedMemberContainer(target.node);
|
|
835
|
+
if (!targetName || !targetContainer)
|
|
836
|
+
return;
|
|
837
|
+
const targetBases = scopedBaseContainers(state, edges, targetContainer);
|
|
838
|
+
const candidates = createScopedCandidateSet(edges);
|
|
839
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
840
|
+
consumeScopedWork(state, edges);
|
|
841
|
+
forEachScopedSymbol(state, edges, sourceFile, (candidate) => {
|
|
842
|
+
if (candidate.node === target.node || scopedMemberName(candidate.node) !== targetName) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const candidateContainer = scopedMemberContainer(candidate.node);
|
|
846
|
+
if (!candidateContainer)
|
|
847
|
+
return;
|
|
848
|
+
const related = targetBases.has(candidateContainer) ||
|
|
849
|
+
scopedBaseContainers(state, edges, candidateContainer).has(targetContainer);
|
|
850
|
+
if (!related)
|
|
851
|
+
return;
|
|
852
|
+
const candidateEndpoint = symbolEndpoint(candidate, state.projectRoot);
|
|
853
|
+
const targetEndpoint = symbolEndpoint(target, state.projectRoot);
|
|
854
|
+
const pairs = [
|
|
855
|
+
[candidateEndpoint, targetEndpoint],
|
|
856
|
+
[targetEndpoint, candidateEndpoint],
|
|
857
|
+
];
|
|
858
|
+
pairs.sort((left, right) => [left[0].file, left[0].selector, left[1].file, left[1].selector]
|
|
859
|
+
.join("\u0000")
|
|
860
|
+
.localeCompare([right[0].file, right[0].selector, right[1].file, right[1].selector].join("\u0000")));
|
|
861
|
+
for (const [sourceEndpoint, targetEndpoint] of pairs) {
|
|
862
|
+
candidates.push(createScopedEdge(state, sourceEndpoint, targetEndpoint, "reference"));
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
addScopedCandidates(edges, candidates);
|
|
867
|
+
}
|
|
868
|
+
function addScopedIncomingHeritageRelationships(state, edges) {
|
|
869
|
+
const candidates = createScopedCandidateSet(edges);
|
|
870
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
871
|
+
consumeScopedWork(state, edges);
|
|
872
|
+
forEachScopedSymbol(state, edges, sourceFile, (candidate) => {
|
|
873
|
+
collectScopedHeritageEdges(state, edges, candidate, candidates);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
addScopedCandidates(edges, candidates);
|
|
877
|
+
}
|
|
878
|
+
function addScopedIncomingReferences(state, edges, target) {
|
|
879
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
880
|
+
consumeScopedWork(state, edges);
|
|
881
|
+
const candidates = createScopedCandidateSet(edges);
|
|
882
|
+
sourceFile.forEachDescendant((reference) => {
|
|
883
|
+
consumeScopedWork(state, edges);
|
|
884
|
+
if (!isCompilerReferenceNode(reference))
|
|
885
|
+
return;
|
|
886
|
+
const declarations = scopedDeclarationsForSymbol(state, edges, compilerReferenceSymbol(reference));
|
|
887
|
+
const declarationOwner = referenceDeclarationNameOwner(reference);
|
|
888
|
+
if (declarationOwner &&
|
|
889
|
+
!compilerDeclarationNameTargets(declarationOwner, declarations).some((declaration) => sameCompilerNode(declaration, target.node))) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (!declarationOwner &&
|
|
893
|
+
!declarations.some((declaration) => sameCompilerNode(declaration, target.node))) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const source = scopedContainingSymbol(state, edges, sourceFile, reference);
|
|
897
|
+
if (source && source.node !== target.node) {
|
|
898
|
+
candidates.push(createScopedEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "reference"));
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
addScopedCandidates(edges, candidates);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function addScopedIncomingImports(state, edges, target) {
|
|
905
|
+
const targetEndpoint = symbolEndpoint(target, state.projectRoot);
|
|
906
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
907
|
+
consumeScopedWork(state, edges);
|
|
908
|
+
sourceFile.forEachChild((declaration) => {
|
|
909
|
+
consumeScopedWork(state, edges);
|
|
910
|
+
if (!Node.isImportDeclaration(declaration))
|
|
911
|
+
return;
|
|
912
|
+
let resolvesTarget = false;
|
|
913
|
+
forEachNamedImportSpecifier(declaration, (specifier) => {
|
|
914
|
+
consumeScopedWork(state, edges);
|
|
915
|
+
resolvesTarget ||= scopedDeclarationsForSymbol(state, edges, specifier.getNameNode().getSymbol()).some((candidate) => candidate === target.node);
|
|
916
|
+
});
|
|
917
|
+
for (const identifier of [declaration.getDefaultImport(), declaration.getNamespaceImport()]) {
|
|
918
|
+
if (!identifier)
|
|
919
|
+
continue;
|
|
920
|
+
consumeScopedWork(state, edges);
|
|
921
|
+
resolvesTarget ||= scopedDeclarationsForSymbol(state, edges, identifier.getSymbol()).some((candidate) => candidate === target.node);
|
|
922
|
+
}
|
|
923
|
+
if (resolvesTarget) {
|
|
924
|
+
addScopedEdge(state, edges, moduleEndpoint(sourceFile, state.projectRoot, declaration), targetEndpoint, "import");
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function addScopedIncomingExports(state, edges, target) {
|
|
930
|
+
const targetEndpoint = symbolEndpoint(target, state.projectRoot);
|
|
931
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
932
|
+
consumeScopedWork(state, edges);
|
|
933
|
+
sourceFile.forEachChild((declaration) => {
|
|
934
|
+
consumeScopedWork(state, edges);
|
|
935
|
+
if (!Node.isExportDeclaration(declaration))
|
|
936
|
+
return;
|
|
937
|
+
let resolvesTarget = false;
|
|
938
|
+
forEachNamedExportSpecifier(declaration, (specifier) => {
|
|
939
|
+
consumeScopedWork(state, edges);
|
|
940
|
+
resolvesTarget ||= scopedDeclarationsForSymbol(state, edges, specifier.getLocalTargetSymbol()).some((candidate) => candidate === target.node);
|
|
941
|
+
});
|
|
942
|
+
if (resolvesTarget) {
|
|
943
|
+
addScopedEdge(state, edges, moduleEndpoint(sourceFile, state.projectRoot, declaration), targetEndpoint, "export");
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function endpointKey(endpoint) {
|
|
949
|
+
return `${endpoint.file}\u0000${endpoint.symbol_path}\u0000${endpoint.selector}`;
|
|
950
|
+
}
|
|
951
|
+
function scopedLocatedEndpoint(state, collector, endpoint) {
|
|
952
|
+
consumeScopedWork(state, collector);
|
|
953
|
+
const sourceFile = state.project.getSourceFile(path.resolve(state.projectRoot, endpoint.file));
|
|
954
|
+
if (!sourceFile)
|
|
955
|
+
throw new Error("Impact relationship endpoint is outside the compiler project.");
|
|
956
|
+
if (endpoint.symbol_path === "<module>")
|
|
957
|
+
return { sourceFile };
|
|
958
|
+
const symbol = findLocatedSymbol(sourceFile, endpoint.selector, () => consumeScopedWork(state, collector));
|
|
959
|
+
if (!symbol || endpointKey(symbolEndpoint(symbol, state.projectRoot)) !== endpointKey(endpoint)) {
|
|
960
|
+
throw new Error("Impact relationship endpoint no longer resolves to the same declaration.");
|
|
961
|
+
}
|
|
962
|
+
return { sourceFile, symbol };
|
|
963
|
+
}
|
|
964
|
+
function addScopedIncomingModuleEdges(state, edges) {
|
|
965
|
+
for (const sourceFile of scopedSourceFiles(state, edges)) {
|
|
966
|
+
addScopedModuleEdges(state, edges, sourceFile);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
export function createCompilerRelationshipResolver(project, projectRoot, freshness, requestContext = NO_REQUEST_CONTEXT) {
|
|
970
|
+
requestContext.checkpoint();
|
|
971
|
+
const state = {
|
|
972
|
+
project,
|
|
973
|
+
projectRoot,
|
|
974
|
+
freshness: normalizeFreshness(freshness),
|
|
975
|
+
requestContext,
|
|
976
|
+
};
|
|
977
|
+
return {
|
|
978
|
+
edgesFor(endpoint, query) {
|
|
979
|
+
requestContext.checkpoint();
|
|
980
|
+
const key = endpointKey(endpoint);
|
|
981
|
+
if (!Number.isSafeInteger(query.max_edges) || query.max_edges < 1) {
|
|
982
|
+
throw new Error("Compiler relationship max_edges must be a positive safe integer.");
|
|
983
|
+
}
|
|
984
|
+
const maxWorkItems = query.max_work_items ?? DEFAULT_COMPILER_RELATIONSHIP_WORK_ITEMS;
|
|
985
|
+
if (!Number.isSafeInteger(maxWorkItems) || maxWorkItems < 1) {
|
|
986
|
+
throw new Error("Compiler relationship max_work_items must be a positive safe integer.");
|
|
987
|
+
}
|
|
988
|
+
const direction = assertEnum(query.direction, ["incoming", "outgoing", "both"], "direction");
|
|
989
|
+
const relationshipKinds = new Set(query.relationship_kinds.map((kind) => assertEnum(kind, RELATIONSHIP_EDGE_KINDS, "edge kind")));
|
|
990
|
+
const workBudget = {
|
|
991
|
+
remaining: maxWorkItems,
|
|
992
|
+
consumed: 0,
|
|
993
|
+
limitReached: false,
|
|
994
|
+
};
|
|
995
|
+
const allowedNeighborKeys = query.allowed_neighbor_keys
|
|
996
|
+
? new Set(query.allowed_neighbor_keys)
|
|
997
|
+
: undefined;
|
|
998
|
+
const allowedNeighborFilePaths = allowedNeighborKeys ? new Set() : undefined;
|
|
999
|
+
try {
|
|
1000
|
+
for (const neighborKey of query.allowed_neighbor_keys ?? []) {
|
|
1001
|
+
consumeRelationshipWork(state, workBudget);
|
|
1002
|
+
allowedNeighborFilePaths.add(path.resolve(projectRoot, neighborKey.split("\u0000", 1)[0]));
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
if (!(error instanceof ScopedRelationshipWorkLimitReached))
|
|
1007
|
+
throw error;
|
|
1008
|
+
return {
|
|
1009
|
+
edges: [],
|
|
1010
|
+
incomplete: true,
|
|
1011
|
+
work_items: workBudget.consumed,
|
|
1012
|
+
work_limit_reached: true,
|
|
1013
|
+
excluded_neighbors: false,
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
const excludedRelationshipIds = new Set(query.excluded_relationship_ids ?? []);
|
|
1017
|
+
const endpointCollector = {
|
|
1018
|
+
edges: new Map(),
|
|
1019
|
+
endpointKey: key,
|
|
1020
|
+
direction,
|
|
1021
|
+
relationshipKinds,
|
|
1022
|
+
maxEdges: query.max_edges,
|
|
1023
|
+
stopAfterFirst: query.stop_after_first === true,
|
|
1024
|
+
allowedNeighborKeys,
|
|
1025
|
+
allowedNeighborFilePaths: allowedNeighborFilePaths
|
|
1026
|
+
? [...allowedNeighborFilePaths].sort((left, right) => left.localeCompare(right))
|
|
1027
|
+
: undefined,
|
|
1028
|
+
excludedRelationshipIds,
|
|
1029
|
+
workBudget,
|
|
1030
|
+
excludedNeighbors: false,
|
|
1031
|
+
};
|
|
1032
|
+
let located;
|
|
1033
|
+
try {
|
|
1034
|
+
located = scopedLocatedEndpoint(state, endpointCollector, endpoint);
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
if (!(error instanceof ScopedRelationshipWorkLimitReached))
|
|
1038
|
+
throw error;
|
|
1039
|
+
return {
|
|
1040
|
+
edges: [],
|
|
1041
|
+
incomplete: true,
|
|
1042
|
+
work_items: workBudget.consumed,
|
|
1043
|
+
work_limit_reached: true,
|
|
1044
|
+
excluded_neighbors: false,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
const outgoing = direction === "outgoing" || direction === "both";
|
|
1048
|
+
const incoming = direction === "incoming" || direction === "both";
|
|
1049
|
+
const producers = [];
|
|
1050
|
+
if (located.symbol) {
|
|
1051
|
+
if (outgoing && (relationshipKinds.has("extends") || relationshipKinds.has("implements"))) {
|
|
1052
|
+
producers.push({
|
|
1053
|
+
kinds: ["extends", "implements"],
|
|
1054
|
+
collect: (collector) => addScopedHeritageEdges(state, collector, located.symbol),
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (incoming &&
|
|
1058
|
+
(Node.isClassDeclaration(located.symbol.node) ||
|
|
1059
|
+
Node.isInterfaceDeclaration(located.symbol.node)) &&
|
|
1060
|
+
(relationshipKinds.has("extends") || relationshipKinds.has("implements"))) {
|
|
1061
|
+
producers.push({
|
|
1062
|
+
kinds: ["extends", "implements"],
|
|
1063
|
+
collect: (collector) => addScopedIncomingHeritageRelationships(state, collector),
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (relationshipKinds.has("reference")) {
|
|
1067
|
+
producers.push({
|
|
1068
|
+
kinds: ["reference"],
|
|
1069
|
+
collect: (collector) => addScopedMemberRelationships(state, collector, located.symbol),
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
if (outgoing && relationshipKinds.has("reference")) {
|
|
1073
|
+
producers.push({
|
|
1074
|
+
kinds: ["reference"],
|
|
1075
|
+
collect: (collector) => addScopedOutgoingReferences(state, collector, located.symbol),
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
if (incoming && relationshipKinds.has("reference")) {
|
|
1079
|
+
producers.push({
|
|
1080
|
+
kinds: ["reference"],
|
|
1081
|
+
collect: (collector) => addScopedIncomingReferences(state, collector, located.symbol),
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
if (incoming && relationshipKinds.has("import")) {
|
|
1085
|
+
producers.push({
|
|
1086
|
+
kinds: ["import"],
|
|
1087
|
+
collect: (collector) => addScopedIncomingImports(state, collector, located.symbol),
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (incoming && relationshipKinds.has("export")) {
|
|
1091
|
+
producers.push({
|
|
1092
|
+
kinds: ["export"],
|
|
1093
|
+
collect: (collector) => addScopedIncomingExports(state, collector, located.symbol),
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
for (const kind of ["import", "export"]) {
|
|
1099
|
+
if (!relationshipKinds.has(kind))
|
|
1100
|
+
continue;
|
|
1101
|
+
if (outgoing) {
|
|
1102
|
+
producers.push({
|
|
1103
|
+
kinds: [kind],
|
|
1104
|
+
collect: (collector) => addScopedModuleEdges(state, collector, located.sourceFile),
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
if (incoming) {
|
|
1108
|
+
producers.push({
|
|
1109
|
+
kinds: [kind],
|
|
1110
|
+
collect: (collector) => addScopedIncomingModuleEdges(state, collector),
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const merged = new Map();
|
|
1116
|
+
let incomplete = false;
|
|
1117
|
+
let excludedNeighbors = false;
|
|
1118
|
+
for (const producer of producers) {
|
|
1119
|
+
const collector = {
|
|
1120
|
+
edges: new Map(),
|
|
1121
|
+
endpointKey: key,
|
|
1122
|
+
direction,
|
|
1123
|
+
relationshipKinds: new Set(producer.kinds.filter((kind) => relationshipKinds.has(kind))),
|
|
1124
|
+
maxEdges: query.max_edges,
|
|
1125
|
+
stopAfterFirst: query.stop_after_first === true,
|
|
1126
|
+
allowedNeighborKeys,
|
|
1127
|
+
allowedNeighborFilePaths: endpointCollector.allowedNeighborFilePaths,
|
|
1128
|
+
excludedRelationshipIds,
|
|
1129
|
+
workBudget,
|
|
1130
|
+
excludedNeighbors: false,
|
|
1131
|
+
};
|
|
1132
|
+
try {
|
|
1133
|
+
producer.collect(collector);
|
|
1134
|
+
}
|
|
1135
|
+
catch (error) {
|
|
1136
|
+
if (!(error instanceof ScopedRelationshipLimitReached) &&
|
|
1137
|
+
!(error instanceof ScopedRelationshipWorkLimitReached)) {
|
|
1138
|
+
throw error;
|
|
1139
|
+
}
|
|
1140
|
+
incomplete = true;
|
|
1141
|
+
}
|
|
1142
|
+
for (const edge of collector.edges.values())
|
|
1143
|
+
merged.set(edge.relationship_id, edge);
|
|
1144
|
+
excludedNeighbors ||= collector.excludedNeighbors;
|
|
1145
|
+
if (workBudget.limitReached)
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
const ordered = [...merged.values()].sort((left, right) => scopedNeighborOrder(key, left, right));
|
|
1149
|
+
if (ordered.length > query.max_edges)
|
|
1150
|
+
incomplete = true;
|
|
1151
|
+
const selected = ordered.slice(0, query.max_edges);
|
|
1152
|
+
return {
|
|
1153
|
+
edges: selected.sort((left, right) => left.relationship_id.localeCompare(right.relationship_id)),
|
|
1154
|
+
incomplete,
|
|
1155
|
+
work_items: workBudget.consumed,
|
|
1156
|
+
work_limit_reached: workBudget.limitReached,
|
|
1157
|
+
excluded_neighbors: excludedNeighbors,
|
|
1158
|
+
};
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
export function collectCompilerRelationships(project, projectRoot, freshness, requestContext = NO_REQUEST_CONTEXT) {
|
|
1163
|
+
requestContext.checkpoint();
|
|
1164
|
+
const sourceFiles = project
|
|
1165
|
+
.getSourceFiles()
|
|
1166
|
+
.sort((left, right) => left.getFilePath().localeCompare(right.getFilePath()));
|
|
1167
|
+
const locatedSymbols = new Map();
|
|
1168
|
+
for (const sourceFile of sourceFiles) {
|
|
1169
|
+
requestContext.checkpoint();
|
|
1170
|
+
for (const symbol of collectSymbols(sourceFile)) {
|
|
1171
|
+
requestContext.checkpoint();
|
|
1172
|
+
locatedSymbols.set(symbol.node, symbol);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
const state = {
|
|
1176
|
+
projectRoot,
|
|
1177
|
+
sourceFiles: new Map(sourceFiles.map((sourceFile) => [sourceFileKey(sourceFile.getFilePath()), sourceFile])),
|
|
1178
|
+
locatedSymbols,
|
|
1179
|
+
freshness: normalizeFreshness(freshness),
|
|
1180
|
+
edges: new Map(),
|
|
1181
|
+
};
|
|
1182
|
+
requestContext.checkpoint();
|
|
1183
|
+
addReferenceEdges(state, requestContext);
|
|
1184
|
+
requestContext.checkpoint();
|
|
1185
|
+
addModuleEdges(state, requestContext);
|
|
1186
|
+
requestContext.checkpoint();
|
|
1187
|
+
addHeritageEdges(state, requestContext);
|
|
1188
|
+
requestContext.checkpoint();
|
|
1189
|
+
return [...state.edges.values()].sort((left, right) => [left.kind, left.source.file, left.source.selector, left.target.file, left.target.selector]
|
|
1190
|
+
.join("\u0000")
|
|
1191
|
+
.localeCompare([
|
|
1192
|
+
right.kind,
|
|
1193
|
+
right.source.file,
|
|
1194
|
+
right.source.selector,
|
|
1195
|
+
right.target.file,
|
|
1196
|
+
right.target.selector,
|
|
1197
|
+
].join("\u0000")));
|
|
1198
|
+
}
|