@optave/codegraph 3.11.0 → 3.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -31
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +71 -0
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -0
- package/dist/domain/graph/builder/call-resolver.js +130 -0
- package/dist/domain/graph/builder/call-resolver.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -0
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +214 -127
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -44
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +10 -766
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +151 -192
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +10 -4
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +126 -79
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +2 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +37 -3
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +201 -136
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/elixir.js +95 -71
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +23 -31
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +27 -34
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +33 -58
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +38 -61
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +149 -52
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/embed.ts +54 -4
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/call-resolver.ts +181 -0
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +321 -152
- package/src/domain/graph/builder/pipeline.ts +19 -957
- package/src/domain/graph/builder/stages/build-edges.ts +229 -275
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +12 -4
- package/src/domain/parser.ts +143 -66
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +39 -3
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +235 -152
- package/src/extractors/elixir.ts +91 -64
- package/src/extractors/gleam.ts +33 -37
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +28 -35
- package/src/extractors/r.ts +38 -56
- package/src/extractors/solidity.ts +43 -71
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +199 -79
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +1 -1
|
@@ -21,6 +21,12 @@ import type {
|
|
|
21
21
|
} from '../../../types.js';
|
|
22
22
|
import { parseFileIncremental } from '../../parser.js';
|
|
23
23
|
import { computeConfidence, resolveImportPath } from '../resolve.js';
|
|
24
|
+
import {
|
|
25
|
+
type CallNodeLookup,
|
|
26
|
+
findCaller,
|
|
27
|
+
resolveCallTargets,
|
|
28
|
+
resolveReceiverEdge,
|
|
29
|
+
} from './call-resolver.js';
|
|
24
30
|
import { BUILTIN_RECEIVERS, readFileSafe } from './helpers.js';
|
|
25
31
|
|
|
26
32
|
// ── Local types ─────────────────────────────────────────────────────────
|
|
@@ -30,6 +36,7 @@ export interface IncrementalStmts {
|
|
|
30
36
|
insertEdge: { run: (...params: unknown[]) => unknown };
|
|
31
37
|
getNodeId: { get: (...params: unknown[]) => { id: number } | undefined };
|
|
32
38
|
countNodes: { get: (...params: unknown[]) => { c: number } | undefined };
|
|
39
|
+
countEdges: { get: (...params: unknown[]) => { c: number } | undefined };
|
|
33
40
|
listSymbols: { all: (...params: unknown[]) => unknown[] };
|
|
34
41
|
findNodeInFile: { all: (...params: unknown[]) => unknown[] };
|
|
35
42
|
findNodeByName: { all: (...params: unknown[]) => unknown[] };
|
|
@@ -40,6 +47,7 @@ interface RebuildResult {
|
|
|
40
47
|
nodesAdded: number;
|
|
41
48
|
nodesRemoved: number;
|
|
42
49
|
edgesAdded: number;
|
|
50
|
+
edgesBefore: number;
|
|
43
51
|
deleted?: boolean;
|
|
44
52
|
event?: string;
|
|
45
53
|
symbolDiff?: unknown;
|
|
@@ -183,8 +191,9 @@ function rebuildReverseDepEdges(
|
|
|
183
191
|
aliases,
|
|
184
192
|
skipBarrel ? null : db,
|
|
185
193
|
);
|
|
186
|
-
const importedNames = buildImportedNamesMap(symbols, rootDir, depRelPath, aliases);
|
|
187
|
-
edgesAdded += buildCallEdges(stmts, depRelPath, symbols, fileNodeRow, importedNames);
|
|
194
|
+
const importedNames = buildImportedNamesMap(symbols, rootDir, depRelPath, aliases, db);
|
|
195
|
+
edgesAdded += buildCallEdges(db, stmts, depRelPath, symbols, fileNodeRow, importedNames);
|
|
196
|
+
edgesAdded += buildClassHierarchyEdges(stmts, depRelPath, symbols);
|
|
188
197
|
return edgesAdded;
|
|
189
198
|
}
|
|
190
199
|
|
|
@@ -307,6 +316,69 @@ function resolveBarrelImportEdges(
|
|
|
307
316
|
return edgesAdded;
|
|
308
317
|
}
|
|
309
318
|
|
|
319
|
+
/** Emit symbol-level `imports-type` edges for a single `import type` statement. */
|
|
320
|
+
function emitTypeOnlySymbolEdges(
|
|
321
|
+
db: BetterSqlite3Database | null,
|
|
322
|
+
stmts: IncrementalStmts,
|
|
323
|
+
imp: ExtractorOutput['imports'][number],
|
|
324
|
+
resolvedPath: string,
|
|
325
|
+
fileNodeId: number,
|
|
326
|
+
): number {
|
|
327
|
+
let edgesAdded = 0;
|
|
328
|
+
for (const name of imp.names) {
|
|
329
|
+
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
330
|
+
let targetFile = resolvedPath;
|
|
331
|
+
if (db && isBarrelFile(db, resolvedPath)) {
|
|
332
|
+
const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
|
|
333
|
+
if (actual) targetFile = actual;
|
|
334
|
+
}
|
|
335
|
+
const candidates = stmts.findNodeInFile.all(cleanName, targetFile) as Array<{
|
|
336
|
+
id: number;
|
|
337
|
+
file: string;
|
|
338
|
+
}>;
|
|
339
|
+
if (candidates.length === 0) continue;
|
|
340
|
+
stmts.insertEdge.run(fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0);
|
|
341
|
+
edgesAdded++;
|
|
342
|
+
}
|
|
343
|
+
return edgesAdded;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Process a single import statement: emit the file→file edge, any
|
|
348
|
+
* symbol-level type-only edges, and barrel re-export edges.
|
|
349
|
+
*/
|
|
350
|
+
function emitEdgesForImport(
|
|
351
|
+
stmts: IncrementalStmts,
|
|
352
|
+
imp: ExtractorOutput['imports'][number],
|
|
353
|
+
fileNodeId: number,
|
|
354
|
+
relPath: string,
|
|
355
|
+
rootDir: string,
|
|
356
|
+
aliases: PathAliases,
|
|
357
|
+
db: BetterSqlite3Database | null,
|
|
358
|
+
): number {
|
|
359
|
+
const resolvedPath = resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
|
|
360
|
+
const targetRow = stmts.getNodeId.get(resolvedPath, 'file', resolvedPath, 0);
|
|
361
|
+
if (!targetRow) return 0;
|
|
362
|
+
|
|
363
|
+
const edgeKind = imp.reexport
|
|
364
|
+
? 'reexports'
|
|
365
|
+
: imp.typeOnly
|
|
366
|
+
? 'imports-type'
|
|
367
|
+
: imp.dynamicImport
|
|
368
|
+
? 'dynamic-imports'
|
|
369
|
+
: 'imports';
|
|
370
|
+
stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
|
|
371
|
+
let edgesAdded = 1;
|
|
372
|
+
|
|
373
|
+
if (imp.typeOnly) {
|
|
374
|
+
edgesAdded += emitTypeOnlySymbolEdges(db, stmts, imp, resolvedPath, fileNodeId);
|
|
375
|
+
}
|
|
376
|
+
if (!imp.reexport && db) {
|
|
377
|
+
edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeId, resolvedPath, imp);
|
|
378
|
+
}
|
|
379
|
+
return edgesAdded;
|
|
380
|
+
}
|
|
381
|
+
|
|
310
382
|
function buildImportEdges(
|
|
311
383
|
stmts: IncrementalStmts,
|
|
312
384
|
relPath: string,
|
|
@@ -318,44 +390,7 @@ function buildImportEdges(
|
|
|
318
390
|
): number {
|
|
319
391
|
let edgesAdded = 0;
|
|
320
392
|
for (const imp of symbols.imports) {
|
|
321
|
-
|
|
322
|
-
path.join(rootDir, relPath),
|
|
323
|
-
imp.source,
|
|
324
|
-
rootDir,
|
|
325
|
-
aliases,
|
|
326
|
-
);
|
|
327
|
-
const targetRow = stmts.getNodeId.get(resolvedPath, 'file', resolvedPath, 0);
|
|
328
|
-
if (targetRow) {
|
|
329
|
-
const edgeKind = imp.reexport ? 'reexports' : imp.typeOnly ? 'imports-type' : 'imports';
|
|
330
|
-
stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
|
|
331
|
-
edgesAdded++;
|
|
332
|
-
|
|
333
|
-
// Type-only imports: create symbol-level edges so the target symbols
|
|
334
|
-
// get fan-in credit and aren't falsely classified as dead code.
|
|
335
|
-
if (imp.typeOnly) {
|
|
336
|
-
for (const name of imp.names) {
|
|
337
|
-
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
338
|
-
let targetFile = resolvedPath;
|
|
339
|
-
if (db && isBarrelFile(db, resolvedPath)) {
|
|
340
|
-
const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
|
|
341
|
-
if (actual) targetFile = actual;
|
|
342
|
-
}
|
|
343
|
-
const candidates = stmts.findNodeInFile.all(cleanName, targetFile) as Array<{
|
|
344
|
-
id: number;
|
|
345
|
-
file: string;
|
|
346
|
-
}>;
|
|
347
|
-
if (candidates.length > 0) {
|
|
348
|
-
stmts.insertEdge.run(fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0);
|
|
349
|
-
edgesAdded++;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Barrel resolution: create edges through re-export chains
|
|
355
|
-
if (!imp.reexport && db) {
|
|
356
|
-
edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeId, resolvedPath, imp);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
393
|
+
edgesAdded += emitEdgesForImport(stmts, imp, fileNodeId, relPath, rootDir, aliases, db);
|
|
359
394
|
}
|
|
360
395
|
return edgesAdded;
|
|
361
396
|
}
|
|
@@ -365,6 +400,7 @@ function buildImportedNamesMap(
|
|
|
365
400
|
rootDir: string,
|
|
366
401
|
relPath: string,
|
|
367
402
|
aliases: PathAliases,
|
|
403
|
+
db: BetterSqlite3Database,
|
|
368
404
|
): Map<string, string> {
|
|
369
405
|
const importedNames = new Map<string, string>();
|
|
370
406
|
for (const imp of symbols.imports) {
|
|
@@ -375,78 +411,79 @@ function buildImportedNamesMap(
|
|
|
375
411
|
aliases,
|
|
376
412
|
);
|
|
377
413
|
for (const name of imp.names) {
|
|
378
|
-
|
|
414
|
+
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
415
|
+
// Mirror full-build's `buildImportedNamesMap`: follow barrel re-exports so
|
|
416
|
+
// `importedNames` maps to the *defining* file, not the barrel. This ensures
|
|
417
|
+
// `computeConfidence` gets `importedFrom === targetFile` and returns 1.0
|
|
418
|
+
// instead of the cross-directory fallback (0.3).
|
|
419
|
+
let targetFile = resolvedPath;
|
|
420
|
+
if (isBarrelFile(db, resolvedPath)) {
|
|
421
|
+
const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
|
|
422
|
+
if (actual) targetFile = actual;
|
|
423
|
+
}
|
|
424
|
+
importedNames.set(cleanName, targetFile);
|
|
379
425
|
}
|
|
380
426
|
}
|
|
381
427
|
return importedNames;
|
|
382
428
|
}
|
|
383
429
|
|
|
384
|
-
// ──
|
|
430
|
+
// ── Class hierarchy edges ───────────────────────────────────────────────
|
|
385
431
|
|
|
386
|
-
|
|
387
|
-
call: ExtractorOutput['calls'][number],
|
|
388
|
-
definitions: ExtractorOutput['definitions'],
|
|
389
|
-
relPath: string,
|
|
390
|
-
stmts: IncrementalStmts,
|
|
391
|
-
): { id: number } | null {
|
|
392
|
-
let caller: { id: number } | null = null;
|
|
393
|
-
let callerSpan = Infinity;
|
|
394
|
-
for (const def of definitions) {
|
|
395
|
-
if (def.line <= call.line) {
|
|
396
|
-
const end = def.endLine || Infinity;
|
|
397
|
-
if (call.line <= end) {
|
|
398
|
-
const span = end - def.line;
|
|
399
|
-
if (span < callerSpan) {
|
|
400
|
-
const row = stmts.getNodeId.get(def.name, def.kind, relPath, def.line);
|
|
401
|
-
if (row) {
|
|
402
|
-
caller = row;
|
|
403
|
-
callerSpan = span;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return caller;
|
|
410
|
-
}
|
|
432
|
+
type NodeWithKind = { id: number; kind: string; file: string };
|
|
411
433
|
|
|
412
|
-
|
|
434
|
+
const HIERARCHY_SOURCE_KINDS = new Set(['class', 'struct', 'record', 'enum']);
|
|
435
|
+
const EXTENDS_TARGET_KINDS = new Set(['class', 'struct', 'trait', 'record']);
|
|
436
|
+
const IMPLEMENTS_TARGET_KINDS = new Set(['interface', 'trait', 'class']);
|
|
437
|
+
|
|
438
|
+
function buildClassHierarchyEdges(
|
|
413
439
|
stmts: IncrementalStmts,
|
|
414
|
-
call: ExtractorOutput['calls'][number],
|
|
415
440
|
relPath: string,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
441
|
+
symbols: ExtractorOutput,
|
|
442
|
+
): number {
|
|
443
|
+
let edgesAdded = 0;
|
|
444
|
+
for (const cls of symbols.classes) {
|
|
445
|
+
const sourceRow = (stmts.findNodeInFile.all(cls.name, relPath) as NodeWithKind[]).find((n) =>
|
|
446
|
+
HIERARCHY_SOURCE_KINDS.has(n.kind),
|
|
447
|
+
);
|
|
448
|
+
if (!sourceRow) continue;
|
|
449
|
+
|
|
450
|
+
if (cls.extends) {
|
|
451
|
+
for (const t of (stmts.findNodeByName.all(cls.extends) as NodeWithKind[]).filter((n) =>
|
|
452
|
+
EXTENDS_TARGET_KINDS.has(n.kind),
|
|
453
|
+
)) {
|
|
454
|
+
stmts.insertEdge.run(sourceRow.id, t.id, 'extends', 1.0, 0);
|
|
455
|
+
edgesAdded++;
|
|
456
|
+
}
|
|
431
457
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
: (typeEntry as { type?: string }).type
|
|
440
|
-
: null;
|
|
441
|
-
if (typeName) {
|
|
442
|
-
const qualified = `${typeName}.${call.name}`;
|
|
443
|
-
targets = stmts.findNodeByName.all(qualified) as Array<{ id: number; file: string }>;
|
|
458
|
+
if (cls.implements) {
|
|
459
|
+
for (const t of (stmts.findNodeByName.all(cls.implements) as NodeWithKind[]).filter((n) =>
|
|
460
|
+
IMPLEMENTS_TARGET_KINDS.has(n.kind),
|
|
461
|
+
)) {
|
|
462
|
+
stmts.insertEdge.run(sourceRow.id, t.id, 'implements', 1.0, 0);
|
|
463
|
+
edgesAdded++;
|
|
464
|
+
}
|
|
444
465
|
}
|
|
445
466
|
}
|
|
446
|
-
return
|
|
467
|
+
return edgesAdded;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ── Call edge building ──────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
function makeIncrementalLookup(db: BetterSqlite3Database, stmts: IncrementalStmts): CallNodeLookup {
|
|
473
|
+
return {
|
|
474
|
+
byNameAndFile: (name, file) =>
|
|
475
|
+
stmts.findNodeInFile.all(name, file) as Array<{ id: number; file: string; kind?: string }>,
|
|
476
|
+
byName: (name) =>
|
|
477
|
+
stmts.findNodeByName.all(name) as Array<{ id: number; file: string; kind?: string }>,
|
|
478
|
+
isBarrel: (file) => isBarrelFile(db, file),
|
|
479
|
+
resolveBarrel: (barrelFile, symbolName) => resolveBarrelTarget(db, barrelFile, symbolName),
|
|
480
|
+
nodeId: (name, kind, file, line) =>
|
|
481
|
+
stmts.getNodeId.get(name, kind, file, line) as { id: number } | undefined,
|
|
482
|
+
};
|
|
447
483
|
}
|
|
448
484
|
|
|
449
485
|
function buildCallEdges(
|
|
486
|
+
db: BetterSqlite3Database,
|
|
450
487
|
stmts: IncrementalStmts,
|
|
451
488
|
relPath: string,
|
|
452
489
|
symbols: ExtractorOutput,
|
|
@@ -465,13 +502,16 @@ function buildCallEdges(
|
|
|
465
502
|
]),
|
|
466
503
|
)
|
|
467
504
|
: new Map();
|
|
505
|
+
const seenCallEdges = new Set<string>();
|
|
506
|
+
const lookup = makeIncrementalLookup(db, stmts);
|
|
468
507
|
let edgesAdded = 0;
|
|
508
|
+
|
|
469
509
|
for (const call of symbols.calls) {
|
|
470
510
|
if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
|
|
471
511
|
|
|
472
|
-
const caller = findCaller(call, symbols.definitions, relPath,
|
|
512
|
+
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
473
513
|
const { targets, importedFrom } = resolveCallTargets(
|
|
474
|
-
|
|
514
|
+
lookup,
|
|
475
515
|
call,
|
|
476
516
|
relPath,
|
|
477
517
|
importedNames,
|
|
@@ -479,18 +519,175 @@ function buildCallEdges(
|
|
|
479
519
|
);
|
|
480
520
|
|
|
481
521
|
for (const t of targets) {
|
|
482
|
-
|
|
522
|
+
const edgeKey = `${caller.id}|${t.id}`;
|
|
523
|
+
if (t.id !== caller.id && !seenCallEdges.has(edgeKey)) {
|
|
524
|
+
seenCallEdges.add(edgeKey);
|
|
483
525
|
const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
|
|
484
526
|
stmts.insertEdge.run(caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0);
|
|
485
527
|
edgesAdded++;
|
|
486
528
|
}
|
|
487
529
|
}
|
|
530
|
+
|
|
531
|
+
if (
|
|
532
|
+
call.receiver &&
|
|
533
|
+
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
534
|
+
call.receiver !== 'this' &&
|
|
535
|
+
call.receiver !== 'self' &&
|
|
536
|
+
call.receiver !== 'super'
|
|
537
|
+
) {
|
|
538
|
+
const recv = resolveReceiverEdge(
|
|
539
|
+
lookup,
|
|
540
|
+
{ name: call.name, receiver: call.receiver },
|
|
541
|
+
caller,
|
|
542
|
+
relPath,
|
|
543
|
+
typeMap,
|
|
544
|
+
seenCallEdges,
|
|
545
|
+
);
|
|
546
|
+
if (recv) {
|
|
547
|
+
stmts.insertEdge.run(recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0);
|
|
548
|
+
edgesAdded++;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
488
551
|
}
|
|
489
552
|
return edgesAdded;
|
|
490
553
|
}
|
|
491
554
|
|
|
492
555
|
// ── Main entry point ────────────────────────────────────────────────────
|
|
493
556
|
|
|
557
|
+
/** Build the "this file was deleted" result returned by `rebuildFile`. */
|
|
558
|
+
function buildDeletionResult(
|
|
559
|
+
relPath: string,
|
|
560
|
+
oldNodes: number,
|
|
561
|
+
edgesBefore: number,
|
|
562
|
+
oldSymbols: unknown[],
|
|
563
|
+
diffSymbols: ((old: unknown[], new_: unknown[]) => unknown) | undefined,
|
|
564
|
+
): RebuildResult {
|
|
565
|
+
const symbolDiff = diffSymbols ? diffSymbols(oldSymbols, []) : null;
|
|
566
|
+
return {
|
|
567
|
+
file: relPath,
|
|
568
|
+
nodesAdded: 0,
|
|
569
|
+
nodesRemoved: oldNodes,
|
|
570
|
+
edgesAdded: 0,
|
|
571
|
+
edgesBefore,
|
|
572
|
+
deleted: true,
|
|
573
|
+
event: 'deleted',
|
|
574
|
+
symbolDiff,
|
|
575
|
+
nodesBefore: oldNodes,
|
|
576
|
+
nodesAfter: 0,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Rebuild all edges originating in the single (just-parsed) target file. */
|
|
581
|
+
function rebuildEdgesForTargetFile(
|
|
582
|
+
db: BetterSqlite3Database,
|
|
583
|
+
stmts: IncrementalStmts,
|
|
584
|
+
relPath: string,
|
|
585
|
+
symbols: ExtractorOutput,
|
|
586
|
+
fileNodeRow: { id: number },
|
|
587
|
+
rootDir: string,
|
|
588
|
+
): number {
|
|
589
|
+
const aliases: PathAliases = { baseUrl: null, paths: {} };
|
|
590
|
+
let edgesAdded = buildContainmentEdges(db, stmts, relPath, symbols);
|
|
591
|
+
edgesAdded += rebuildDirContainment(db, stmts, relPath);
|
|
592
|
+
edgesAdded += buildImportEdges(stmts, relPath, symbols, rootDir, fileNodeRow.id, aliases, db);
|
|
593
|
+
const importedNames = buildImportedNamesMap(symbols, rootDir, relPath, aliases, db);
|
|
594
|
+
edgesAdded += buildCallEdges(db, stmts, relPath, symbols, fileNodeRow, importedNames);
|
|
595
|
+
edgesAdded += buildClassHierarchyEdges(stmts, relPath, symbols);
|
|
596
|
+
return edgesAdded;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Re-parse the reverse-deps and delete their outgoing edges so the cascade
|
|
601
|
+
* can rebuild them. Returns the parsed symbols map together with the total
|
|
602
|
+
* edge count across all deps measured *before* deletion — callers add this
|
|
603
|
+
* to their own `edgesBefore` so the net delta stays correct even when the
|
|
604
|
+
* reverse-dep cascade re-inserts edges.
|
|
605
|
+
*/
|
|
606
|
+
async function parseReverseDeps(
|
|
607
|
+
db: BetterSqlite3Database,
|
|
608
|
+
rootDir: string,
|
|
609
|
+
reverseDeps: string[],
|
|
610
|
+
stmts: IncrementalStmts,
|
|
611
|
+
engineOpts: EngineOpts,
|
|
612
|
+
cache: unknown,
|
|
613
|
+
): Promise<{ depSymbols: Map<string, ExtractorOutput>; reverseDepsEdgesBefore: number }> {
|
|
614
|
+
const depSymbols = new Map<string, ExtractorOutput>();
|
|
615
|
+
let reverseDepsEdgesBefore = 0;
|
|
616
|
+
for (const depRelPath of reverseDeps) {
|
|
617
|
+
const symbols_ = await parseReverseDep(rootDir, depRelPath, engineOpts, cache);
|
|
618
|
+
if (symbols_) {
|
|
619
|
+
reverseDepsEdgesBefore += stmts.countEdges.get(depRelPath)?.c ?? 0;
|
|
620
|
+
deleteOutgoingEdges(db, depRelPath);
|
|
621
|
+
depSymbols.set(depRelPath, symbols_);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return { depSymbols, reverseDepsEdgesBefore };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Pass 2 of the reverse-dep cascade: now that the changed file's `reexports`
|
|
629
|
+
* edges exist, resolve barrel imports for every reverse-dep so transitive
|
|
630
|
+
* call edges through the barrel still find their targets.
|
|
631
|
+
*/
|
|
632
|
+
function emitBarrelImportEdgesForReverseDeps(
|
|
633
|
+
db: BetterSqlite3Database,
|
|
634
|
+
stmts: IncrementalStmts,
|
|
635
|
+
depSymbols: Map<string, ExtractorOutput>,
|
|
636
|
+
rootDir: string,
|
|
637
|
+
): number {
|
|
638
|
+
let edgesAdded = 0;
|
|
639
|
+
for (const [depRelPath, symbols_] of depSymbols) {
|
|
640
|
+
const fileNodeRow_ = stmts.getNodeId.get(depRelPath, 'file', depRelPath, 0);
|
|
641
|
+
if (!fileNodeRow_) continue;
|
|
642
|
+
const aliases_: PathAliases = { baseUrl: null, paths: {} };
|
|
643
|
+
for (const imp of symbols_.imports) {
|
|
644
|
+
if (imp.reexport) continue;
|
|
645
|
+
const resolvedPath = resolveImportPath(
|
|
646
|
+
path.join(rootDir, depRelPath),
|
|
647
|
+
imp.source,
|
|
648
|
+
rootDir,
|
|
649
|
+
aliases_,
|
|
650
|
+
);
|
|
651
|
+
edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeRow_.id, resolvedPath, imp);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return edgesAdded;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Two-pass reverse-dep cascade:
|
|
659
|
+
* 1. Rebuild direct edges (creating `reexports` edges for barrels).
|
|
660
|
+
* 2. Add barrel import edges (which need `reexports` edges to exist).
|
|
661
|
+
* Returns both the gross edges-added count and the pre-deletion edge count
|
|
662
|
+
* for all reverse deps so callers can compute a true net delta.
|
|
663
|
+
*/
|
|
664
|
+
async function runReverseDepCascade(
|
|
665
|
+
db: BetterSqlite3Database,
|
|
666
|
+
rootDir: string,
|
|
667
|
+
reverseDeps: string[],
|
|
668
|
+
stmts: IncrementalStmts,
|
|
669
|
+
engineOpts: EngineOpts,
|
|
670
|
+
cache: unknown,
|
|
671
|
+
): Promise<{ edgesAdded: number; reverseDepsEdgesBefore: number }> {
|
|
672
|
+
const { depSymbols, reverseDepsEdgesBefore } = await parseReverseDeps(
|
|
673
|
+
db,
|
|
674
|
+
rootDir,
|
|
675
|
+
reverseDeps,
|
|
676
|
+
stmts,
|
|
677
|
+
engineOpts,
|
|
678
|
+
cache,
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
let edgesAdded = 0;
|
|
682
|
+
// Pass 1: direct edges only (no barrel resolution) — creates reexports edges
|
|
683
|
+
for (const [depRelPath, symbols_] of depSymbols) {
|
|
684
|
+
edgesAdded += rebuildReverseDepEdges(db, rootDir, depRelPath, symbols_, stmts, true);
|
|
685
|
+
}
|
|
686
|
+
// Pass 2: add barrel import edges (reexports edges now exist)
|
|
687
|
+
edgesAdded += emitBarrelImportEdgesForReverseDeps(db, stmts, depSymbols, rootDir);
|
|
688
|
+
return { edgesAdded, reverseDepsEdgesBefore };
|
|
689
|
+
}
|
|
690
|
+
|
|
494
691
|
/**
|
|
495
692
|
* Parse a single file and update the database incrementally.
|
|
496
693
|
*/
|
|
@@ -506,6 +703,7 @@ export async function rebuildFile(
|
|
|
506
703
|
const { diffSymbols } = options;
|
|
507
704
|
const relPath = normalizePath(path.relative(rootDir, filePath));
|
|
508
705
|
const oldNodes = stmts.countNodes.get(relPath)?.c || 0;
|
|
706
|
+
const edgesBefore = stmts.countEdges.get(relPath)?.c || 0;
|
|
509
707
|
const oldSymbols: unknown[] = diffSymbols ? stmts.listSymbols.all(relPath) : [];
|
|
510
708
|
|
|
511
709
|
// Find reverse-deps BEFORE purging (edges still reference the old nodes)
|
|
@@ -519,18 +717,7 @@ export async function rebuildFile(
|
|
|
519
717
|
|
|
520
718
|
if (!fs.existsSync(filePath)) {
|
|
521
719
|
if (cache) (cache as { remove(p: string): void }).remove(filePath);
|
|
522
|
-
|
|
523
|
-
return {
|
|
524
|
-
file: relPath,
|
|
525
|
-
nodesAdded: 0,
|
|
526
|
-
nodesRemoved: oldNodes,
|
|
527
|
-
edgesAdded: 0,
|
|
528
|
-
deleted: true,
|
|
529
|
-
event: 'deleted',
|
|
530
|
-
symbolDiff,
|
|
531
|
-
nodesBefore: oldNodes,
|
|
532
|
-
nodesAfter: 0,
|
|
533
|
-
};
|
|
720
|
+
return buildDeletionResult(relPath, oldNodes, edgesBefore, oldSymbols, diffSymbols);
|
|
534
721
|
}
|
|
535
722
|
|
|
536
723
|
let code: string;
|
|
@@ -551,47 +738,28 @@ export async function rebuildFile(
|
|
|
551
738
|
|
|
552
739
|
const fileNodeRow = stmts.getNodeId.get(relPath, 'file', relPath, 0);
|
|
553
740
|
if (!fileNodeRow)
|
|
554
|
-
return {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const importedNames = buildImportedNamesMap(symbols, rootDir, relPath, aliases);
|
|
562
|
-
edgesAdded += buildCallEdges(stmts, relPath, symbols, fileNodeRow, importedNames);
|
|
741
|
+
return {
|
|
742
|
+
file: relPath,
|
|
743
|
+
nodesAdded: newNodes,
|
|
744
|
+
nodesRemoved: oldNodes,
|
|
745
|
+
edgesAdded: 0,
|
|
746
|
+
edgesBefore,
|
|
747
|
+
};
|
|
563
748
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
// Pass 2: add barrel import edges (reexports edges now exist)
|
|
580
|
-
for (const [depRelPath, symbols_] of depSymbols) {
|
|
581
|
-
const fileNodeRow_ = stmts.getNodeId.get(depRelPath, 'file', depRelPath, 0);
|
|
582
|
-
if (!fileNodeRow_) continue;
|
|
583
|
-
const aliases_: PathAliases = { baseUrl: null, paths: {} };
|
|
584
|
-
for (const imp of symbols_.imports) {
|
|
585
|
-
if (imp.reexport) continue;
|
|
586
|
-
const resolvedPath = resolveImportPath(
|
|
587
|
-
path.join(rootDir, depRelPath),
|
|
588
|
-
imp.source,
|
|
589
|
-
rootDir,
|
|
590
|
-
aliases_,
|
|
591
|
-
);
|
|
592
|
-
edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeRow_.id, resolvedPath, imp);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
749
|
+
let edgesAdded = rebuildEdgesForTargetFile(db, stmts, relPath, symbols, fileNodeRow, rootDir);
|
|
750
|
+
const { edgesAdded: cascadeEdges, reverseDepsEdgesBefore } = await runReverseDepCascade(
|
|
751
|
+
db,
|
|
752
|
+
rootDir,
|
|
753
|
+
reverseDeps,
|
|
754
|
+
stmts,
|
|
755
|
+
engineOpts,
|
|
756
|
+
cache,
|
|
757
|
+
);
|
|
758
|
+
edgesAdded += cascadeEdges;
|
|
759
|
+
// Include pre-deletion edge counts from reverse deps so the net delta
|
|
760
|
+
// (edgesAdded - edgesBefore) is correct even when the cascade re-inserts
|
|
761
|
+
// their edges unchanged.
|
|
762
|
+
const totalEdgesBefore = edgesBefore + reverseDepsEdgesBefore;
|
|
595
763
|
|
|
596
764
|
const symbolDiff = diffSymbols ? diffSymbols(oldSymbols, newSymbols) : null;
|
|
597
765
|
const event = oldNodes === 0 ? 'added' : 'modified';
|
|
@@ -601,6 +769,7 @@ export async function rebuildFile(
|
|
|
601
769
|
nodesAdded: newNodes,
|
|
602
770
|
nodesRemoved: oldNodes,
|
|
603
771
|
edgesAdded,
|
|
772
|
+
edgesBefore: totalEdgesBefore,
|
|
604
773
|
deleted: false,
|
|
605
774
|
event,
|
|
606
775
|
symbolDiff,
|