MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / getImpactRecursive

Method getImpactRecursive

src/graph/traversal.ts:543–607  ·  view source on GitHub ↗
(
    nodeId: string,
    maxDepth: number,
    currentDepth: number,
    nodes: Map<string, Node>,
    edges: Edge[],
    visited: Set<string>
  )

Source from the content-addressed store, hash-verified

541 }
542
543 private getImpactRecursive(
544 nodeId: string,
545 maxDepth: number,
546 currentDepth: number,
547 nodes: Map<string, Node>,
548 edges: Edge[],
549 visited: Set<string>
550 ): void {
551 // Mark visited before the depth check so a node collected at the depth
552 // boundary still lands in `visited`. Otherwise it could sit in `nodes` but
553 // not `visited`, and the two loops below — which used different sets to
554 // gate re-processing — would disagree about it (#1089).
555 if (visited.has(nodeId)) {
556 return;
557 }
558 visited.add(nodeId);
559 if (currentDepth >= maxDepth) {
560 return;
561 }
562
563 // For container nodes (classes, interfaces, structs, etc.), also traverse
564 // into their children so that callers of contained methods appear in impact
565 const focalNode = this.queries.getNodeById(nodeId);
566 if (focalNode) {
567 const containerKinds = new Set(['class', 'interface', 'struct', 'trait', 'protocol', 'module', 'enum']);
568 if (containerKinds.has(focalNode.kind)) {
569 const containsEdges = this.queries.getOutgoingEdges(nodeId, ['contains']);
570 if (containsEdges.length > 0) {
571 const children = this.queries.getNodesByIds(containsEdges.map((e) => e.target));
572 for (const edge of containsEdges) {
573 const childNode = children.get(edge.target);
574 if (childNode && !visited.has(childNode.id)) {
575 nodes.set(childNode.id, childNode);
576 edges.push(edge);
577 // Recurse into children at the same depth (they're part of the same symbol)
578 this.getImpactRecursive(childNode.id, maxDepth, currentDepth, nodes, edges, visited);
579 }
580 }
581 }
582 }
583 }
584
585 // Get all incoming edges (things that depend on this node). Exclude
586 // `contains`: a container "contains" its members but does not *depend* on
587 // them, so following it upward would climb to the parent class and then
588 // re-expand every sibling member — exploding impact for a leaf symbol. (#536)
589 const incomingEdges = this.queries.getIncomingEdges(nodeId).filter((e) => e.kind !== 'contains');
590 if (incomingEdges.length === 0) return;
591 const sources = this.queries.getNodesByIds(incomingEdges.map((e) => e.source));
592
593 for (const edge of incomingEdges) {
594 const sourceNode = sources.get(edge.source);
595 if (!sourceNode) continue;
596 // Record the dependency edge unconditionally. The gate used to also gate
597 // edge collection (`!nodes.has(...)`), so a second incoming edge into a
598 // node already collected via another path was silently dropped from
599 // `edges` even though it's a real dependency (#1089). Each node's incoming
600 // edges are fetched once (nodes are expanded once), so no edge repeats.

Callers 1

getImpactRadiusMethod · 0.95

Calls 7

hasMethod · 0.80
getNodesByIdsMethod · 0.80
getNodeByIdMethod · 0.65
getMethod · 0.65
getOutgoingEdgesMethod · 0.45
setMethod · 0.45
getIncomingEdgesMethod · 0.45

Tested by

no test coverage detected