UNPKG

@plugjs/plug

Version:
271 lines (270 loc) 10.1 kB
// logging/report.ts import { BuildFailure } from "../asserts.mjs"; import { readFile } from "../fs.mjs"; import { $blu, $cyn, $gry, $plur, $red, $und, $wht, $ylw } from "./colors.mjs"; import { githubAnnotation } from "./github.mjs"; import { ERROR, NOTICE, WARN, logLevels } from "./levels.mjs"; import { logOptions } from "./options.mjs"; var _showSources = logOptions.showSources; var _githubAnnotations = logOptions.githubAnnotations; logOptions.on("changed", (options) => { _showSources = options.showSources; _githubAnnotations = options.githubAnnotations; }); var nul = "\u2400"; var ReportImpl = class { constructor(_title, _task, _emitter) { this._title = _title; this._task = _task; this._emitter = _emitter; } _sources = /* @__PURE__ */ new Map(); _annotations = /* @__PURE__ */ new Map(); _records = /* @__PURE__ */ new Map(); _noticeRecords = 0; _warningRecords = 0; _errorRecords = 0; _noticeAnnotations = 0; _warningAnnotations = 0; _errorAnnotations = 0; get notices() { return this._noticeRecords + this._noticeAnnotations; } get warnings() { return this._warningRecords + this._warningAnnotations; } get errors() { return this._errorRecords + this._errorAnnotations; } get noticeRecords() { return this._noticeRecords; } get warningRecords() { return this._warningRecords; } get errorRecords() { return this._errorRecords; } get noticeAnnotations() { return this._noticeAnnotations; } get warningAnnotations() { return this._warningAnnotations; } get errorAnnotations() { return this._errorAnnotations; } get records() { return this._noticeRecords + this._warningRecords + this._errorRecords; } get annotations() { return this._noticeAnnotations + this._warningAnnotations + this._errorAnnotations; } get empty() { return !(this.records + this.annotations); } annotate(annotationLevel, file, note) { if (note) { const level = annotationLevel; this._annotations.set(file, { level, note }); switch (level) { case NOTICE: this._noticeRecords++; break; case WARN: this._warningRecords++; break; case ERROR: this._errorRecords++; break; } } return this; } add(...records) { for (const record of records) { let messages = Array.isArray(record.message) ? [...record.message.map((msg) => msg.split("\n")).flat(1)] : record.message.split("\n"); messages = messages.filter((message) => !!message); if (!messages.length) { const options = { taskName: this._task, level: ERROR }; this._emitter(options, ["No message for report record"]); throw BuildFailure.fail(); } const level = record.level; const file = record.file; const source = record.source || void 0; const tags = record.tags ? Array.isArray(record.tags) ? [...record.tags] : [record.tags] : []; switch (level) { case NOTICE: this._noticeRecords++; break; case WARN: this._warningRecords++; break; case ERROR: this._errorRecords++; break; } let line = 0; let column = 0; let length = 1; if (file && record.line) { line = record.line; if (record.column) { column = record.column; if (record.length) { length = record.length; if (length < 0) { length = Number.MAX_SAFE_INTEGER; } } } } if (file && source && !this._sources.has(file)) { this._sources.set(file, source.split("\n")); } let reports = this._records.get(file || nul); if (!reports) this._records.set(file || nul, reports = /* @__PURE__ */ new Set()); reports.add({ level, messages, tags, line, column, length }); } return this; } async loadSources() { const promises = []; for (const file of this._records.keys()) { if (!file) continue; if (file === nul) continue; if (this._sources.has(file)) continue; promises.push(readFile(file, "utf-8").then((source) => source.split("\n")).then((lines) => this._sources.set(file, lines))); } await Promise.allSettled(promises); } done(showSources) { if (showSources == null) showSources = _showSources; if (!this.empty) this._emit(showSources); if (this.errors) throw BuildFailure.fail(); } _emit(showSources) { let fPad = 0; let aPad = 0; let mPad = 0; let lPad = 0; let cPad = 0; if (this._annotations.size === 0 && this._records.size === 0) return; const entries = [...this._annotations.keys(), ...this._records.keys()].filter((file, i, a) => a.indexOf(file) === i).sort((a, b) => { return (a || "") < (b || "") ? -1 : (a || "") > (b || "") ? 1 : 0; }).map((file) => { const ann = file && file !== nul && this._annotations.get(file); const records = [...this._records.get(file) || []].sort(({ line: al, column: ac }, { line: bl, column: bc }) => (al || Number.MAX_SAFE_INTEGER) - (bl || Number.MAX_SAFE_INTEGER) || (ac || Number.MAX_SAFE_INTEGER) - (bc || Number.MAX_SAFE_INTEGER)).map((record) => { if (record.line && record.line > lPad) lPad = record.line; if (record.column && record.column > cPad) cPad = record.column; for (const message of record.messages) { if (message.length > mPad) mPad = message.length; } return record; }); if (file && file.length > fPad) fPad = file.length; if (ann && ann.note.length > aPad) aPad = ann.note.length; return { file, records, annotation: ann }; }); mPad = mPad <= 100 ? mPad : 0; lPad = lPad.toString().length; cPad = cPad.toString().length; const options = { taskName: this._task, level: NOTICE }; this._emitter(options, [""]); this._emitter(options, [$und($wht(this._title))]); for (let f = 0; f < entries.length; f++) { const { file, records, annotation } = entries[f]; const source = file && file != nul && this._sources.get(file); if (f === 0 || entries[f - 1]?.records.length) { this._emitter(options, [""]); } if (file && file !== nul && annotation) { const { level, note } = annotation; const $col = level === NOTICE ? $blu : level === WARN ? $ylw : $red; const ann = `${$gry("[")}${$col(note)}${$gry("]")}`; const pad = "".padStart(fPad + aPad - (file.length + note.length)); this._emitter({ ...options, level }, [$wht($und(file)), pad, ann]); } else if (file !== nul) { this._emitter(options, [$wht($und(file))]); } else if (f > 0) { this._emitter(options, [""]); } for (let r = 0; r < records.length; r++) { const { level, messages, tags, line, column, length = 1 } = records[r]; let pfx; if (file && line) { if (column) { pfx = ` ${line.toString().padStart(lPad)}:${column.toString().padEnd(cPad)} `; } else { pfx = ` ${line.toString().padStart(lPad)}:${"-".padEnd(cPad)} `; } } else if (file != nul) { pfx = ` ${"-".padStart(lPad)}:${"-".padEnd(cPad)} `; } else { pfx = " ~ "; } const prefix = "".padStart(pfx.length + 1); const tag = tags.length == 0 ? "" : `${$gry("[")}${tags.map((tag2) => $cyn(tag2)).join($gry("|"))}${$gry("]")}`; if (messages.length === 1) { this._emitter({ ...options, level }, [$gry(pfx), messages[0].padEnd(mPad), tag]); } else { for (let m = 0; m < messages.length; m++) { if (!m) { this._emitter({ ...options, level }, [$gry(pfx), messages[m]]); } else if (m === messages.length - 1) { this._emitter({ ...options, level, prefix }, [messages[m].padEnd(mPad), tag]); } else { this._emitter({ ...options, level, prefix }, [messages[m]]); } } } if (showSources && source && source[line - 1]) { if (column) { const $col = level === NOTICE ? $blu : level === WARN ? $ylw : $red; const offset = column - 1; const text = source[line - 1] || ""; const head = $gry(text.substring(0, offset)); const body = $und($col(text.substring(offset, offset + length))); const tail = $gry(text.substring(offset + length)); this._emitter({ ...options, level, prefix }, [$gry(`| ${head}${body}${tail}`)]); } else { this._emitter({ ...options, level, prefix }, [$gry(`| ${source[line - 1]}`)]); } } } } this._emitter(options, [""]); const status = ["Found"]; if (this.errors) { status.push($plur(this.errors, "error", "errors")); } if (this.warnings) { if (this.errors) status.push("and"); status.push($plur(this.warnings, "warning", "warnings")); } if (this.errors || this.warnings) { this._emitter(options, status); this._emitter(options, [""]); } if (_githubAnnotations) { for (const entry of entries) { const file = entry.file === nul ? void 0 : entry.file; for (const report of entry.records) { const type = report.level === logLevels.ERROR ? "error" : report.level === logLevels.WARN ? "warning" : null; if (!type) continue; const title = `${this._title} (task "${this._task}")`; const col = report.column || void 0; const line = report.line || void 0; const endColumn = report.column ? report.length >= Number.MAX_SAFE_INTEGER ? void 0 : report.column + report.length || void 0 : void 0; const message = report.messages.join("\n"); githubAnnotation({ type, title, file, col, line, endColumn }, message); } } } } }; export { ReportImpl }; //# sourceMappingURL=report.mjs.map