(queries: QueryBuilder)
| 681 | } |
| 682 | |
| 683 | function interfaceOverrideEdges(queries: QueryBuilder): Edge[] { |
| 684 | const edges: Edge[] = []; |
| 685 | const seen = new Set<string>(); |
| 686 | const methodsOf = (classId: string): Node[] => |
| 687 | queries |
| 688 | .getOutgoingEdges(classId, ['contains']) |
| 689 | .map((e) => queries.getNodeById(e.target)) |
| 690 | .filter((n): n is Node => !!n && n.kind === 'method'); |
| 691 | // Concrete-side kinds vary by language: `class` covers Java / Kotlin / |
| 692 | // C# / TS / Swift-classes / Scala-classes; `struct` covers Swift value |
| 693 | // types that conform to protocols. Iterate both. |
| 694 | const concreteKinds = ['class', 'struct'] as const; |
| 695 | for (const kind of concreteKinds) { |
| 696 | for (const cls of queries.getNodesByKind(kind)) { |
| 697 | const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language)); |
| 698 | if (implMethods.length === 0) continue; |
| 699 | for (const sup of queries.getOutgoingEdges(cls.id, ['implements', 'extends'])) { |
| 700 | const base = queries.getNodeById(sup.target); |
| 701 | if (!base || !IFACE_OVERRIDE_LANGS.has(base.language) || base.id === cls.id) continue; |
| 702 | // Group impl methods by name to handle OVERLOADS: an interface `list()` and |
| 703 | // `list(params)` are distinct nodes and a call may resolve to either, so |
| 704 | // link every base overload → every same-name impl overload (keying by name |
| 705 | // alone would drop all but one and miss the resolved overload). |
| 706 | const implByName = new Map<string, Node[]>(); |
| 707 | for (const m of implMethods) { |
| 708 | const arr = implByName.get(m.name); |
| 709 | if (arr) arr.push(m); else implByName.set(m.name, [m]); |
| 710 | } |
| 711 | let added = 0; |
| 712 | for (const bm of methodsOf(base.id)) { |
| 713 | if (added >= MAX_CALLBACKS_PER_CHANNEL) break; |
| 714 | for (const m of implByName.get(bm.name) ?? []) { |
| 715 | if (added >= MAX_CALLBACKS_PER_CHANNEL) break; |
| 716 | if (bm.id === m.id) continue; |
| 717 | const key = `${bm.id}>${m.id}`; |
| 718 | if (seen.has(key)) continue; |
| 719 | seen.add(key); |
| 720 | edges.push({ |
| 721 | source: bm.id, |
| 722 | target: m.id, |
| 723 | kind: 'calls', |
| 724 | line: bm.startLine, |
| 725 | provenance: 'heuristic', |
| 726 | metadata: { synthesizedBy: 'interface-impl', via: m.name, registeredAt: `${m.filePath}:${m.startLine}` }, |
| 727 | }); |
| 728 | added++; |
| 729 | } |
| 730 | } |
| 731 | } |
| 732 | } |
| 733 | } |
| 734 | return edges; |
| 735 | } |
| 736 | |
| 737 | /** |
| 738 | * Go gRPC stub → impl bridge. The protoc-gen-go-grpc codegen emits an |
no test coverage detected