@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
|
@@ -20,6 +20,12 @@ import type {
|
|
|
20
20
|
TypeMapEntry,
|
|
21
21
|
} from '../../../../types.js';
|
|
22
22
|
import { computeConfidence } from '../../resolve.js';
|
|
23
|
+
import {
|
|
24
|
+
type CallNodeLookup,
|
|
25
|
+
findCaller,
|
|
26
|
+
resolveCallTargets,
|
|
27
|
+
resolveReceiverEdge,
|
|
28
|
+
} from '../call-resolver.js';
|
|
23
29
|
import type { PipelineContext } from '../context.js';
|
|
24
30
|
import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
|
|
25
31
|
|
|
@@ -89,12 +95,74 @@ function setupNodeLookups(ctx: PipelineContext, allNodes: QueryNodeRow[]): void
|
|
|
89
95
|
|
|
90
96
|
// ── Import edges ────────────────────────────────────────────────────────
|
|
91
97
|
|
|
98
|
+
/** Pick the edge kind for an import statement based on its modifiers. */
|
|
99
|
+
function importEdgeKind(imp: Import): string {
|
|
100
|
+
if (imp.reexport) return 'reexports';
|
|
101
|
+
if (imp.typeOnly) return 'imports-type';
|
|
102
|
+
if (imp.dynamicImport) return 'dynamic-imports';
|
|
103
|
+
return 'imports';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* For a `import type` statement, emit symbol-level `imports-type` edges so
|
|
108
|
+
* the target symbols get fan-in credit and aren't classified as dead code.
|
|
109
|
+
*/
|
|
110
|
+
function emitTypeOnlySymbolEdges(
|
|
111
|
+
ctx: PipelineContext,
|
|
112
|
+
imp: Import,
|
|
113
|
+
resolvedPath: string,
|
|
114
|
+
fileNodeId: number,
|
|
115
|
+
allEdgeRows: EdgeRowTuple[],
|
|
116
|
+
): void {
|
|
117
|
+
if (!ctx.nodesByNameAndFile) return;
|
|
118
|
+
for (const name of imp.names) {
|
|
119
|
+
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
120
|
+
let targetFile = resolvedPath;
|
|
121
|
+
if (isBarrelFile(ctx, resolvedPath)) {
|
|
122
|
+
const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
|
|
123
|
+
if (actual) targetFile = actual;
|
|
124
|
+
}
|
|
125
|
+
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
126
|
+
if (candidates && candidates.length > 0) {
|
|
127
|
+
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Process a single import statement and emit all resulting edges (file→file,
|
|
134
|
+
* type-only symbol-level, and barrel re-export targets).
|
|
135
|
+
*/
|
|
136
|
+
function emitEdgesForImport(
|
|
137
|
+
ctx: PipelineContext,
|
|
138
|
+
imp: Import,
|
|
139
|
+
fileNodeId: number,
|
|
140
|
+
relPath: string,
|
|
141
|
+
getNodeIdStmt: NodeIdStmt,
|
|
142
|
+
allEdgeRows: EdgeRowTuple[],
|
|
143
|
+
): void {
|
|
144
|
+
const resolvedPath = getResolved(ctx, path.join(ctx.rootDir, relPath), imp.source);
|
|
145
|
+
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
146
|
+
if (!targetRow) return;
|
|
147
|
+
|
|
148
|
+
const edgeKind = importEdgeKind(imp);
|
|
149
|
+
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
150
|
+
|
|
151
|
+
if (imp.typeOnly) {
|
|
152
|
+
emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
|
|
156
|
+
buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, allEdgeRows);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
92
160
|
function buildImportEdges(
|
|
93
161
|
ctx: PipelineContext,
|
|
94
162
|
getNodeIdStmt: NodeIdStmt,
|
|
95
163
|
allEdgeRows: EdgeRowTuple[],
|
|
96
164
|
): void {
|
|
97
|
-
const { fileSymbols, barrelOnlyFiles
|
|
165
|
+
const { fileSymbols, barrelOnlyFiles } = ctx;
|
|
98
166
|
|
|
99
167
|
for (const [relPath, symbols] of fileSymbols) {
|
|
100
168
|
const isBarrelOnly = barrelOnlyFiles.has(relPath);
|
|
@@ -105,40 +173,7 @@ function buildImportEdges(
|
|
|
105
173
|
for (const imp of symbols.imports) {
|
|
106
174
|
// Barrel-only files: only emit reexport edges, skip regular imports
|
|
107
175
|
if (isBarrelOnly && !imp.reexport) continue;
|
|
108
|
-
|
|
109
|
-
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
|
|
110
|
-
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
111
|
-
if (!targetRow) continue;
|
|
112
|
-
|
|
113
|
-
const edgeKind = imp.reexport
|
|
114
|
-
? 'reexports'
|
|
115
|
-
: imp.typeOnly
|
|
116
|
-
? 'imports-type'
|
|
117
|
-
: imp.dynamicImport
|
|
118
|
-
? 'dynamic-imports'
|
|
119
|
-
: 'imports';
|
|
120
|
-
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
121
|
-
|
|
122
|
-
// Type-only imports: create symbol-level edges so the target symbols
|
|
123
|
-
// get fan-in credit and aren't falsely classified as dead code.
|
|
124
|
-
if (imp.typeOnly && ctx.nodesByNameAndFile) {
|
|
125
|
-
for (const name of imp.names) {
|
|
126
|
-
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
127
|
-
let targetFile = resolvedPath;
|
|
128
|
-
if (isBarrelFile(ctx, resolvedPath)) {
|
|
129
|
-
const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
|
|
130
|
-
if (actual) targetFile = actual;
|
|
131
|
-
}
|
|
132
|
-
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
133
|
-
if (candidates && candidates.length > 0) {
|
|
134
|
-
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0]);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
|
|
140
|
-
buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, allEdgeRows);
|
|
141
|
-
}
|
|
176
|
+
emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdgeRows);
|
|
142
177
|
}
|
|
143
178
|
}
|
|
144
179
|
}
|
|
@@ -174,83 +209,98 @@ function buildBarrelEdges(
|
|
|
174
209
|
|
|
175
210
|
// ── Import edges (native engine) ────────────────────────────────────────
|
|
176
211
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
212
|
+
/** Native FFI input shape for a single import statement. */
|
|
213
|
+
interface NativeImportInfo {
|
|
214
|
+
source: string;
|
|
215
|
+
names: string[];
|
|
216
|
+
reexport: boolean;
|
|
217
|
+
typeOnly: boolean;
|
|
218
|
+
dynamicImport: boolean;
|
|
219
|
+
wildcardReexport: boolean;
|
|
220
|
+
}
|
|
184
221
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
/** Native FFI input shape for a single file. */
|
|
223
|
+
interface NativeFileInput {
|
|
224
|
+
file: string;
|
|
225
|
+
fileNodeId: number;
|
|
226
|
+
isBarrelOnly: boolean;
|
|
227
|
+
imports: NativeImportInfo[];
|
|
228
|
+
definitionNames: string[];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Native FFI input shape for re-exports of a single file. */
|
|
232
|
+
interface NativeReexportInput {
|
|
233
|
+
file: string;
|
|
234
|
+
reexports: Array<{ source: string; names: string[]; wildcardReexport: boolean }>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Lazily-resolving cache of file-node rows for the native input arrays. */
|
|
238
|
+
interface FileNodeIdRegistry {
|
|
239
|
+
ids: Array<{ file: string; nodeId: number }>;
|
|
240
|
+
add(relPath: string): { id: number } | undefined;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function createFileNodeIdRegistry(getNodeIdStmt: NodeIdStmt): FileNodeIdRegistry {
|
|
244
|
+
const ids: Array<{ file: string; nodeId: number }> = [];
|
|
245
|
+
const seen = new Set<string>();
|
|
246
|
+
const cache = new Map<string, { id: number }>();
|
|
247
|
+
return {
|
|
248
|
+
ids,
|
|
249
|
+
add(relPath: string) {
|
|
250
|
+
if (seen.has(relPath)) return cache.get(relPath);
|
|
251
|
+
const row = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
252
|
+
if (row) {
|
|
253
|
+
seen.add(relPath);
|
|
254
|
+
ids.push({ file: relPath, nodeId: row.id });
|
|
255
|
+
cache.set(relPath, row);
|
|
256
|
+
}
|
|
257
|
+
return row;
|
|
258
|
+
},
|
|
214
259
|
};
|
|
215
|
-
|
|
260
|
+
}
|
|
216
261
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
262
|
+
function toNativeImportInfo(imp: Import): NativeImportInfo {
|
|
263
|
+
return {
|
|
264
|
+
source: imp.source,
|
|
265
|
+
names: imp.names,
|
|
266
|
+
reexport: !!imp.reexport,
|
|
267
|
+
typeOnly: !!imp.typeOnly,
|
|
268
|
+
dynamicImport: !!imp.dynamicImport,
|
|
269
|
+
wildcardReexport: !!imp.wildcardReexport,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Pre-resolve every import for the given files, registering each resolved
|
|
275
|
+
* target with the registry so the native side has full node-id coverage.
|
|
276
|
+
*
|
|
277
|
+
* Resolved-import keys use forward-slash-normalized rootDir + "/" + relPath to
|
|
278
|
+
* match the Rust lookup format. On Windows, rootDir has backslashes but Rust
|
|
279
|
+
* normalizes them — the JS side must do the same or every key lookup misses
|
|
280
|
+
* (#750).
|
|
281
|
+
*/
|
|
282
|
+
function buildNativeFileInputs(
|
|
283
|
+
ctx: PipelineContext,
|
|
284
|
+
registry: FileNodeIdRegistry,
|
|
285
|
+
): {
|
|
286
|
+
files: NativeFileInput[];
|
|
287
|
+
resolvedImports: Array<{ key: string; resolvedPath: string }>;
|
|
288
|
+
} {
|
|
289
|
+
const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
|
|
223
290
|
const fwdRootDir = rootDir.replace(/\\/g, '/');
|
|
291
|
+
const files: NativeFileInput[] = [];
|
|
292
|
+
const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
|
|
224
293
|
|
|
225
294
|
for (const [relPath, symbols] of fileSymbols) {
|
|
226
|
-
const fileNodeRow =
|
|
295
|
+
const fileNodeRow = registry.add(relPath);
|
|
227
296
|
if (!fileNodeRow) continue;
|
|
228
297
|
|
|
229
|
-
const importInfos:
|
|
230
|
-
source: string;
|
|
231
|
-
names: string[];
|
|
232
|
-
reexport: boolean;
|
|
233
|
-
typeOnly: boolean;
|
|
234
|
-
dynamicImport: boolean;
|
|
235
|
-
wildcardReexport: boolean;
|
|
236
|
-
}> = [];
|
|
237
|
-
|
|
298
|
+
const importInfos: NativeImportInfo[] = [];
|
|
238
299
|
for (const imp of symbols.imports) {
|
|
239
|
-
// Pre-resolve and register target file node
|
|
240
300
|
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
|
|
301
|
+
registry.add(resolvedPath);
|
|
244
302
|
resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
|
|
245
|
-
|
|
246
|
-
importInfos.push({
|
|
247
|
-
source: imp.source,
|
|
248
|
-
names: imp.names,
|
|
249
|
-
reexport: !!imp.reexport,
|
|
250
|
-
typeOnly: !!imp.typeOnly,
|
|
251
|
-
dynamicImport: !!imp.dynamicImport,
|
|
252
|
-
wildcardReexport: !!imp.wildcardReexport,
|
|
253
|
-
});
|
|
303
|
+
importInfos.push(toNativeImportInfo(imp));
|
|
254
304
|
}
|
|
255
305
|
|
|
256
306
|
files.push({
|
|
@@ -261,61 +311,75 @@ function buildImportEdgesNative(
|
|
|
261
311
|
definitionNames: symbols.definitions.map((d) => d.name),
|
|
262
312
|
});
|
|
263
313
|
}
|
|
314
|
+
return { files, resolvedImports };
|
|
315
|
+
}
|
|
264
316
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}));
|
|
283
|
-
fileReexports.push({ file, reexports });
|
|
317
|
+
/** Flatten `ctx.reexportMap` into the array shape the native side expects. */
|
|
318
|
+
function buildNativeReexports(
|
|
319
|
+
ctx: PipelineContext,
|
|
320
|
+
registry: FileNodeIdRegistry,
|
|
321
|
+
): NativeReexportInput[] {
|
|
322
|
+
const fileReexports: NativeReexportInput[] = [];
|
|
323
|
+
if (!ctx.reexportMap) return fileReexports;
|
|
324
|
+
|
|
325
|
+
for (const [file, entries] of ctx.reexportMap) {
|
|
326
|
+
const reexports = (
|
|
327
|
+
entries as Array<{ source: string; names: string[]; wildcardReexport: boolean }>
|
|
328
|
+
).map((re) => ({
|
|
329
|
+
source: re.source,
|
|
330
|
+
names: re.names,
|
|
331
|
+
wildcardReexport: !!re.wildcardReexport,
|
|
332
|
+
}));
|
|
333
|
+
fileReexports.push({ file, reexports });
|
|
284
334
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
addFileNodeId(re.source);
|
|
288
|
-
}
|
|
335
|
+
for (const re of reexports) {
|
|
336
|
+
registry.add(re.source);
|
|
289
337
|
}
|
|
290
338
|
}
|
|
339
|
+
return fileReexports;
|
|
340
|
+
}
|
|
291
341
|
|
|
292
|
-
|
|
342
|
+
function collectBarrelFiles(ctx: PipelineContext): string[] {
|
|
293
343
|
const barrelFiles: string[] = [];
|
|
294
|
-
for (const [relPath] of fileSymbols) {
|
|
295
|
-
if (isBarrelFile(ctx, relPath))
|
|
296
|
-
barrelFiles.push(relPath);
|
|
297
|
-
}
|
|
344
|
+
for (const [relPath] of ctx.fileSymbols) {
|
|
345
|
+
if (isBarrelFile(ctx, relPath)) barrelFiles.push(relPath);
|
|
298
346
|
}
|
|
347
|
+
return barrelFiles;
|
|
348
|
+
}
|
|
299
349
|
|
|
300
|
-
|
|
350
|
+
function collectSymbolNodes(
|
|
351
|
+
ctx: PipelineContext,
|
|
352
|
+
): Array<{ name: string; file: string; nodeId: number }> {
|
|
301
353
|
const symbolNodes: Array<{ name: string; file: string; nodeId: number }> = [];
|
|
302
|
-
if (ctx.nodesByNameAndFile)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
}
|
|
354
|
+
if (!ctx.nodesByNameAndFile) return symbolNodes;
|
|
355
|
+
for (const [key, nodes] of ctx.nodesByNameAndFile) {
|
|
356
|
+
if (nodes.length === 0) continue;
|
|
357
|
+
const [name, file] = key.split('|');
|
|
358
|
+
symbolNodes.push({ name: name!, file: file!, nodeId: nodes[0]!.id });
|
|
309
359
|
}
|
|
360
|
+
return symbolNodes;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function buildImportEdgesNative(
|
|
364
|
+
ctx: PipelineContext,
|
|
365
|
+
getNodeIdStmt: NodeIdStmt,
|
|
366
|
+
allEdgeRows: EdgeRowTuple[],
|
|
367
|
+
native: NativeAddon,
|
|
368
|
+
): void {
|
|
369
|
+
const registry = createFileNodeIdRegistry(getNodeIdStmt);
|
|
370
|
+
|
|
371
|
+
const { files, resolvedImports } = buildNativeFileInputs(ctx, registry);
|
|
372
|
+
const fileReexports = buildNativeReexports(ctx, registry);
|
|
373
|
+
const barrelFiles = collectBarrelFiles(ctx);
|
|
374
|
+
const symbolNodes = collectSymbolNodes(ctx);
|
|
310
375
|
|
|
311
|
-
// 7. Call native
|
|
312
376
|
const nativeEdges = native.buildImportEdges!(
|
|
313
377
|
files,
|
|
314
378
|
resolvedImports,
|
|
315
379
|
fileReexports,
|
|
316
|
-
|
|
380
|
+
registry.ids,
|
|
317
381
|
barrelFiles,
|
|
318
|
-
rootDir,
|
|
382
|
+
ctx.rootDir,
|
|
319
383
|
symbolNodes,
|
|
320
384
|
) as NativeEdge[];
|
|
321
385
|
|
|
@@ -428,6 +492,7 @@ function buildCallEdgesJS(
|
|
|
428
492
|
allEdgeRows: EdgeRowTuple[],
|
|
429
493
|
): void {
|
|
430
494
|
const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
|
|
495
|
+
const lookup = makeContextLookup(ctx, getNodeIdStmt);
|
|
431
496
|
|
|
432
497
|
for (const [relPath, symbols] of fileSymbols) {
|
|
433
498
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
@@ -439,13 +504,12 @@ function buildCallEdgesJS(
|
|
|
439
504
|
const seenCallEdges = new Set<string>();
|
|
440
505
|
|
|
441
506
|
buildFileCallEdges(
|
|
442
|
-
ctx,
|
|
443
507
|
relPath,
|
|
444
508
|
symbols,
|
|
445
509
|
fileNodeRow,
|
|
446
510
|
importedNames,
|
|
447
511
|
seenCallEdges,
|
|
448
|
-
|
|
512
|
+
lookup,
|
|
449
513
|
allEdgeRows,
|
|
450
514
|
typeMap,
|
|
451
515
|
);
|
|
@@ -482,127 +546,37 @@ function buildImportedNamesMap(
|
|
|
482
546
|
return importedNames;
|
|
483
547
|
}
|
|
484
548
|
|
|
485
|
-
function
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
let callerSpan = Infinity;
|
|
494
|
-
for (const def of definitions) {
|
|
495
|
-
if (def.line <= call.line) {
|
|
496
|
-
const end = def.endLine || Infinity;
|
|
497
|
-
if (call.line <= end) {
|
|
498
|
-
const span = end - def.line;
|
|
499
|
-
if (span < callerSpan) {
|
|
500
|
-
const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
|
|
501
|
-
if (row) {
|
|
502
|
-
caller = row;
|
|
503
|
-
callerSpan = span;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
return caller || fileNodeRow;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function resolveCallTargets(
|
|
513
|
-
ctx: PipelineContext,
|
|
514
|
-
call: Call,
|
|
515
|
-
relPath: string,
|
|
516
|
-
importedNames: Map<string, string>,
|
|
517
|
-
typeMap: Map<string, TypeMapEntry | string>,
|
|
518
|
-
): { targets: NodeRow[]; importedFrom: string | undefined } {
|
|
519
|
-
const importedFrom = importedNames.get(call.name);
|
|
520
|
-
let targets: NodeRow[] | undefined;
|
|
521
|
-
|
|
522
|
-
if (importedFrom) {
|
|
523
|
-
targets = ctx.nodesByNameAndFile.get(`${call.name}|${importedFrom}`) || [];
|
|
524
|
-
if (targets.length === 0 && isBarrelFile(ctx, importedFrom)) {
|
|
525
|
-
const actualSource = resolveBarrelExport(ctx, importedFrom, call.name);
|
|
526
|
-
if (actualSource) {
|
|
527
|
-
targets = ctx.nodesByNameAndFile.get(`${call.name}|${actualSource}`) || [];
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (!targets || targets.length === 0) {
|
|
533
|
-
targets = ctx.nodesByNameAndFile.get(`${call.name}|${relPath}`) || [];
|
|
534
|
-
if (targets.length === 0) {
|
|
535
|
-
targets = resolveByMethodOrGlobal(ctx, call, relPath, typeMap);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (targets.length > 1) {
|
|
540
|
-
targets.sort((a, b) => {
|
|
541
|
-
const confA = computeConfidence(relPath, a.file, importedFrom ?? null);
|
|
542
|
-
const confB = computeConfidence(relPath, b.file, importedFrom ?? null);
|
|
543
|
-
return confB - confA;
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return { targets, importedFrom };
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function resolveByMethodOrGlobal(
|
|
551
|
-
ctx: PipelineContext,
|
|
552
|
-
call: Call,
|
|
553
|
-
relPath: string,
|
|
554
|
-
typeMap: Map<string, TypeMapEntry | string>,
|
|
555
|
-
): NodeRow[] {
|
|
556
|
-
// Type-aware resolution: translate variable receiver to its declared type
|
|
557
|
-
if (call.receiver && typeMap) {
|
|
558
|
-
const typeEntry = typeMap.get(call.receiver);
|
|
559
|
-
const typeName = typeEntry
|
|
560
|
-
? typeof typeEntry === 'string'
|
|
561
|
-
? typeEntry
|
|
562
|
-
: typeEntry.type
|
|
563
|
-
: null;
|
|
564
|
-
if (typeName) {
|
|
565
|
-
const qualifiedName = `${typeName}.${call.name}`;
|
|
566
|
-
const typed = (ctx.nodesByName.get(qualifiedName) || []).filter((n) => n.kind === 'method');
|
|
567
|
-
if (typed.length > 0) return typed;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (
|
|
572
|
-
!call.receiver ||
|
|
573
|
-
call.receiver === 'this' ||
|
|
574
|
-
call.receiver === 'self' ||
|
|
575
|
-
call.receiver === 'super'
|
|
576
|
-
) {
|
|
577
|
-
return (ctx.nodesByName.get(call.name) || []).filter(
|
|
578
|
-
(n) => computeConfidence(relPath, n.file, null) >= 0.5,
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
return [];
|
|
549
|
+
function makeContextLookup(ctx: PipelineContext, getNodeIdStmt: NodeIdStmt): CallNodeLookup {
|
|
550
|
+
return {
|
|
551
|
+
byNameAndFile: (name, file) => ctx.nodesByNameAndFile.get(`${name}|${file}`) ?? [],
|
|
552
|
+
byName: (name) => ctx.nodesByName.get(name) ?? [],
|
|
553
|
+
isBarrel: (file) => isBarrelFile(ctx, file),
|
|
554
|
+
resolveBarrel: (barrelFile, symbolName) => resolveBarrelExport(ctx, barrelFile, symbolName),
|
|
555
|
+
nodeId: (name, kind, file, line) => getNodeIdStmt.get(name, kind, file, line),
|
|
556
|
+
};
|
|
582
557
|
}
|
|
583
558
|
|
|
584
559
|
function buildFileCallEdges(
|
|
585
|
-
ctx: PipelineContext,
|
|
586
560
|
relPath: string,
|
|
587
561
|
symbols: ExtractorOutput,
|
|
588
562
|
fileNodeRow: { id: number },
|
|
589
563
|
importedNames: Map<string, string>,
|
|
590
564
|
seenCallEdges: Set<string>,
|
|
591
|
-
|
|
565
|
+
lookup: CallNodeLookup,
|
|
592
566
|
allEdgeRows: EdgeRowTuple[],
|
|
593
567
|
typeMap: Map<string, TypeMapEntry | string>,
|
|
594
568
|
): void {
|
|
595
569
|
for (const call of symbols.calls) {
|
|
596
570
|
if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
|
|
597
571
|
|
|
598
|
-
const caller = findCaller(call, symbols.definitions, relPath,
|
|
572
|
+
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
|
|
599
573
|
const isDynamic: number = call.dynamic ? 1 : 0;
|
|
600
574
|
const { targets, importedFrom } = resolveCallTargets(
|
|
601
|
-
|
|
575
|
+
lookup,
|
|
602
576
|
call,
|
|
603
577
|
relPath,
|
|
604
578
|
importedNames,
|
|
605
|
-
typeMap,
|
|
579
|
+
typeMap as Map<string, unknown>,
|
|
606
580
|
);
|
|
607
581
|
|
|
608
582
|
for (const t of targets) {
|
|
@@ -614,7 +588,6 @@ function buildFileCallEdges(
|
|
|
614
588
|
}
|
|
615
589
|
}
|
|
616
590
|
|
|
617
|
-
// Receiver edge
|
|
618
591
|
if (
|
|
619
592
|
call.receiver &&
|
|
620
593
|
!BUILTIN_RECEIVERS.has(call.receiver) &&
|
|
@@ -622,36 +595,17 @@ function buildFileCallEdges(
|
|
|
622
595
|
call.receiver !== 'self' &&
|
|
623
596
|
call.receiver !== 'super'
|
|
624
597
|
) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
allEdgeRows: EdgeRowTuple[],
|
|
637
|
-
typeMap: Map<string, TypeMapEntry | string>,
|
|
638
|
-
): void {
|
|
639
|
-
const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
|
|
640
|
-
const typeEntry = typeMap?.get(call.receiver!);
|
|
641
|
-
const typeName = typeEntry ? (typeof typeEntry === 'string' ? typeEntry : typeEntry.type) : null;
|
|
642
|
-
const typeConfidence = typeEntry && typeof typeEntry === 'object' ? typeEntry.confidence : null;
|
|
643
|
-
const effectiveReceiver = typeName || call.receiver!;
|
|
644
|
-
const samefile = ctx.nodesByNameAndFile.get(`${effectiveReceiver}|${relPath}`) || [];
|
|
645
|
-
const candidates = samefile.length > 0 ? samefile : ctx.nodesByName.get(effectiveReceiver) || [];
|
|
646
|
-
const receiverNodes = candidates.filter((n) => receiverKinds.has(n.kind));
|
|
647
|
-
if (receiverNodes.length > 0 && caller) {
|
|
648
|
-
const recvTarget = receiverNodes[0]!;
|
|
649
|
-
const recvKey = `recv|${caller.id}|${recvTarget.id}`;
|
|
650
|
-
if (!seenCallEdges.has(recvKey)) {
|
|
651
|
-
seenCallEdges.add(recvKey);
|
|
652
|
-
// Use type source confidence when available, otherwise 0.7 for untyped receiver
|
|
653
|
-
const confidence = typeConfidence ?? (typeName ? 0.9 : 0.7);
|
|
654
|
-
allEdgeRows.push([caller.id, recvTarget.id, 'receiver', confidence, 0]);
|
|
598
|
+
const recv = resolveReceiverEdge(
|
|
599
|
+
lookup,
|
|
600
|
+
{ name: call.name, receiver: call.receiver },
|
|
601
|
+
caller,
|
|
602
|
+
relPath,
|
|
603
|
+
typeMap as Map<string, unknown>,
|
|
604
|
+
seenCallEdges,
|
|
605
|
+
);
|
|
606
|
+
if (recv) {
|
|
607
|
+
allEdgeRows.push([recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0]);
|
|
608
|
+
}
|
|
655
609
|
}
|
|
656
610
|
}
|
|
657
611
|
}
|