locationRewriter
uses node:path
A rewriter of what's printed while a file that's gone afterwards (a patch's) is checked and saved: each place in it
(patch-123.ts:51:44, as the checker and linter say, its path whole or relative, spaces and all) said as where it is
in a definition (tcmts.lib.f:12:44, the line as tcm view numbers it), and the file itself as the names it holds.
places are the definitions' lines in the file (from 0), with their first line's in the view (-1: not known).
import fs from "node:fs"; import path from "node:path"; export function locationRewriter( file: string, places: { name: string; line: number; end: number; viewAt: number }[], names: string[], ): (s: string) => string { const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const base = path.basename(file); // the file as it may be written: whole, or from here, through a link or by its real path, with either slash let real = file; try { real = path.join(fs.realpathSync(path.dirname(file)), base); } catch {} const forms = new Set<string>(); for (const f of [file, real]) for (const g of [f, path.relative(process.cwd(), f)]) { forms.add(g); forms.add(g.split(path.sep).join("/")); } const where = `(?:${[ ...[...forms] .filter((f) => f && f !== base) .sort((a, b) => b.length - a.length) .map(esc), `(?:[^\\s:'"]*[\\\\/])?${esc(base)}`, ].join("|")})`; // (the checker's colours can come between the parts) const ansi = "(?:\\x1b\\[[0-9;]*m)*"; return (s) => s .replace( new RegExp(`${where}${ansi}:${ansi}(\\d+)${ansi}:${ansi}(\\d+)`, "g"), (whole, l: string, c: string) => { const at = Number(l) - 1; const d = places.find((x) => at >= x.line && at <= x.end); if (!d) return whole; return `${d.name}:${d.viewAt >= 0 ? d.viewAt + (at - d.line) + 1 : at - d.line + 1}:${c}`; }, ) .replace(new RegExp(where, "g"), names.join(", ")); }