File: src/graph/traversal.ts
Lines: 246–249 (getCallersRecursive), 296–299 (getCalleesRecursive)
Defect: The depth guard (if (currentDepth >= maxDepth || visited.has(nodeId)) return;) at L246/L296 returns without executing visited.add(nodeId) at L249/L299, so when currentDepth >= maxDepth fires the caller/callee node is never marked visited; a node reachable via multiple edges from the same parent is then pushed to result once per edge.
Failure trace:
- Function
X has two calls edges from Y (e.g., Y calls X twice in different expressions).
getCallersRecursive("X", 1, 0, result, visited): depth 0 < maxDepth 1 → continue; visited.add("X").
- Push loop, edge e1 (
Y→X): !visited.has("Y") = true → result.push({Y,e1}); recurse ("Y", 1, 1, …) → depth 1 ≥ maxDepth 1 → return without visited.add("Y").
- Push loop, edge e2 (
Y→X): !visited.has("Y") = true (Y was never added) → result.push({Y,e2}) → duplicate.
- With default
maxDepth=1 (as used in the public getCallers/getCallees at src/index.ts) any function called by the same caller via two edges produces a duplicate entry.
Expected fix:
Move visited.add(nodeId) to immediately after the visited.has(nodeId) arm of the guard and before the depth arm fires — or, equivalently, split the guard into two separate checks and add to visited unconditionally after the visited.has check:
if (visited.has(nodeId)) return;
visited.add(nodeId); // mark before depth check
if (currentDepth >= maxDepth) return;
Alternatively, add visited.add(callerNode.id) (resp. calleeNode.id) in the push loop before the recursive call, to prevent the same node being pushed more than once per result set.
File:
src/graph/traversal.tsLines: 246–249 (
getCallersRecursive), 296–299 (getCalleesRecursive)Defect: The depth guard (
if (currentDepth >= maxDepth || visited.has(nodeId)) return;) at L246/L296 returns without executingvisited.add(nodeId)at L249/L299, so whencurrentDepth >= maxDepthfires the caller/callee node is never marked visited; a node reachable via multiple edges from the same parent is then pushed toresultonce per edge.Failure trace:
Xhas twocallsedges fromY(e.g.,YcallsXtwice in different expressions).getCallersRecursive("X", 1, 0, result, visited): depth 0 < maxDepth 1 → continue;visited.add("X").Y→X):!visited.has("Y")= true →result.push({Y,e1}); recurse("Y", 1, 1, …)→ depth 1 ≥ maxDepth 1 → return withoutvisited.add("Y").Y→X):!visited.has("Y")= true (Y was never added) →result.push({Y,e2})→ duplicate.maxDepth=1(as used in the publicgetCallers/getCalleesatsrc/index.ts) any function called by the same caller via two edges produces a duplicate entry.Expected fix:
Move
visited.add(nodeId)to immediately after thevisited.has(nodeId)arm of the guard and before the depth arm fires — or, equivalently, split the guard into two separate checks and add tovisitedunconditionally after thevisited.hascheck:Alternatively, add
visited.add(callerNode.id)(resp.calleeNode.id) in the push loop before the recursive call, to prevent the same node being pushed more than once per result set.