Skip to content

Commit 0d79d98

Browse files
authored
fix(native): resolve dataflow null paramIndex and import edge key mismatch (#788)
* fix(native): resolve dataflow null paramIndex and import edge key mismatch Two native engine bugs found during v3.8.0 dogfooding: 1. Dataflow edge insertion fails with null paramIndex (#753): napi-rs Option<u32> expects `undefined`, not `null`. Changed paramIndex from null to undefined for returns/mutates edges. 2. Native import edge builder produces 0 edges on Windows (#750): buildImportEdgesNative built resolve keys with backslash rootDir but Rust normalizes to forward slashes, causing every lookup to miss. Fixed by normalizing rootDir in JS keys. Added defensive fallback: if native returns 0 edges when imports exist, retry with the JS implementation. Closes #750, closes #753 * fix: tighten paramIndex type and document fallback false-positive (#788) - Remove `null` from bulkInsertDataflow paramIndex type to match napi-rs Option<u32> contract, preventing future null regressions - Document known false-positive in import-edge fallback for codebases with only external imports
1 parent 6cd885a commit 0d79d98

3 files changed

Lines changed: 23 additions & 8 deletions

File tree

src/domain/graph/builder/stages/build-edges.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,12 @@ function buildImportEdgesNative(
198198
const fileNodeRowCache = new Map<string, { id: number }>();
199199

200200
// 2. Pre-resolve all imports and build resolved imports array.
201-
// Keys use rootDir + "/" + relPath to match the Rust lookup format
202-
// (format!("{}/{}", root_dir, file)) — avoids separator mismatches on Windows
203-
// where path.join uses backslashes but Rust joins with forward slash.
201+
// Keys use forward-slash-normalized rootDir + "/" + relPath to match the Rust
202+
// lookup format (format!("{}/{}", root_dir.replace('\\', "/"), file)).
203+
// On Windows, rootDir has backslashes but Rust normalizes them — the JS side
204+
// must do the same or every resolve key lookup misses (#750).
204205
const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
206+
const fwdRootDir = rootDir.replace(/\\/g, '/');
205207

206208
for (const [relPath, symbols] of fileSymbols) {
207209
const fileNodeRow = addFileNodeId(relPath);
@@ -221,8 +223,8 @@ function buildImportEdgesNative(
221223
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
222224
addFileNodeId(resolvedPath);
223225

224-
// Key matches Rust's format!("{}/{}", root_dir, file_input.file)
225-
resolvedImports.push({ key: `${rootDir}/${relPath}|${imp.source}`, resolvedPath });
226+
// Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
227+
resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
226228

227229
importInfos.push({
228230
source: imp.source,
@@ -737,7 +739,20 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
737739
const useNativeImportEdges =
738740
native?.buildImportEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
739741
if (useNativeImportEdges) {
742+
const beforeLen = allEdgeRows.length;
740743
buildImportEdgesNative(ctx, getNodeIdStmt, allEdgeRows, native!);
744+
// Fallback: if native produced 0 import edges but there are imports to
745+
// process, the native binary may have a key-format mismatch (e.g. Windows
746+
// path separators — #750). Retry with the JS implementation.
747+
// NOTE: This also fires for codebases where every import targets an
748+
// external package (npm deps) that the resolver intentionally skips.
749+
// In that case the JS path resolves zero edges too, so the only cost
750+
// is the redundant JS traversal — no correctness impact.
751+
const hasImports = [...ctx.fileSymbols.values()].some((s) => s.imports.length > 0);
752+
if (allEdgeRows.length === beforeLen && hasImports) {
753+
debug('Native buildImportEdges produced 0 edges — falling back to JS');
754+
buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
755+
}
741756
} else {
742757
buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
743758
}

src/features/dataflow.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ function collectNativeEdges(
294294
sourceId: producerNode.id,
295295
targetId: consumerNode.id,
296296
kind: 'returns',
297-
paramIndex: null,
297+
paramIndex: undefined,
298298
expression: assignment.expression,
299299
line: assignment.line,
300300
confidence: 1.0,
@@ -308,7 +308,7 @@ function collectNativeEdges(
308308
sourceId: mutatorNode.id,
309309
targetId: mutatorNode.id,
310310
kind: 'mutates',
311-
paramIndex: null,
311+
paramIndex: undefined,
312312
expression: mut.mutatingExpr,
313313
line: mut.line,
314314
confidence: 1.0,

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2220,7 +2220,7 @@ export interface NativeDatabase {
22202220
sourceId: number;
22212221
targetId: number;
22222222
kind: string;
2223-
paramIndex?: number | null;
2223+
paramIndex?: number;
22242224
expression?: string | null;
22252225
line?: number | null;
22262226
confidence: number;

0 commit comments

Comments
 (0)