* The focal symbol's members, in source order, one level of nesting deep. * * A file's outline is file → class → method, so direct children alone would * show a class and nothing inside it. The grandchildren come from ONE batched * `getOutgoingEdgesFrom` over the container children, never a quer
( cg: CodeGraph, focal: Node, containsOut: readonly Edge[], endpoints: Map<string, Node>, overrides?: Map<string, WireOverride> )
| 238 | * `getOutgoingEdgesFrom` over the container children, never a query per child. |
| 239 | */ |
| 240 | function buildMembers( |
| 241 | cg: CodeGraph, |
| 242 | focal: Node, |
| 243 | containsOut: readonly Edge[], |
| 244 | endpoints: Map<string, Node>, |
| 245 | overrides?: Map<string, WireOverride> |
| 246 | ): { items: WireMember[]; total: number } { |
| 247 | const direct: Array<{ node: Node; parentId: string; depth: number }> = []; |
| 248 | for (const edge of containsOut) { |
| 249 | const child = endpoints.get(edge.target); |
| 250 | if (child) direct.push({ node: child, parentId: focal.id, depth: 1 }); |
| 251 | } |
| 252 | |
| 253 | const containerIds = direct |
| 254 | .filter((entry) => CONTAINER_KINDS.has(entry.node.kind)) |
| 255 | .map((entry) => entry.node.id); |
| 256 | |
| 257 | const nested: Array<{ node: Node; parentId: string; depth: number }> = []; |
| 258 | if (containerIds.length > 0) { |
| 259 | const grandEdges = cg.getOutgoingEdgesFrom(containerIds, ['contains']); |
| 260 | const grandNodes = cg.getNodesByIds(grandEdges.map((e) => e.target)); |
| 261 | for (const edge of grandEdges) { |
| 262 | const child = grandNodes.get(edge.target); |
| 263 | if (child) nested.push({ node: child, parentId: edge.source, depth: 2 }); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | const all = [...direct, ...nested].sort( |
| 268 | (a, b) => a.node.startLine - b.node.startLine || a.node.name.localeCompare(b.node.name) |
| 269 | ); |
| 270 | const shown = all.slice(0, MAX_OUTLINE_NODES); |
| 271 | |
| 272 | // Two queries for the whole outline, not two per row: a file with 400 |
| 273 | // symbols would otherwise be 800 lookups behind one screen. |
| 274 | const memberIds = shown.map((entry) => entry.node.id); |
| 275 | const fanIn = cg.getFanIn(memberIds); |
| 276 | const fanOut = cg.getFanOut(memberIds); |
| 277 | |
| 278 | return { |
| 279 | items: shown.map((entry) => { |
| 280 | const member: WireMember = { |
| 281 | ...toNodeRef(entry.node), |
| 282 | parentId: entry.parentId, |
| 283 | depth: entry.depth, |
| 284 | fanIn: fanIn.get(entry.node.id) ?? 0, |
| 285 | fanOut: fanOut.get(entry.node.id) ?? 0, |
| 286 | }; |
| 287 | const override = overrides?.get(entry.node.id); |
| 288 | if (override) member.overrides = override; |
| 289 | return member; |
| 290 | }), |
| 291 | total: all.length, |
| 292 | }; |
| 293 | } |
| 294 | |
| 295 | // ============================================================================= |
| 296 | // Test coverage |
no test coverage detected