( cg: CodeGraph, nodes: readonly Node[] )
| 178 | * has to print — see `wireList`. |
| 179 | */ |
| 180 | export function buildOutlineEntries( |
| 181 | cg: CodeGraph, |
| 182 | nodes: readonly Node[] |
| 183 | ): { entries: WireOutlineEntry[]; total: number } { |
| 184 | const nodeIds = nodes.map((n) => n.id); |
| 185 | const inThisFile = new Set(nodeIds); |
| 186 | const fileNodeId = nodes.find((n) => n.kind === 'file')?.id; |
| 187 | |
| 188 | const parentOf = new Map<string, string>(); |
| 189 | for (const edge of cg.getOutgoingEdgesFrom(nodeIds, ['contains'])) { |
| 190 | // Only nesting *within* this file: a `contains` edge reaching out of it is |
| 191 | // not something a file outline can draw. |
| 192 | if (inThisFile.has(edge.target) && !parentOf.has(edge.target)) { |
| 193 | parentOf.set(edge.target, edge.source); |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | const fanIn = cg.getFanIn(nodeIds); |
| 198 | const fanOut = cg.getFanOut(nodeIds); |
| 199 | |
| 200 | const outlineNodes = nodes |
| 201 | // The file node is the subject of the screen, not a row in its own outline; |
| 202 | // import declarations get their own rail and would otherwise be most of it. |
| 203 | .filter((n) => n.kind !== 'file' && n.kind !== 'import') |
| 204 | .sort((a, b) => a.startLine - b.startLine || a.name.localeCompare(b.name)); |
| 205 | |
| 206 | const entries: WireOutlineEntry[] = outlineNodes.slice(0, MAX_OUTLINE_NODES).map((node) => ({ |
| 207 | ...toNodeRef(node), |
| 208 | parentId: resolveOutlineParent(node.id, parentOf, fileNodeId), |
| 209 | depth: depthOf(node.id, parentOf, fileNodeId), |
| 210 | fanIn: fanIn.get(node.id) ?? 0, |
| 211 | fanOut: fanOut.get(node.id) ?? 0, |
| 212 | })); |
| 213 | |
| 214 | return { entries, total: outlineNodes.length }; |
| 215 | } |
| 216 | |
| 217 | /** |
| 218 | * The outline parent of a symbol: its container within the file, or null when |
no test coverage detected