(existing: string, patch: string, rootId = "root")
| 139 | * Returns the merged program as a string. |
| 140 | */ |
| 141 | export function mergeStatements(existing: string, patch: string, rootId = "root"): string { |
| 142 | const existingStmts = parseStatements(existing); |
| 143 | const patchStmts = parseStatements(stripFences(patch)); |
| 144 | |
| 145 | if (!existingStmts.length) { |
| 146 | return patchStmts.map((stmt) => stmt.raw).join("\n"); |
| 147 | } |
| 148 | if (!patchStmts.length) return existing; |
| 149 | |
| 150 | // Merge: patch statements override existing by name |
| 151 | const merged = new Map<string, string>(); |
| 152 | const asts = new Map<string, ASTNode>(); |
| 153 | const order: string[] = []; |
| 154 | |
| 155 | for (const stmt of existingStmts) { |
| 156 | merged.set(stmt.id, stmt.raw); |
| 157 | asts.set(stmt.id, stmt.ast); |
| 158 | order.push(stmt.id); |
| 159 | } |
| 160 | |
| 161 | for (const stmt of patchStmts) { |
| 162 | if (stmt.ast.k === "Null") { |
| 163 | // `name = null` in a patch means "delete this statement" |
| 164 | merged.delete(stmt.id); |
| 165 | asts.delete(stmt.id); |
| 166 | const idx = order.indexOf(stmt.id); |
| 167 | if (idx !== -1) order.splice(idx, 1); |
| 168 | continue; |
| 169 | } |
| 170 | if (!merged.has(stmt.id)) { |
| 171 | order.push(stmt.id); |
| 172 | } |
| 173 | merged.set(stmt.id, stmt.raw); |
| 174 | asts.set(stmt.id, stmt.ast); |
| 175 | } |
| 176 | |
| 177 | // GC: remove statements unreachable from root |
| 178 | gcUnreachable(order, merged, asts, rootId); |
| 179 | |
| 180 | return order |
| 181 | .filter((id) => merged.has(id)) |
| 182 | .map((id) => merged.get(id)!) |
| 183 | .join("\n"); |
| 184 | } |
no test coverage detected