mergeScratch

tcmts.cli.merge.mergeScratch · #5zzsc3kj · term · runs anywhere

Three-way merge of a definition's text as a scratch file holds it (tcm edit's, without its namespace line): its imports as a set, what either side imports (an import nothing uses is harmless; one missing isn't), and its code by lines (mergeLines), conflicts marked. imports: what it imports, one name an import; stretches: where each side's changes are in body, the code after them.

import { mergeLines } from "#codebase/tcmts/cli/merge";
import type { MarkerLabels, Merged } from "#codebase/tcmts/cli/merge";
import { groupImports } from "#codebase/tcmts/loader";

export function mergeScratch(
  base: string,
  ours: string,
  theirs: string,
  labels?: MarkerLabels,
): Merged {
  const split = (text: string) => {
    const lines = text.split("\n");
    const imports: string[] = [];
    let i = 0;
    for (; i < lines.length; i++) {
      if (lines[i] === "") continue;
      if (!lines[i]!.startsWith("import ")) break;
      let statement = lines[i]!;
      while (!statement.trimEnd().endsWith(";") && i + 1 < lines.length)
        statement += `\n${lines[++i]}`;
      // (one import a name: grouped again below, as `tcm edit` groups them)
      const m = /^import (type )?\{([\s\S]*)\} from ("[^"]*");$/.exec(statement.trim());
      if (!m) imports.push(statement.trim());
      else
        for (const n of m[2]!.split(",").map((s) => s.trim()))
          if (n) imports.push(`import ${m[1] ?? ""}{ ${n} } from ${m[3]};`);
    }
    return { imports, body: lines.slice(i) };
  };
  const [b, o, t] = [base, ours, theirs].map(split);
  const imports = [...new Set([...o!.imports, ...t!.imports])];
  const grouped = groupImports(imports);
  const { lines, ...body } = mergeLines(b!.body, o!.body, t!.body, labels);
  return {
    text: [...grouped, ...(grouped.length ? [""] : []), ...lines].join("\n"),
    body: lines,
    imports,
    ...body,
  };
}

uses

used by