* Every outgoing edge from every symbol in the file, grouped twice over: by the * symbol that makes the call, and within that by the symbol it reaches. * * Grouping by the OWNER as well as the target is what separates this from the * Symbol view's rail. Across one body, a helper called from thre
( cg: CodeGraph, nodes: readonly Node[], posixPath: string )
| 202 | * and the outline already draws it. |
| 203 | */ |
| 204 | function buildCalls( |
| 205 | cg: CodeGraph, |
| 206 | nodes: readonly Node[], |
| 207 | posixPath: string |
| 208 | ): { calls: WireFileCall[]; total: number; intraFileCalls: number } { |
| 209 | const nodeIds = nodes.map((n) => n.id); |
| 210 | const lineOf = new Map(nodes.map((n) => [n.id, n.startLine] as const)); |
| 211 | |
| 212 | const edges = cg.getOutgoingEdgesFrom(nodeIds).filter((e) => e.kind !== 'contains'); |
| 213 | if (edges.length === 0) return { calls: [], total: 0, intraFileCalls: 0 }; |
| 214 | |
| 215 | const bySource = new Map<string, Edge[]>(); |
| 216 | for (const edge of edges) { |
| 217 | const bucket = bySource.get(edge.source); |
| 218 | if (bucket) bucket.push(edge); |
| 219 | else bySource.set(edge.source, [edge]); |
| 220 | } |
| 221 | |
| 222 | // One batched lookup for every counterpart, never one per edge: the engine's |
| 223 | // busiest file reaches several hundred distinct symbols. |
| 224 | const endpoints = cg.getNodesByIds([...new Set(edges.map((e) => e.target))]); |
| 225 | |
| 226 | const all: WireFileCall[] = []; |
| 227 | for (const [ownerId, group] of bySource) { |
| 228 | for (const relation of groupRelations(group, (e) => e.target, endpoints)) { |
| 229 | all.push({ ownerId, ownerLine: lineOf.get(ownerId) ?? 0, relation }); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | // Source order — the only ordering this screen has. A row with no recorded |
| 234 | // call site (an edge the extractor gave no line) sorts to the end, where it |
| 235 | // is also what a cap trims first. |
| 236 | all.sort( |
| 237 | (a, b) => |
| 238 | firstLine(a.relation) - firstLine(b.relation) || |
| 239 | a.ownerLine - b.ownerLine || |
| 240 | a.relation.node.name.localeCompare(b.relation.node.name) |
| 241 | ); |
| 242 | |
| 243 | const calls = all.slice(0, MAX_FILE_CALL_GROUPS); |
| 244 | |
| 245 | // Arcs, counted over what was KEPT — see the module comment. |
| 246 | let intraFileCalls = 0; |
| 247 | for (const call of calls) { |
| 248 | if (call.relation.node.file !== posixPath) continue; |
| 249 | const target = call.relation.node.line; |
| 250 | for (const line of call.relation.lines) if (line !== target) intraFileCalls++; |
| 251 | } |
| 252 | |
| 253 | return { calls, total: all.length, intraFileCalls }; |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * The file's unresolved references, as line markers. |
no test coverage detected