docschema
Version:
Schema declaration and validation library using JsDoc comments
117 lines (100 loc) • 2.46 kB
JavaScript
/**
* @see https://www.digitalocean.com/community/tutorials/js-stack-trace
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack
*
* @type {RegExp[]}
*/
const patterns = [
// V8: at ClassName.methodName (/path/fileName.js:10:20)
/at (.+) \((.+):(\d+):(\d+)\)$/u,
// V8: at /path/fileName.js:10:20
/at ()(.+):(\d+):(\d+)$/u,
// FF30: trace@/path/fileName.js:10:20
/(.*)@(.+):(\d+):(\d+)$/u,
// FF14 to DD29: trace@/path/fileName.js:10
/(.*)@(.+):(\d+)()$/u,
]
/**
* @param {string} stackLine
* @returns {null | string[]}
*/
export function parseStackLine(stackLine) {
/** @type {null | string[]} */
let match = null
for (const pattern of patterns) {
match = pattern.exec(stackLine ?? '')
if (match) break
}
if (!match) {
return null
}
return match
}
/**
* @param {string} fileURI
* @returns {string}
*/
export function getPathFromFileURI(fileURI) {
/**
* Unix:
* file://localhost/etc/fstab
* file:///etc/fstab
* KDE:
* file:/etc/fstab
* Windows:
* file:///c:/WINDOWS/clock.avi
*
* @see https://en.wikipedia.org/wiki/File_URI_scheme
*
* @type {RegExpExecArray | null}
*/
const match = /^file:(?:\/\/\/([a-zA-Z]:[\\\/].*)|\/*(\/.+))/u
.exec(fileURI.trim())
return match?.[1] ?? match?.[2] ?? fileURI
}
/**
* This function works in V8 and Firefox
*
* @param {number} level
* @returns {{
* fileName: string,
* lineNumber: number
* columnNumber: number
* } | null}
* @throws {Error}
*/
export function stackTrace(level = 1) {
/** @type {string[]} */
let stack = []
try {
throw new Error('')
}
catch (error) {
if (error instanceof Error) {
stack = (error.stack ?? '')
.replaceAll('\r', '')
.split('\n')
.map((line) => line.trim())
/*
* Remove the first value if it stars with Error,
* and remove the second line, because it leads
* to the current function.
*/
stack = stack.splice((stack[0] ?? '').startsWith('Error') ? 2 : 1)
}
}
if (!stack[level]) {
return null
}
const parsed = parseStackLine(stack[level] ?? '')
if (!parsed) {
return null
}
const fileName = getPathFromFileURI(parsed[2] ?? '')
return {
fileName: decodeURI(fileName),
lineNumber: parseInt(parsed[3] ?? '0'),
columnNumber: parseInt(parsed[4] ?? '0')
}
}
export default stackTrace