diffReport
uses tcmts.cli.files.fileContent, which uses Buffer
A diff as lines of text: what tcm diff prints and the review page shows.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { isEmpty, lineDiff, namesIn } from "#codebase/tcmts/cli/diff"; import type { NamespaceDiff, NameState, ReportLine } from "#codebase/tcmts/cli/diff"; import { fileContent, formatOf, isBytes } from "#codebase/tcmts/cli/files"; import { shortHash } from "#codebase/tcmts/core/hash"; import { render } from "#codebase/tcmts/loader"; export function diffReport( cb: Codebase, d: NamespaceDiff, before: NameState, after: NameState, title: string, ): ReportLine[] { const out: ReportLine[] = [[["bold", title]]]; if (isEmpty(d)) return [...out, [["dim", " no changes"]]]; const src = cb.source(); const [oldNames, newNames] = [namesIn(before), namesIn(after)]; const body = (hash: string, nameOf: (h: string) => string | undefined, rec = src.get(hash)!) => { // a file is its text (links naming what they reach now), or its size if it's bytes if (rec.kind === "file") { const at = nameOf(hash) ?? ""; const content = fileContent(cb, rec, at.slice(0, Math.max(0, at.lastIndexOf(".")))); return isBytes(rec) ? [`(${formatOf(rec) || "bytes"}, ${content.length} bytes)`] : content.toString("utf8").trimEnd().split("\n"); } return render([rec], src, cb.lay, { style: "scratch", omitImports: true, nameOf, exportDeclarations: true, }) .text.trimEnd() .split("\n"); }; const name = (hash: string) => newNames(hash) ?? shortHash(hash); const section = (label: string, n: number) => n && out.push( [], [ ["plain", " "], ["dim", label], ], ); const changedLines = (lines: ReturnType<typeof lineDiff>) => lines.forEach(([op, line], k) => { // keep two lines of context around changes in long definitions const near = lines.slice(Math.max(0, k - 2), k + 3).some(([o]) => o !== " "); if (op === " " && !near && lines.length > 12) { if (lines[k - 1]?.[0] !== " " || k === 0) out.push([["dim", " ⋮"]]); return; } out.push([ op === "-" ? ["red", ` - ${line}`] : op === "+" ? ["green", ` + ${line}`] : ["dim", ` ${line}`], ]); }); section("edited", d.edited.length); for (const c of d.edited) { out.push([ ["plain", " "], ["yellow", "~"], ["plain", " "], ["bold", c.path], ["plain", " "], ["dim", `${shortHash(c.old)} → ${shortHash(c.new)}`], ]); changedLines(lineDiff(body(c.old, oldNames), body(c.new, newNames))); } section("comments, layout or doc changed (the code is the same)", d.reworded.length); for (const r of d.reworded) { out.push([ ["plain", " "], ["yellow", "~"], ["plain", " "], ["bold", r.path], ["plain", " "], ["dim", shortHash(r.hash)], ]); const as = (text: string | undefined) => cb.store.defWithText(r.hash, text)!; changedLines(lineDiff(body(r.hash, oldNames, as(r.old)), body(r.hash, newNames, as(r.new)))); } section("updated because a dependency changed (code unchanged)", d.propagated.length); for (const c of d.propagated) out.push([ ["plain", " "], ["yellow", "~"], ["plain", ` ${c.path} `], ["dim", `${shortHash(c.old)} → ${shortHash(c.new)} via ${c.because.map(name).join(", ")}`], ]); section("added", d.added.length); for (const a of d.added) out.push([ ["plain", " "], ["green", "+"], ["plain", " "], ["bold", a.path], ["plain", " "], ["dim", `${shortHash(a.hash)} : ${src.get(a.hash)!.signature}`], ]); section("removed", d.removed.length); for (const r of d.removed) out.push([ ["plain", " "], ["red", "-"], ["plain", ` ${r.path} `], ["dim", shortHash(r.hash)], ]); section("renamed", d.renamed.length); for (const r of d.renamed) out.push([ ["plain", " "], ["cyan", ">"], ["plain", ` ${r.from} → `], ["bold", r.to], ["plain", " "], ["dim", shortHash(r.hash)], ]); section("aliased", d.aliased.length); for (const a of d.aliased) out.push([ ["plain", " "], ["cyan", "&"], ["plain", " "], ["bold", a.path], ["plain", " "], ["dim", `same as ${a.existing.join(", ")}`], ]); section("conflicted", d.conflicted.length); for (const c of d.conflicted) out.push([ ["plain", " "], ["red", "!"], ["plain", " "], ["bold", c.path], ["plain", " "], ["dim", c.hashes.map((h) => shortHash(h)).join(", ")], ]); return out; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.diff.NameState
- tcmts.cli.diff.NamespaceDiff
- tcmts.cli.diff.ReportLine
- tcmts.cli.diff.isEmpty
- tcmts.cli.diff.lineDiff
- tcmts.cli.diff.namesIn
- tcmts.cli.files.fileContent
- tcmts.cli.files.formatOf
- tcmts.cli.files.isBytes
- tcmts.core.hash.shortHash
- tcmts.loader.render