(
nodeId: string,
maxDepth: number,
currentDepth: number,
result: Array<{ node: Node; edge: Edge }>,
visited: Set<string>
)
| 268 | } |
| 269 | |
| 270 | private getCallersRecursive( |
| 271 | nodeId: string, |
| 272 | maxDepth: number, |
| 273 | currentDepth: number, |
| 274 | result: Array<{ node: Node; edge: Edge }>, |
| 275 | visited: Set<string> |
| 276 | ): void { |
| 277 | // Mark visited BEFORE the depth check, not after. Folding both into one |
| 278 | // guard meant that when `currentDepth >= maxDepth` fired we returned without |
| 279 | // marking the node — so a caller reachable from the same parent via two |
| 280 | // edges (two call sites, or calls + references) was pushed once per edge, |
| 281 | // duplicating it in `result` at the default `maxDepth=1` (#1086). |
| 282 | if (visited.has(nodeId)) { |
| 283 | return; |
| 284 | } |
| 285 | visited.add(nodeId); |
| 286 | if (currentDepth >= maxDepth) { |
| 287 | return; |
| 288 | } |
| 289 | |
| 290 | // `instantiates` counts as a caller: constructing a class (`Foo(...)` / |
| 291 | // `new Foo()`) is calling its constructor, so the instantiation site is a |
| 292 | // caller of the class. Without it, `callers <Class>` surfaced only the |
| 293 | // importing file (via `imports`) and missed every construction site — |
| 294 | // the opposite of "what breaks if I change this class?" (#774). |
| 295 | const incomingEdges = this.queries.getIncomingEdges(nodeId, ['calls', 'references', 'imports', 'instantiates']); |
| 296 | if (incomingEdges.length === 0) return; |
| 297 | |
| 298 | // Batch-fetch all caller nodes in one round-trip instead of one |
| 299 | // getNodeById per edge (was N+1 — meaningful on functions with many callers). |
| 300 | const sourceIds = incomingEdges.map((e) => e.source); |
| 301 | const callerNodes = this.queries.getNodesByIds(sourceIds); |
| 302 | |
| 303 | for (const edge of incomingEdges) { |
| 304 | const callerNode = callerNodes.get(edge.source); |
| 305 | if (callerNode && !visited.has(callerNode.id)) { |
| 306 | result.push({ node: callerNode, edge }); |
| 307 | this.getCallersRecursive(callerNode.id, maxDepth, currentDepth + 1, result, visited); |
| 308 | } |
| 309 | } |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * Find all functions/methods called by a function |
no test coverage detected