UNPKG

yini-parser

Version:

Readable configuration without YAML foot-guns or JSON noise. The official Node.js parser for YINI config format — An INI-inspired configuration format with clear nesting, explicit types, and predictable parsing.

317 lines (316 loc) 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorDataHandler = void 0; const env_1 = require("../config/env"); const print_1 = require("../utils/print"); const string_1 = require("../utils/string"); // All the issue titles are defined here to get a quick overview of all // titles, and to easier check that all titles match with relation to // the other titles. const issueTitle = [ 'FATAL ERROR!', 'Internal error!', // 'Internal-Error'. 'Syntax error.', // 'Syntax-Error'. 'Syntax warning.', 'Notice:', 'Info:', ]; /** * This class handles all error/notice reporting and processes exit/throwing. */ class ErrorDataHandler { /** '1-Abort-on-Errors' is the default. Below is from the YINI spec: **Abort Sensitivity Levels** while parsing a YINI document: (AKA severity threshold) - Level 0 = ignore errors and try parse anyway (may remap falty key/section names) - Level 1 = abort on errors only - Level 2 = abort even on warnings */ constructor(subjectType, fileName = undefined, bailSensitivityLevel = '1-Abort-on-Errors', isQuiet = false, // Reduce output (show only errors, does not effect warnings and etc. in meta data). isSilent = false, // Suppress all output (even errors, exit code only). isThrowOnError = false) { this.errors = []; this.warnings = []; this.notices = []; this.infos = []; this.numFatalErrors = 0; this.numInternalErrors = 0; this.numSyntaxErrors = 0; this.numSyntaxWarnings = 0; this.numNotices = 0; this.numInfos = 0; this.subjectType = subjectType; this.fileName = fileName; this.persistThreshold = bailSensitivityLevel; this.isQuiet = isQuiet; this.isSilent = isSilent; this.isThrowOnError = isThrowOnError; } makeIssue(line, column, type, message, advice = undefined, hint = undefined) { const issue = { line, column: !column ? undefined : column, typeKey: (0, string_1.toLowerSnakeCase)(type), message, advice: advice || undefined, // Note, this will render ''-values as undfined and omit these in console outputs. hint: hint || undefined, // Note, this will render ''-values as undfined and omit these in console outputs. }; (0, print_1.debugPrint)('issue:'); (0, env_1.isDebug)() && console.log(issue); return issue; } /** * After pushing processing may continue or exit, depending on the error * and/or the bail threshold (that can be optionally set by the user). * * @note This function MIGHT result in a return, throw, or exit depending * on the bail policy (set by the user). * * @param ctx * @param type * @param msgWhat Name of the specific error or what failed. E.g. "Key already exists in this section scope". * @param msgWhy More details and more specific info about the issue/error. * @param msgHint Hint or HUMBLE suggestion on how to fix the issue. */ pushOrBail(ctx, type, msgWhat, msgWhy = '', msgHint = '') { (0, print_1.debugPrint)('-> pushOrBail(..)'); (0, print_1.debugPrint)('ctx.exception?.name =' + ctx?.exception?.name); (0, print_1.debugPrint)('ctx.exception?.message = ' + ctx?.exception?.message); (0, print_1.debugPrint)('exception?.offendingToken = ' + ctx?.exception?.offendingToken); (0, print_1.debugPrint)(); (0, print_1.debugPrint)('ctx.ruleIndex = ' + ctx?.start.channel); (0, print_1.debugPrint)('ctx.ruleIndex = ' + ctx?.ruleIndex); (0, print_1.debugPrint)('ctx.ruleContext = ' + ctx?.ruleContext); (0, print_1.debugPrint)('ctx.stop?.line = ' + ctx?.stop?.line); (0, print_1.debugPrint)('ctx.stop?.column = ' + ctx?.stop?.column); const lineNum = ctx?.start.line || undefined; // Line (1-based). // const startCol: number | undefined = !ctx // ? undefined // : ++ctx.start.column // Column (0-based). // const endCol: number | undefined = !!ctx?.stop?.column // ? ++ctx.stop.column // : undefined // Note: Column (0-based). const startCol = ctx?.start?.column != null ? ctx.start.column + 1 : undefined; // Note: Column (0-based). const endCol = ctx?.stop?.column != null ? ctx.stop.column + 1 : undefined; // Note: Column (0-based). let colNum = startCol || endCol; let msgWhatWithLineNum = msgWhat; if (lineNum && lineNum > 0) { //@todo func that removes possible . at end //msgWhatWithLineNum = // Patch message with the offending line number. msgWhatWithLineNum += ' at line ' + lineNum; if (colNum) { msgWhatWithLineNum += ', column ' + colNum; } if (process.env.NODE_ENV === 'test') { msgWhatWithLineNum += `\nAt line: ${lineNum}, column(s): ${startCol}-${endCol}`; } } (0, print_1.debugPrint)('persistThreshold = ' + this.persistThreshold); (0, print_1.debugPrint)(' lineNum = ' + lineNum); (0, print_1.debugPrint)(' colNum = ' + colNum); (0, print_1.debugPrint)('startCol = ' + startCol); (0, print_1.debugPrint)(' endCol = ' + endCol); (0, print_1.debugPrint)(); const loc = { lineNum: lineNum || 0, // 1-based, if n/a use 0. colNum: colNum || 0, // 1-based, if n/a use 0. }; if (!this.isSilent) { console.log(); // Print an empty line before outputting message. } switch (type) { case 'Internal-Error': this.numInternalErrors++; this.errors.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); this.emitInternalError(loc, msgWhatWithLineNum, msgWhy, msgHint); if (this.persistThreshold === '1-Abort-on-Errors' || this.persistThreshold === '2-Abort-Even-on-Warnings') { if (!this.isThrowOnError) { (0, print_1.debugPrint)('Skipped throwing'); } else { // (?, not if can delete this message now (it may have been superceded), 20250921) In test, throw an error instead of exiting. throw new Error(`Internal-Error: ${msgWhat}`); } } break; case 'Syntax-Error': this.numSyntaxErrors++; this.errors.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); this.emitSyntaxError(loc, msgWhatWithLineNum, msgWhy, msgHint); if (this.persistThreshold === '1-Abort-on-Errors' || this.persistThreshold === '2-Abort-Even-on-Warnings') { if (!this.isThrowOnError) { (0, print_1.debugPrint)('Skipped throwing'); } else { // (?, not if can delete this message now (it may have been superceded), 20250921) In test, throw an error instead of exiting. throw new Error(`Syntax-Error: ${'' + msgWhat}`); } } break; case 'Syntax-Warning': this.numSyntaxWarnings++; this.warnings.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); if (!this.isQuiet) { this.emitSyntaxWarning(loc, msgWhatWithLineNum, msgWhy, msgHint); } if (this.persistThreshold === '2-Abort-Even-on-Warnings') { if (!this.isThrowOnError) { (0, print_1.debugPrint)('Skipped throwing'); } else { // (?, not if can delete this message now (it may have been superceded), 20250921) In test, throw an error instead of exiting. throw new Error(`Syntax-Warning: ${msgWhat}`); } } break; case 'Notice': this.numNotices++; this.notices.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); this.emitNotice(loc, msgWhatWithLineNum, msgWhy, msgHint); break; case 'Info': this.numInfos++; this.infos.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); this.emitInfo(loc, msgWhatWithLineNum, msgWhy, msgHint); break; default: // Unhandled/unknown error type → Fatal. this.numFatalErrors++; this.errors.push(this.makeIssue(lineNum, colNum, type, msgWhat, msgWhy, msgHint)); this.emitFatalError(loc, msgWhatWithLineNum, msgWhy, msgHint); /* "Best practises": - ONLY on I/O failures: file not found, unreadable file, encoding errors. - ONLY on programmer/usage errors: invalid options, conflicting flags. - ONLY on internal faults: invariants broken, unexpected exceptions from dependencies. */ // CANNOT recover fatal errors, will lead to an bail! // In test, throw an error instead of bailing/exiting. // IMPORTANT: Never exit with exit code since this is a library! throw new Error(`Internal-Error: ${msgWhat}`); } } formatSignificantMessageLine(loc, issueTitle) { switch (this.subjectType) { case 'None/Ignore': return issueTitle; case 'File': case 'Inline': { // Construct a full line from several parts. const titlePart = (0, string_1.trimTrailingNonLetters)(issueTitle.trim()); let line = `${titlePart} in `; if (this.subjectType === 'Inline') { line += 'inline YINI content'; } else { line += `${this.fileName}`; } if (loc?.lineNum) { line += `:${loc.lineNum}`; if (loc?.colNum) line += `:${loc.colNum}`; } return line; } } } /* * - error/warning → console.error / console.warn * - notice/info → console.log / console.info */ emitFatalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[0]); if (!this.isSilent) { console.error(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } emitInternalError(loc, msgWhat = 'Something went wrong!', msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[1]); if (!this.isSilent) { console.error(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } emitSyntaxError(loc, msgWhat, msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[2]); if (!this.isSilent) { console.error(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } emitSyntaxWarning(loc, msgWhat, msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[3]); if (!this.isQuiet && !this.isSilent) { console.warn(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } emitNotice(loc, msgWhat, msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[4]); if (!this.isQuiet && !this.isSilent) { console.log(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } emitInfo(loc, msgWhat, msgWhy = '', msgHint = '') { const messageHeader = this.formatSignificantMessageLine(loc, issueTitle[5]); if (!this.isQuiet && !this.isSilent) { console.info(messageHeader); // Print the issue title. msgWhat && console.log(msgWhat); msgWhy && console.log(msgWhy); msgHint && console.log(msgHint); console.log(); // Emit an empty line before outputting message. } } getNumOfAllMessages() { return (this.getNumOfErrors() + this.getNumOfWarnings() + this.getNumOfNotices() + this.getNumOfInfos()); } getNumOfErrors() { return (this.numFatalErrors + this.numInternalErrors + this.numSyntaxErrors); } getNumOfWarnings() { return this.numSyntaxWarnings; } getNumOfNotices() { return this.numNotices; } getNumOfInfos() { return this.numInfos; } getErrors() { return this.errors; } getWarnings() { return this.warnings; } getNotices() { return this.notices; } getInfos() { return this.infos; } } exports.ErrorDataHandler = ErrorDataHandler; //# sourceMappingURL=errorDataHandler.js.map