UNPKG

@dev-build-deploy/diagnose-it

Version:
332 lines (331 loc) 13.1 kB
"use strict"; /* * SPDX-FileCopyrightText: 2023 Kevin de Jong <monkaii@hotmail.com> * SPDX-License-Identifier: MIT */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiagnosticsMessage = exports.DiagnosticsLevelEnum = void 0; const chalk_1 = __importDefault(require("chalk")); const fixitHint_1 = require("./fixitHint"); const diff_1 = require("./diff"); /** * Diagnostics Message Level * @enum {number} * * @example * ```typescript * const level = DiagnosticsLevelEnum.Error; * console.log(level); // 4 * console.log(DiagnosticsLevelEnum[level]); // Error * ``` */ var DiagnosticsLevelEnum; (function (DiagnosticsLevelEnum) { DiagnosticsLevelEnum[DiagnosticsLevelEnum["Ignored"] = 0] = "Ignored"; DiagnosticsLevelEnum[DiagnosticsLevelEnum["Note"] = 1] = "Note"; DiagnosticsLevelEnum[DiagnosticsLevelEnum["Remark"] = 2] = "Remark"; DiagnosticsLevelEnum[DiagnosticsLevelEnum["Warning"] = 3] = "Warning"; DiagnosticsLevelEnum[DiagnosticsLevelEnum["Error"] = 4] = "Error"; DiagnosticsLevelEnum[DiagnosticsLevelEnum["Fatal"] = 5] = "Fatal"; })(DiagnosticsLevelEnum || (exports.DiagnosticsLevelEnum = DiagnosticsLevelEnum = {})); /** * Diagnostics Message Level Configuration * @type {DiagnosticsLevelType} * @property {string} text Textual representation of the level * @property {chalk.Chalk} color Chalk color for the level * * @example * ```typescript * const level = DiagnosticsLevelConfiguration[DiagnosticsLevelEnum.Error]; * console.log(level.text); // error * console.log(level.color("Hello")); // Hello in red * ``` */ const DiagnosticsLevelConfiguration = { [DiagnosticsLevelEnum.Ignored]: { text: "ignored", color: chalk_1.default.bold.gray, }, [DiagnosticsLevelEnum.Note]: { text: "note", color: chalk_1.default.bold.blue, }, [DiagnosticsLevelEnum.Remark]: { text: "remark", color: chalk_1.default.bold.blue, }, [DiagnosticsLevelEnum.Warning]: { text: "warning", color: chalk_1.default.bold.yellow, }, [DiagnosticsLevelEnum.Error]: { text: "error", color: chalk_1.default.bold.red, }, [DiagnosticsLevelEnum.Fatal]: { text: "fatal", color: chalk_1.default.bold.red, }, }; /** * Diagnostics Message * @static createError Creates an error message * @static createWarning Creates a warning message * @function setFile Sets the file name * @function setLevel Sets the message level * @function setMessage Sets the message * @function setContext Sets the context * @function addFixitHint Adds a FixIt hint * @function toString Returns the message as a string * * @example Using the constructor * ```typescript * const message = new DiagnosticsMessage({ * file: "test.ts", * level: DiagnosticsLevelEnum.Error, * message: { * text: "Unknown property `foo`", * linenumber: 3, * column: 10 * } * }); * ``` * * @example Using the static methods * ```typescript * const message = DiagnosticsMessage.createError("test.ts", { * text: "Unknown property `foo`", * linenumber: 3, * column: 10 * }); * ``` * * @example Creating a message with a context and FixIt Hints * ```typescript * const message = DiagnosticsMessage.createError("test.ts", { * text: "Unknown property `foo`", * linenumber: 3, * column: 1 * }) * .setContext(2, ["monkey: banana", "foo: foo", "elephant: trunk"]) * .addFixitHint(FixItHint.createInsertion(1, "bar")); * ``` */ class DiagnosticsMessage { constructor(data) { var _a; this.fixitHints = new Array(); this.setFile(data.file); this.setLevel((_a = data.level) !== null && _a !== void 0 ? _a : DiagnosticsLevelEnum.Note); this.setMessage(data.message); } /** * Creates a Diagnostics message with the level `Error` * @param file File path * @param message Error message * @returns Diagnostics Message */ static createError(file, message) { return new DiagnosticsMessage({ file, level: DiagnosticsLevelEnum.Error, message }); } /** * Creates a Diagnostics message with the level `Warning` * @param file File path * @param message Warning message * @returns Diagnostics Message */ static createWarning(file, message) { return new DiagnosticsMessage({ file, level: DiagnosticsLevelEnum.Warning, message }); } /** * Sets the path towards the file associated with the Diagnostics Message. * * Notes: * - The file path cannot contain newlines. * - All special characters in filenames ( $ # & * ? ; | < > ( ) { } [ ] ' " ` ~ ! \ ) will be escaped. * @param file File path * @returns this */ setFile(file) { if (/\r?\n/.test(file)) throw new Error("File name cannot contain newlines."); // Escape all special characters (which are not yet escaped with the prefix `\`) in file names file = file.replace(/\\([$#&*?;|<>(){}[\]'"`~!\\ ])/gi, "$1"); file = file.replace(/[$#&*?;|<>(){}[\]'"`~!\\ ]/g, "\\$&"); this.file = file; return this; } /** * Sets the level of the Diagnostics Message (i.e. `Error`, `Warning`, ...) * @param level Diagnostics Level * @returns this */ setLevel(level) { this.level = level; return this; } /** * Sets the message of the Diagnostics Message. * * Notes: * * The message cannot contain newlines. * * The column number must be greater than 0. * * The line number must be greater than 0. * * @param message Diagnostics Message * @returns this */ setMessage(message) { var _a, _b; if (/\r?\n/.test(message.text)) throw new Error("Message cannot contain newlines."); if (((_a = message.column) !== null && _a !== void 0 ? _a : 1) <= 0) throw new RangeError("Column number must be greater than 0."); if (((_b = message.linenumber) !== null && _b !== void 0 ? _b : 1) <= 0) throw new RangeError("Line number must be greater than 0."); this.message = message; return this; } /** * Sets the context of the Diagnostics Message. * * Notes: * * The context can be provided as an array of strings or a single string. * Any single string with newlines will be split into multiple lines. * * The starting line number must be greater than 0. * * The line number associated with the message must be within the range of the context. * * @param linenumber Line number for the first line provided by `context` * @param lines Context lines * @returns this */ setContext(linenumber, lines) { var _a, _b; if (((_a = this.message.linenumber) !== null && _a !== void 0 ? _a : 1) < linenumber || ((_b = this.message.linenumber) !== null && _b !== void 0 ? _b : 1) > linenumber + lines.length - 1) { throw new RangeError("The line numbers provided by the context do not match the line number associated with the message."); } if (linenumber <= 0) throw new RangeError("Line number must be greater than 0."); const contextLines = Array.isArray(lines) ? lines : lines.split(/\r?\n/); this.context = { linenumber, lines: contextLines }; return this; } /** * Adds a FixIt Hint to the Diagnostics Message. * * Notes: * * You must first provide a context before adding a FixIt Hint. * * The FixIt Hint cannot overlap with an existing FixIt Hint. * * @param hint FixIt Hint * @returns this * * @example Adding a FixIt Hint * ```typescript * const message = DiagnosticsMessage.createError("test.ts", { * text: "Unknown property `foo`", * linenumber: 3, * column: 1 * }) * .setContext(2, ["monkey: banana", "foo: foo", "elephant: trunk"]) * .addFixitHint(FixItHint.createInsertion(1, "bar")); * ``` */ addFixitHint(hint) { if (this.context === undefined) throw new Error("Cannot add FixIt hint without a context."); // Check if the new hint does not overlap with the ranges in the current fixit hints if (this.fixitHints.some(fixit => fixit.range.index <= hint.range.index && hint.range.index <= fixit.range.index + fixit.range.length)) { throw new Error("Cannot add FixIt hint that overlaps with an existing FixIt hint."); } // Add the new hint this.fixitHints.push(hint); // Sort the FixIt hints by index this.fixitHints = this.fixitHints.sort((a, b) => a.range.index - b.range.index); return this; } /** * Returns the FixIt Hints associated with the Diagnostics Message. * @returns Array of FixIt Hints */ getFixitHints() { return this.fixitHints; } getFixitHintsString(prefix) { var _a, _b; const elements = new Array(); if (this.message.column === undefined) return elements; let caretLine = ""; let hintLine = ""; // Determine the maximum width of the first line based on the FixIt Hints and the message const lastFixitHint = this.fixitHints.length > 0 ? this.fixitHints[this.fixitHints.length - 1] : undefined; const maxWidth = lastFixitHint !== undefined ? lastFixitHint.range.index + Math.max(lastFixitHint.range.length, (_b = (_a = lastFixitHint.text) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) : this.message.text.length; // Append each character (either caret or fixit-hint) line by line for (let i = 1; i <= maxWidth; i++) { const fixit = this.fixitHints.find(fixit => i >= fixit.range.index && i < fixit.range.index + fixit.range.length); const fixitHint = this.fixitHints.find(hint => { var _a, _b; return i >= hint.range.index && i < hint.range.index + ((_b = (_a = hint.text) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); }); const char = this.message.column === i && (fixit === null || fixit === void 0 ? void 0 : fixit.modification) !== "DEFAULT" ? "^" : fixit !== undefined ? "~" : " "; const color = fixit !== undefined ? fixitHint_1.ModificationColorCodes[fixit.modification] : chalk_1.default.bold.green; caretLine += char !== " " ? color(char) : char; if (fixitHint !== undefined && fixitHint.text !== undefined) { hintLine += fixitHint.text.charAt(i - fixitHint.range.index); } else { hintLine += " "; } } return hintLine.trimEnd().length === 0 ? [prefix + caretLine.trimEnd()] : [prefix + caretLine.trimEnd(), prefix + hintLine.trimEnd()]; } getContextString() { const result = new Array(); if (this.context === undefined) { return result; } result.push(""); const maxWidth = (this.context.linenumber + this.context.lines.length).toString().length; const spacing = " ".repeat(2); const seperator = " | "; for (let i = 0; i < this.context.lines.length; i++) { const isContextLine = this.context.linenumber + i === this.message.linenumber; const lineColor = isContextLine ? chalk_1.default.bold.whiteBright : chalk_1.default.bold.gray; const line = this.context.lines[i]; const linenumber = (this.context.linenumber + i).toString().padStart(maxWidth, " "); result.push(lineColor(`${spacing}${linenumber}${seperator}${line}`)); if (this.context.linenumber + i === this.message.linenumber) { result.push(...this.getFixitHintsString(`${spacing}${" ".repeat(maxWidth)}${seperator}`)); } } return result; } /** * Generates an Expressive Diagnostics message in formatted output. * @returns Formatted message */ toString() { var _a, _b; const level = DiagnosticsLevelConfiguration[this.level]; const message = [ chalk_1.default.bold(`${this.file}:${(_a = this.message.linenumber) !== null && _a !== void 0 ? _a : 1}:${(_b = this.message.column) !== null && _b !== void 0 ? _b : 1}: `) + level.color(`${level.text}: `) + chalk_1.default.bold(this.message.text), ...this.getContextString(), ]; return message.join("\n"); } /** * Applies the FixIt hints to the context and returns the result as a string. * @returns Fixed string */ applyFixitHints() { return (0, diff_1.applyPatch)((0, diff_1.createPatch)(this)); } } exports.DiagnosticsMessage = DiagnosticsMessage;