Skip to content

Commit

Permalink
Split file
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jun 5, 2022
1 parent 4a7ca65 commit c902130
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 60 deletions.
70 changes: 10 additions & 60 deletions src/error/normalize/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { setFullStack, getStackHeader, fixStack } from './stack.js'

// Ensure we are using an Error instance
export const normalizeError = function (error) {
if (!(error instanceof Error)) {
Expand All @@ -12,6 +14,8 @@ export const normalizeError = function (error) {
return error
}

// If an exception is not an Error instance, create one.
// `String()` might throw due to `value.toString()`, so we handle it.
const createError = function (value) {
try {
const error = new Error(String(value))
Expand All @@ -22,6 +26,7 @@ const createError = function (value) {
}
}

// Ensure `error.name` is a string
const normalizeName = function (error) {
if (!isDefinedString(error.name)) {
setErrorProperty(
Expand All @@ -34,12 +39,14 @@ const normalizeName = function (error) {
}
}

// Ensure `error.message` is a string
const normalizeMessage = function (error) {
if (!isDefinedString(error.message)) {
error.message = ''
}
}

// Ensure `error.stack` exists and looks normal
const normalizeStack = function (error) {
if (!isDefinedString(error.stack)) {
setFullStack(error)
Expand All @@ -52,76 +59,18 @@ const normalizeStack = function (error) {
}
}

const setFullStack = function (error) {
error.stack = `${getStackHeader(error)}\n${getStackTrace()}`
}

const getStackHeader = function (error) {
return `${error.name}: ${error.message}`
}

const fixStack = function (error, header) {
const lines = error.stack.split('\n')
const index = lines.findIndex(isStackLine)
const linesA = index === -1 ? [getStackTrace()] : lines.slice(index)
error.stack = [header, ...linesA].join('\n')
}

const isStackLine = function (line) {
return STACK_LINE_REGEXP.test(line)
}

const STACK_LINE_REGEXP = /^\s*at\s/u

const getStackTrace = function () {
// eslint-disable-next-line unicorn/error-message
const lines = new Error('').stack.split('\n')
const index = findInternalIndex(lines)
return lines.slice(index).join('\n')
}

// Remove stack lines due to this library itself.
// `index` should never be `-1`, but we include this as a failsafe.
// The stack trace might be truncated, e.g. due to `Error.stackTraceLimit` in
// Node.js, leading to the whole stack to be removed. In that case, we keep the
// last stack line.
const findInternalIndex = function (lines) {
const index = findLastIndex(lines, isInternalStackLine)

if (index === -1) {
return 1
}

return lines.length - 1 === index ? index : index + 1
}

// TODO: use `Array.findLastIndex()` after dropping support for Node <18
const findLastIndex = function (array, condition) {
// eslint-disable-next-line fp/no-loops, fp/no-mutation, fp/no-let
for (let index = array.length - 1; index >= 0; index -= 1) {
// eslint-disable-next-line max-depth
if (condition(array[index])) {
return index
}
}

return -1
}

const isInternalStackLine = function (line) {
return line.includes(`at ${normalizeError.name}`)
}

const isDefinedString = function (value) {
return typeof value === 'string' && value !== ''
}

// Recurse over `error.cause`
const normalizeCause = function (error) {
if (error.cause !== undefined) {
error.cause = normalizeError(error.cause)
}
}

// Recurse over `error.errors`
const normalizeAggregate = function (error) {
if (hasAggregate(error)) {
error.errors = error.errors.map(normalizeError)
Expand All @@ -139,6 +88,7 @@ const isErrorInstance = function (error) {
return error instanceof Error
}

// Error properties are non-enumerable
const setErrorProperty = function (error, propName, value) {
// eslint-disable-next-line fp/no-mutating-methods
Object.defineProperty(error, 'name', {
Expand Down
69 changes: 69 additions & 0 deletions src/error/normalize/stack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Ensure `error.stack` reflects `error.name` and `error.message`
// Also create stack trace if missing.
export const setFullStack = function (error) {
error.stack = `${getStackHeader(error)}\n${getStackTrace()}`
}

// Expected first line of `error.stack`
export const getStackHeader = function (error) {
return `${error.name}: ${error.message}`
}

// Ensure `error.stack` reflects `error.name` and `error.message`
export const fixStack = function (error, header) {
const lines = error.stack.split('\n')
const index = lines.findIndex(isStackLine)
const linesA = index === -1 ? [getStackTrace()] : lines.slice(index)
error.stack = [header, ...linesA].join('\n')
}

const isStackLine = function (line) {
return STACK_LINE_REGEXP.test(line)
}

const STACK_LINE_REGEXP = /^\s*at\s/u

// Generate a new stack trace
const getStackTrace = function () {
// eslint-disable-next-line unicorn/error-message
const lines = new Error('').stack.split('\n')
const index = findInternalIndex(lines)
return lines.slice(index).join('\n')
}

// Remove stack lines due to this library itself.
// `index` should never be `-1`, but we include this as a failsafe.
// The stack trace might be truncated, e.g. due to `Error.stackTraceLimit` in
// Node.js, leading to the whole stack to be removed. In that case, we keep the
// last stack line.
const findInternalIndex = function (lines) {
const index = findLastIndex(lines, isInternalStackLine)

if (index === -1) {
return 1
}

return lines.length - 1 === index ? index : index + 1
}

// TODO: use `Array.findLastIndex()` after dropping support for Node <18
const findLastIndex = function (array, condition) {
// eslint-disable-next-line fp/no-loops, fp/no-mutation, fp/no-let
for (let index = array.length - 1; index >= 0; index -= 1) {
// eslint-disable-next-line max-depth
if (condition(array[index])) {
return index
}
}

return -1
}

const isInternalStackLine = function (line) {
return line.includes(NORMALIZE_ERROR_LINE)
}

// How the top-level function appears in a stack trace.
// Since stack traces are implementation-specific, we must be very conservative
// and careful. Also, we must ensure transpilation does not change this.
const NORMALIZE_ERROR_LINE = 'at normalizeError'

0 comments on commit c902130

Please sign in to comment.