You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

112 lines
3.7 KiB
JavaScript

4 years ago
"use strict";
const chalk = require("chalk");
const syncpipe = require("syncpipe");
4 years ago
const { validateArguments } = require("@validatem/core");
const required = require("@validatem/required");
const defaultTo = require("@validatem/default-to");
const isBoolean = require("@validatem/is-boolean");
const oneOf = require("@validatem/one-of");
const stripErrorFromStack = require("./strip-error-from-stack");
const isInstanceOf = require("./is-instance-of");
const getChain = require("../get-chain");
function prefixExtraLines(string, prefix) {
return string
.split("\n")
.map((line, i) => {
if (i > 0) {
return prefix + line;
} else {
return line;
}
})
.join("\n");
}
function formattedErrorHeading(error, colorAll = false, indentation) {
let formattedMessage = syncpipe(error.message, [
(_) => _.trim(),
(_) => (indentation != null)
? prefixExtraLines(_, indentation)
: _
]);
4 years ago
let formattedName = chalk.red(`${chalk.bold(error.name)}:`);
let coloredMessage = (colorAll === true) ? chalk.red(formattedMessage) : formattedMessage;
return `${formattedName} ${coloredMessage}`;
}
function formattedError(error, indentation = "", prefix = "") {
let strippedStack = stripErrorFromStack(error.stack);
let formattedStack = strippedStack.split("\n").map((line) => {
if (line.trim().length === 0) {
return null;
} else if (line[0] === " ") {
return indentation + line;
} else {
return indentation + ` ${line}`;
}
}).filter(line => line != null).join("\n");
let heading = indentation + prefix + formattedErrorHeading(error);
return `${heading}\n${formattedStack}`;
4 years ago
}
module.exports = function renderError(_error, _options) {
let [ error, options ] = validateArguments(arguments, {
error: [ required, isInstanceOf(Error) ],
options: [ defaultTo({}), {
// color: [ oneOf([ true, false, "auto" ]), defaultTo("auto") ], // FIXME: Implement
allStacktraces: [ isBoolean, defaultTo(false) ]
}]
});
let {allStacktraces} = options;
let errors = getChain(error);
if (errors.length === 1) {
// Special case: there's not actually a cause chain, so we should render the error more simply
return formattedError(errors[0]);
} else {
let detailedErrorsToDisplay = (allStacktraces === true) ? errors : errors.slice(-1);
4 years ago
let summary = errors.map((error, i) => {
let prefix = (i > 0) ? "⤷ " : "";
4 years ago
/* IDEA: After every summarized error, add a summarized stacktrace; that is, a stacktrace that only contains the 'user code' entries that might point the developer at the source of the problem. To do that, we should filter out all node_modules and error-chain stuff, as well as internals like timers.js. Then, we should deduplicate lines across stacktraces, and hide the function name if it's boilerplate (eg. Promise.try.then stuff). Try this out with the 'rpm' regex in the CVM smartctl wrapper, as that covers all bases; duplication, stacktraces without user code, etc. */
// let cleanStack = error.stack.split("\n").filter((line) => {
// return (!line.includes("node_modules")
// && !line.includes("node-error-chain")
// && !line.includes("(timers.js")
// && !line.includes("<anonymous>")
// );
// }).join("\n");
4 years ago
// console.log(cleanStack);
return prefix + formattedErrorHeading(error, false, " ");
}).join("\n");
4 years ago
let stacktraces = detailedErrorsToDisplay.map((error, i) => {
let causedByPrefix = (i > 0 ? "Caused by: " : "");
let causedByPadding = (i > 0) ? " " : "";
4 years ago
return formattedError(error, causedByPadding, causedByPrefix);
}).join("\n\n");
4 years ago
let stacktraceSection = (allStacktraces === true)
? `${chalk.cyan("All stacktraces:")}\n\n${stacktraces}`
: `${chalk.cyan("Stacktrace for original error:")}\n\n${stacktraces}`;
4 years ago
return `${summary}\n\n${stacktraceSection}`;
}
4 years ago
};