import { Ctx, File } from "./ctx.ts"; import { exhausted } from "./util.ts"; export type Span = { begin: Pos; end: Pos; }; export type Pos = { idx: number; line: number; col: number; }; export const Span = { fromto: ({ begin }: Span, { end }: Span): Span => ({ begin, end }), } as const; export type Report = { severity: "fatal" | "error" | "warning" | "info"; msg: string; file?: File; span?: Span; pos?: Pos; }; export type ReportLocation = | { file: File; span: Span } | { file: File; pos: Pos }; function severityColor( severity: "fatal" | "error" | "warning" | "info", ): string { switch (severity) { case "fatal": case "error": return "red"; case "warning": return "yellow"; case "info": return "blue"; } exhausted(severity); } export function prettyPrintReport(ctx: Ctx, rep: Report) { if (rep.span && rep.span.begin.idx === rep.span.end.idx) { return prettyPrintReport(ctx, { ...rep, span: undefined, pos: rep.span.begin, }); } const { severity, msg } = rep; const sevColor = severityColor(severity); console.error( `%c${severity}%c: %c${msg}`, `color: ${sevColor}; font-weight: bold`, "", "font-weight: bold;", ); if (!rep.file) { return; } const { absPath: path } = ctx.fileInfo(rep.file); const { line, col } = rep.span?.begin ?? rep.pos!; console.error( ` %c--> %c./${path}:${line}:${col}`, "color: cyan", "", ); if (rep.span) { const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n"); spanLines.pop(); if (spanLines.length == 1) { const sqgPad = " ".repeat(rep.span.begin.col - 1); console.error( ` %c|`, "color: cyan", ); console.error( `%c${rep.span.begin.line.toString().padStart(4, " ")}| %c${ spanLines[0] }`, "color: cyan", "", ); console.error( ` %c| %c${sqgPad}${ "~".repeat(rep.span.end.col - rep.span.begin.col + 1) }`, "color: cyan", `color: ${sevColor}; font-weight: bold`, ); console.error(""); // empty newline return; } for (let i = 0; i < spanLines.length; i++) { console.error( `${(rep.span.begin.line + i).toString().padStart(4, " ")}%c| ${ spanLines[i] }`, "color: cyan", ); if (i == 0) { console.error( ` %c| ${" ".repeat(rep.span.begin.col - 1)}%c${ "~".repeat( spanLines[i].length - (rep.span.begin.col - 1), ) }`, "color: cyan", `color: ${sevColor}; font-weight: bold`, ); } else if (i == spanLines.length - 1) { console.error( ` %c| %c${"~".repeat(rep.span.end.col)}\x1b[0m`, "color: cyan", `color: ${sevColor}; font-weight: bold`, ); } else { console.error( ` %c| %c${"~".repeat(spanLines[i].length)}\x1b[0m`, "color: cyan", `color: ${sevColor}; font-weight: bold`, ); } } console.error(""); // empty newline } else if (rep.pos) { console.error( ` %c|`, "color: cyan", ); console.error( `%c${rep.pos.line.toString().padStart(4, " ")}| %c${ ctx.filePosLineText(rep.file, rep.pos) }`, "color: cyan", "", ); console.error( ` %c| %c${" ".repeat(rep.pos.col - 1)}^\x1b[0m`, "color: cyan", `color: ${sevColor}; font-weight: bold`, ); console.error(""); // empty newline } } export function printStackTrace() { class StackTracer extends Error { constructor() { super("StackTracer"); } } try { throw new StackTracer(); } catch (error) { if (!(error instanceof StackTracer)) { throw error; } console.log( error.stack?.replace( "Error: StackTracer", "Stack trace:", ) ?? error, ); } }