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.
131 lines
2.8 KiB
JavaScript
131 lines
2.8 KiB
JavaScript
"use strict";
|
|
|
|
// FIXME: Move this into its own package!
|
|
|
|
const execall = require("execall");
|
|
const defaultValue = require("default-value");
|
|
|
|
// I'm so, so sorry...
|
|
let lineRegex = /\s+at\s+(?:(?:((?:[^\[\n])+)\[as ([^\]]+)\] |((?:[^\(\n])+))\(([^\)\n]+)\)|([^\n]+))(?:\n|$)/gm;
|
|
let geckoLineRegex = /^([^@\n]*)@(.+)/gm;
|
|
let numberRegex = /^\d+$/;
|
|
|
|
function maybeTrim(value) {
|
|
if (value != null) {
|
|
let trimmed = value.trim();
|
|
|
|
if (trimmed.length > 0) {
|
|
return trimmed;
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function isLocation(locationString) {
|
|
return (locationString.split(":").length > 2);
|
|
}
|
|
|
|
function splitLocation(locationString) {
|
|
let parts = locationString.split(":");
|
|
|
|
if (parts.length > 2) {
|
|
let pathPartCount = parts.length - 2;
|
|
|
|
return [
|
|
parts.slice(0, pathPartCount).join(":"),
|
|
parts[pathPartCount],
|
|
parts[pathPartCount + 1],
|
|
];
|
|
}
|
|
}
|
|
|
|
function parseLocation(locationString) {
|
|
if (locationString === "<anonymous>") {
|
|
return { anonymous: true };
|
|
} else {
|
|
let parts = splitLocation(locationString);
|
|
|
|
if (parts != null && numberRegex.test(parts[1]) && numberRegex.test(parts[2])) {
|
|
return {
|
|
path: parts[0],
|
|
line: parseInt(parts[1]),
|
|
column: parseInt(parts[2])
|
|
};
|
|
} else {
|
|
throw new Error(`Could not parse location from string: ${locationString}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function extractFramesV8(stack) {
|
|
let matches = execall(lineRegex, stack);
|
|
|
|
if (matches.length > 0) {
|
|
return matches.map((match) => {
|
|
let groups = match.subMatches;
|
|
|
|
return {
|
|
functionName: maybeTrim(defaultValue(groups[0], groups[2])),
|
|
alias: groups[1],
|
|
location: parseLocation(defaultValue(groups[3], groups[4]))
|
|
};
|
|
});
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function extractFramesGecko(stack) {
|
|
let matches = execall(geckoLineRegex, stack);
|
|
|
|
if (matches.length > 0) {
|
|
return matches.map((match) => {
|
|
let groups = match.subMatches;
|
|
|
|
return {
|
|
functionName: maybeTrim(groups[0]),
|
|
location: parseLocation(groups[1])
|
|
};
|
|
});
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function extractFrames(stack) {
|
|
// TODO: Maybe make this code even more cautious, and match each stacktrace line individually, aborting as soon as any one line cannot be parsed?
|
|
return defaultValue(
|
|
extractFramesV8(stack),
|
|
() => extractFramesGecko(stack),
|
|
{ evaluate: true }
|
|
);
|
|
}
|
|
|
|
module.exports = function parseStackTrace(error) {
|
|
let stack = error.stack;
|
|
let lines = stack.split("\n");
|
|
|
|
let firstStackLine = lines
|
|
.map((line) => line.trim())
|
|
.findIndex((line) => isLocation(line));
|
|
|
|
if (firstStackLine !== -1) {
|
|
let cleanStack = lines
|
|
.slice(firstStackLine)
|
|
.join("\n");
|
|
|
|
let extracted = extractFrames(cleanStack);
|
|
|
|
if (extracted != null) {
|
|
return extracted;
|
|
} else {
|
|
throw new Error(`Could not extract stacktrace frames`);
|
|
}
|
|
} else {
|
|
throw new Error(`Could not find a stacktrace frame`);
|
|
}
|
|
};
|