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
JavaScript
"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