(skipInChain: ((e: Edge) => boolean) | null)
| 2043 | // already shown in the rendered main chain (a 2-node chain renders nothing, so a |
| 2044 | // direct named→named synth hop still surfaces — #687). |
| 2045 | const collectSynthLinks = (skipInChain: ((e: Edge) => boolean) | null): string[] => { |
| 2046 | const synthLines: string[] = []; |
| 2047 | const synthSeen = new Set<string>(); |
| 2048 | for (const n of [...named.values(), ...dynNamed.values()]) { |
| 2049 | if (synthLines.length >= 6) break; |
| 2050 | for (const { node: other, edge } of [...cg.getCallers(n.id), ...cg.getCallees(n.id)]) { |
| 2051 | if (synthLines.length >= 6) break; |
| 2052 | if (edge.provenance !== 'heuristic' || other.id === n.id) continue; |
| 2053 | if (skipInChain && skipInChain(edge)) continue; |
| 2054 | const src = edge.source === n.id ? n : other; |
| 2055 | const tgt = edge.source === n.id ? other : n; |
| 2056 | const key = `${src.name}>${tgt.name}`; |
| 2057 | if (synthSeen.has(key)) continue; |
| 2058 | synthSeen.add(key); |
| 2059 | const note = this.synthEdgeNote(edge); |
| 2060 | synthLines.push(`- ${src.name} → ${tgt.name} [${note ? note.compact : edge.kind}]`); |
| 2061 | } |
| 2062 | } |
| 2063 | return synthLines; |
| 2064 | }; |
| 2065 | if (named.size < 2) { |
| 2066 | // <2 CALLABLES resolved. Two recoveries before giving up: (1) synthesized |
| 2067 | // edges among named CONSTANT/VARIABLE endpoints — RTK thunk→thunk is |
nothing calls this directly
no test coverage detected