* Remove statements unreachable from `root` (garbage collection). * Walks the AST graph from root, collecting all referenced statement IDs. * $state variables are always kept (they're referenced at runtime, not by Ref nodes).
( order: string[], merged: Map<string, string>, asts: Map<string, ASTNode>, rootId = "root", )
| 90 | * $state variables are always kept (they're referenced at runtime, not by Ref nodes). |
| 91 | */ |
| 92 | function gcUnreachable( |
| 93 | order: string[], |
| 94 | merged: Map<string, string>, |
| 95 | asts: Map<string, ASTNode>, |
| 96 | rootId = "root", |
| 97 | ): void { |
| 98 | const rootAst = asts.get(rootId); |
| 99 | if (!rootAst) return; // no root → can't GC |
| 100 | |
| 101 | // BFS from root to find all reachable statements |
| 102 | const reachable = new Set<string>([rootId]); |
| 103 | const queue: string[] = [rootId]; |
| 104 | |
| 105 | while (queue.length > 0) { |
| 106 | const id = queue.pop()!; |
| 107 | const ast = asts.get(id); |
| 108 | if (!ast) continue; |
| 109 | |
| 110 | const refs = new Set<string>(); |
| 111 | collectRefs(ast, refs); |
| 112 | |
| 113 | for (const ref of refs) { |
| 114 | if (!reachable.has(ref) && asts.has(ref)) { |
| 115 | reachable.add(ref); |
| 116 | queue.push(ref); |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // Keep $state variables — they're bound at runtime, not via Ref |
| 122 | for (const id of order) { |
| 123 | if (id.startsWith("$")) reachable.add(id); |
| 124 | } |
| 125 | |
| 126 | // Remove unreachable statements |
| 127 | for (let i = order.length - 1; i >= 0; i--) { |
| 128 | if (!reachable.has(order[i])) { |
| 129 | merged.delete(order[i]); |
| 130 | order.splice(i, 1); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Merge an existing program with a patch (partial update). |
no test coverage detected