503 lines
13 KiB
JavaScript
503 lines
13 KiB
JavaScript
|
/**
|
|||
|
* @typedef ErrnoExceptionFields
|
|||
|
* @property {number | undefined} [errnode]
|
|||
|
* @property {string | undefined} [code]
|
|||
|
* @property {string | undefined} [path]
|
|||
|
* @property {string | undefined} [syscall]
|
|||
|
* @property {string | undefined} [url]
|
|||
|
*
|
|||
|
* @typedef {Error & ErrnoExceptionFields} ErrnoException
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* @typedef {(...args: Array<any>) => string} MessageFunction
|
|||
|
*/
|
|||
|
|
|||
|
// Manually “tree shaken” from:
|
|||
|
// <https://github.com/nodejs/node/blob/45f5c9b/lib/internal/errors.js>
|
|||
|
// Last checked on: Nov 2, 2023.
|
|||
|
import v8 from 'node:v8'
|
|||
|
import assert from 'node:assert'
|
|||
|
// Needed for types.
|
|||
|
// eslint-disable-next-line no-unused-vars
|
|||
|
import {URL} from 'node:url'
|
|||
|
import {format, inspect} from 'node:util'
|
|||
|
|
|||
|
const own = {}.hasOwnProperty
|
|||
|
|
|||
|
const classRegExp = /^([A-Z][a-z\d]*)+$/
|
|||
|
// Sorted by a rough estimate on most frequently used entries.
|
|||
|
const kTypes = new Set([
|
|||
|
'string',
|
|||
|
'function',
|
|||
|
'number',
|
|||
|
'object',
|
|||
|
// Accept 'Function' and 'Object' as alternative to the lower cased version.
|
|||
|
'Function',
|
|||
|
'Object',
|
|||
|
'boolean',
|
|||
|
'bigint',
|
|||
|
'symbol'
|
|||
|
])
|
|||
|
|
|||
|
export const codes = {}
|
|||
|
|
|||
|
/**
|
|||
|
* Create a list string in the form like 'A and B' or 'A, B, ..., and Z'.
|
|||
|
* We cannot use Intl.ListFormat because it's not available in
|
|||
|
* --without-intl builds.
|
|||
|
*
|
|||
|
* @param {Array<string>} array
|
|||
|
* An array of strings.
|
|||
|
* @param {string} [type]
|
|||
|
* The list type to be inserted before the last element.
|
|||
|
* @returns {string}
|
|||
|
*/
|
|||
|
function formatList(array, type = 'and') {
|
|||
|
return array.length < 3
|
|||
|
? array.join(` ${type} `)
|
|||
|
: `${array.slice(0, -1).join(', ')}, ${type} ${array[array.length - 1]}`
|
|||
|
}
|
|||
|
|
|||
|
/** @type {Map<string, MessageFunction | string>} */
|
|||
|
const messages = new Map()
|
|||
|
const nodeInternalPrefix = '__node_internal_'
|
|||
|
/** @type {number} */
|
|||
|
let userStackTraceLimit
|
|||
|
|
|||
|
codes.ERR_INVALID_ARG_TYPE = createError(
|
|||
|
'ERR_INVALID_ARG_TYPE',
|
|||
|
/**
|
|||
|
* @param {string} name
|
|||
|
* @param {Array<string> | string} expected
|
|||
|
* @param {unknown} actual
|
|||
|
*/
|
|||
|
(name, expected, actual) => {
|
|||
|
assert(typeof name === 'string', "'name' must be a string")
|
|||
|
if (!Array.isArray(expected)) {
|
|||
|
expected = [expected]
|
|||
|
}
|
|||
|
|
|||
|
let message = 'The '
|
|||
|
if (name.endsWith(' argument')) {
|
|||
|
// For cases like 'first argument'
|
|||
|
message += `${name} `
|
|||
|
} else {
|
|||
|
const type = name.includes('.') ? 'property' : 'argument'
|
|||
|
message += `"${name}" ${type} `
|
|||
|
}
|
|||
|
|
|||
|
message += 'must be '
|
|||
|
|
|||
|
/** @type {Array<string>} */
|
|||
|
const types = []
|
|||
|
/** @type {Array<string>} */
|
|||
|
const instances = []
|
|||
|
/** @type {Array<string>} */
|
|||
|
const other = []
|
|||
|
|
|||
|
for (const value of expected) {
|
|||
|
assert(
|
|||
|
typeof value === 'string',
|
|||
|
'All expected entries have to be of type string'
|
|||
|
)
|
|||
|
|
|||
|
if (kTypes.has(value)) {
|
|||
|
types.push(value.toLowerCase())
|
|||
|
} else if (classRegExp.exec(value) === null) {
|
|||
|
assert(
|
|||
|
value !== 'object',
|
|||
|
'The value "object" should be written as "Object"'
|
|||
|
)
|
|||
|
other.push(value)
|
|||
|
} else {
|
|||
|
instances.push(value)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Special handle `object` in case other instances are allowed to outline
|
|||
|
// the differences between each other.
|
|||
|
if (instances.length > 0) {
|
|||
|
const pos = types.indexOf('object')
|
|||
|
if (pos !== -1) {
|
|||
|
types.slice(pos, 1)
|
|||
|
instances.push('Object')
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (types.length > 0) {
|
|||
|
message += `${types.length > 1 ? 'one of type' : 'of type'} ${formatList(
|
|||
|
types,
|
|||
|
'or'
|
|||
|
)}`
|
|||
|
if (instances.length > 0 || other.length > 0) message += ' or '
|
|||
|
}
|
|||
|
|
|||
|
if (instances.length > 0) {
|
|||
|
message += `an instance of ${formatList(instances, 'or')}`
|
|||
|
if (other.length > 0) message += ' or '
|
|||
|
}
|
|||
|
|
|||
|
if (other.length > 0) {
|
|||
|
if (other.length > 1) {
|
|||
|
message += `one of ${formatList(other, 'or')}`
|
|||
|
} else {
|
|||
|
if (other[0].toLowerCase() !== other[0]) message += 'an '
|
|||
|
message += `${other[0]}`
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
message += `. Received ${determineSpecificType(actual)}`
|
|||
|
|
|||
|
return message
|
|||
|
},
|
|||
|
TypeError
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_INVALID_MODULE_SPECIFIER = createError(
|
|||
|
'ERR_INVALID_MODULE_SPECIFIER',
|
|||
|
/**
|
|||
|
* @param {string} request
|
|||
|
* @param {string} reason
|
|||
|
* @param {string} [base]
|
|||
|
*/
|
|||
|
(request, reason, base = undefined) => {
|
|||
|
return `Invalid module "${request}" ${reason}${
|
|||
|
base ? ` imported from ${base}` : ''
|
|||
|
}`
|
|||
|
},
|
|||
|
TypeError
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_INVALID_PACKAGE_CONFIG = createError(
|
|||
|
'ERR_INVALID_PACKAGE_CONFIG',
|
|||
|
/**
|
|||
|
* @param {string} path
|
|||
|
* @param {string} [base]
|
|||
|
* @param {string} [message]
|
|||
|
*/
|
|||
|
(path, base, message) => {
|
|||
|
return `Invalid package config ${path}${
|
|||
|
base ? ` while importing ${base}` : ''
|
|||
|
}${message ? `. ${message}` : ''}`
|
|||
|
},
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_INVALID_PACKAGE_TARGET = createError(
|
|||
|
'ERR_INVALID_PACKAGE_TARGET',
|
|||
|
/**
|
|||
|
* @param {string} pkgPath
|
|||
|
* @param {string} key
|
|||
|
* @param {unknown} target
|
|||
|
* @param {boolean} [isImport=false]
|
|||
|
* @param {string} [base]
|
|||
|
*/
|
|||
|
(pkgPath, key, target, isImport = false, base = undefined) => {
|
|||
|
const relError =
|
|||
|
typeof target === 'string' &&
|
|||
|
!isImport &&
|
|||
|
target.length > 0 &&
|
|||
|
!target.startsWith('./')
|
|||
|
if (key === '.') {
|
|||
|
assert(isImport === false)
|
|||
|
return (
|
|||
|
`Invalid "exports" main target ${JSON.stringify(target)} defined ` +
|
|||
|
`in the package config ${pkgPath}package.json${
|
|||
|
base ? ` imported from ${base}` : ''
|
|||
|
}${relError ? '; targets must start with "./"' : ''}`
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
return `Invalid "${
|
|||
|
isImport ? 'imports' : 'exports'
|
|||
|
}" target ${JSON.stringify(
|
|||
|
target
|
|||
|
)} defined for '${key}' in the package config ${pkgPath}package.json${
|
|||
|
base ? ` imported from ${base}` : ''
|
|||
|
}${relError ? '; targets must start with "./"' : ''}`
|
|||
|
},
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_MODULE_NOT_FOUND = createError(
|
|||
|
'ERR_MODULE_NOT_FOUND',
|
|||
|
/**
|
|||
|
* @param {string} path
|
|||
|
* @param {string} base
|
|||
|
* @param {boolean} [exactUrl]
|
|||
|
*/
|
|||
|
(path, base, exactUrl = false) => {
|
|||
|
return `Cannot find ${
|
|||
|
exactUrl ? 'module' : 'package'
|
|||
|
} '${path}' imported from ${base}`
|
|||
|
},
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_NETWORK_IMPORT_DISALLOWED = createError(
|
|||
|
'ERR_NETWORK_IMPORT_DISALLOWED',
|
|||
|
"import of '%s' by %s is not supported: %s",
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_PACKAGE_IMPORT_NOT_DEFINED = createError(
|
|||
|
'ERR_PACKAGE_IMPORT_NOT_DEFINED',
|
|||
|
/**
|
|||
|
* @param {string} specifier
|
|||
|
* @param {string} packagePath
|
|||
|
* @param {string} base
|
|||
|
*/
|
|||
|
(specifier, packagePath, base) => {
|
|||
|
return `Package import specifier "${specifier}" is not defined${
|
|||
|
packagePath ? ` in package ${packagePath}package.json` : ''
|
|||
|
} imported from ${base}`
|
|||
|
},
|
|||
|
TypeError
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_PACKAGE_PATH_NOT_EXPORTED = createError(
|
|||
|
'ERR_PACKAGE_PATH_NOT_EXPORTED',
|
|||
|
/**
|
|||
|
* @param {string} pkgPath
|
|||
|
* @param {string} subpath
|
|||
|
* @param {string} [base]
|
|||
|
*/
|
|||
|
(pkgPath, subpath, base = undefined) => {
|
|||
|
if (subpath === '.')
|
|||
|
return `No "exports" main defined in ${pkgPath}package.json${
|
|||
|
base ? ` imported from ${base}` : ''
|
|||
|
}`
|
|||
|
return `Package subpath '${subpath}' is not defined by "exports" in ${pkgPath}package.json${
|
|||
|
base ? ` imported from ${base}` : ''
|
|||
|
}`
|
|||
|
},
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_UNSUPPORTED_DIR_IMPORT = createError(
|
|||
|
'ERR_UNSUPPORTED_DIR_IMPORT',
|
|||
|
"Directory import '%s' is not supported " +
|
|||
|
'resolving ES modules imported from %s',
|
|||
|
Error
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_UNKNOWN_FILE_EXTENSION = createError(
|
|||
|
'ERR_UNKNOWN_FILE_EXTENSION',
|
|||
|
/**
|
|||
|
* @param {string} ext
|
|||
|
* @param {string} path
|
|||
|
*/
|
|||
|
(ext, path) => {
|
|||
|
return `Unknown file extension "${ext}" for ${path}`
|
|||
|
},
|
|||
|
TypeError
|
|||
|
)
|
|||
|
|
|||
|
codes.ERR_INVALID_ARG_VALUE = createError(
|
|||
|
'ERR_INVALID_ARG_VALUE',
|
|||
|
/**
|
|||
|
* @param {string} name
|
|||
|
* @param {unknown} value
|
|||
|
* @param {string} [reason='is invalid']
|
|||
|
*/
|
|||
|
(name, value, reason = 'is invalid') => {
|
|||
|
let inspected = inspect(value)
|
|||
|
|
|||
|
if (inspected.length > 128) {
|
|||
|
inspected = `${inspected.slice(0, 128)}...`
|
|||
|
}
|
|||
|
|
|||
|
const type = name.includes('.') ? 'property' : 'argument'
|
|||
|
|
|||
|
return `The ${type} '${name}' ${reason}. Received ${inspected}`
|
|||
|
},
|
|||
|
TypeError
|
|||
|
// Note: extra classes have been shaken out.
|
|||
|
// , RangeError
|
|||
|
)
|
|||
|
|
|||
|
/**
|
|||
|
* Utility function for registering the error codes. Only used here. Exported
|
|||
|
* *only* to allow for testing.
|
|||
|
* @param {string} sym
|
|||
|
* @param {MessageFunction | string} value
|
|||
|
* @param {ErrorConstructor} def
|
|||
|
* @returns {new (...args: Array<any>) => Error}
|
|||
|
*/
|
|||
|
function createError(sym, value, def) {
|
|||
|
// Special case for SystemError that formats the error message differently
|
|||
|
// The SystemErrors only have SystemError as their base classes.
|
|||
|
messages.set(sym, value)
|
|||
|
|
|||
|
return makeNodeErrorWithCode(def, sym)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @param {ErrorConstructor} Base
|
|||
|
* @param {string} key
|
|||
|
* @returns {ErrorConstructor}
|
|||
|
*/
|
|||
|
function makeNodeErrorWithCode(Base, key) {
|
|||
|
// @ts-expect-error It’s a Node error.
|
|||
|
return NodeError
|
|||
|
/**
|
|||
|
* @param {Array<unknown>} args
|
|||
|
*/
|
|||
|
function NodeError(...args) {
|
|||
|
const limit = Error.stackTraceLimit
|
|||
|
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0
|
|||
|
const error = new Base()
|
|||
|
// Reset the limit and setting the name property.
|
|||
|
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit
|
|||
|
const message = getMessage(key, args, error)
|
|||
|
Object.defineProperties(error, {
|
|||
|
// Note: no need to implement `kIsNodeError` symbol, would be hard,
|
|||
|
// probably.
|
|||
|
message: {
|
|||
|
value: message,
|
|||
|
enumerable: false,
|
|||
|
writable: true,
|
|||
|
configurable: true
|
|||
|
},
|
|||
|
toString: {
|
|||
|
/** @this {Error} */
|
|||
|
value() {
|
|||
|
return `${this.name} [${key}]: ${this.message}`
|
|||
|
},
|
|||
|
enumerable: false,
|
|||
|
writable: true,
|
|||
|
configurable: true
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
captureLargerStackTrace(error)
|
|||
|
// @ts-expect-error It’s a Node error.
|
|||
|
error.code = key
|
|||
|
return error
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @returns {boolean}
|
|||
|
*/
|
|||
|
function isErrorStackTraceLimitWritable() {
|
|||
|
// Do no touch Error.stackTraceLimit as V8 would attempt to install
|
|||
|
// it again during deserialization.
|
|||
|
try {
|
|||
|
// @ts-expect-error: not in types?
|
|||
|
if (v8.startupSnapshot.isBuildingSnapshot()) {
|
|||
|
return false
|
|||
|
}
|
|||
|
} catch {}
|
|||
|
|
|||
|
const desc = Object.getOwnPropertyDescriptor(Error, 'stackTraceLimit')
|
|||
|
if (desc === undefined) {
|
|||
|
return Object.isExtensible(Error)
|
|||
|
}
|
|||
|
|
|||
|
return own.call(desc, 'writable') && desc.writable !== undefined
|
|||
|
? desc.writable
|
|||
|
: desc.set !== undefined
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* This function removes unnecessary frames from Node.js core errors.
|
|||
|
* @template {(...args: unknown[]) => unknown} T
|
|||
|
* @param {T} fn
|
|||
|
* @returns {T}
|
|||
|
*/
|
|||
|
function hideStackFrames(fn) {
|
|||
|
// We rename the functions that will be hidden to cut off the stacktrace
|
|||
|
// at the outermost one
|
|||
|
const hidden = nodeInternalPrefix + fn.name
|
|||
|
Object.defineProperty(fn, 'name', {value: hidden})
|
|||
|
return fn
|
|||
|
}
|
|||
|
|
|||
|
const captureLargerStackTrace = hideStackFrames(
|
|||
|
/**
|
|||
|
* @param {Error} error
|
|||
|
* @returns {Error}
|
|||
|
*/
|
|||
|
// @ts-expect-error: fine
|
|||
|
function (error) {
|
|||
|
const stackTraceLimitIsWritable = isErrorStackTraceLimitWritable()
|
|||
|
if (stackTraceLimitIsWritable) {
|
|||
|
userStackTraceLimit = Error.stackTraceLimit
|
|||
|
Error.stackTraceLimit = Number.POSITIVE_INFINITY
|
|||
|
}
|
|||
|
|
|||
|
Error.captureStackTrace(error)
|
|||
|
|
|||
|
// Reset the limit
|
|||
|
if (stackTraceLimitIsWritable) Error.stackTraceLimit = userStackTraceLimit
|
|||
|
|
|||
|
return error
|
|||
|
}
|
|||
|
)
|
|||
|
|
|||
|
/**
|
|||
|
* @param {string} key
|
|||
|
* @param {Array<unknown>} args
|
|||
|
* @param {Error} self
|
|||
|
* @returns {string}
|
|||
|
*/
|
|||
|
function getMessage(key, args, self) {
|
|||
|
const message = messages.get(key)
|
|||
|
assert(message !== undefined, 'expected `message` to be found')
|
|||
|
|
|||
|
if (typeof message === 'function') {
|
|||
|
assert(
|
|||
|
message.length <= args.length, // Default options do not count.
|
|||
|
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
|
|||
|
`match the required ones (${message.length}).`
|
|||
|
)
|
|||
|
return Reflect.apply(message, self, args)
|
|||
|
}
|
|||
|
|
|||
|
const regex = /%[dfijoOs]/g
|
|||
|
let expectedLength = 0
|
|||
|
while (regex.exec(message) !== null) expectedLength++
|
|||
|
assert(
|
|||
|
expectedLength === args.length,
|
|||
|
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
|
|||
|
`match the required ones (${expectedLength}).`
|
|||
|
)
|
|||
|
if (args.length === 0) return message
|
|||
|
|
|||
|
args.unshift(message)
|
|||
|
return Reflect.apply(format, null, args)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Determine the specific type of a value for type-mismatch errors.
|
|||
|
* @param {unknown} value
|
|||
|
* @returns {string}
|
|||
|
*/
|
|||
|
function determineSpecificType(value) {
|
|||
|
if (value === null || value === undefined) {
|
|||
|
return String(value)
|
|||
|
}
|
|||
|
|
|||
|
if (typeof value === 'function' && value.name) {
|
|||
|
return `function ${value.name}`
|
|||
|
}
|
|||
|
|
|||
|
if (typeof value === 'object') {
|
|||
|
if (value.constructor && value.constructor.name) {
|
|||
|
return `an instance of ${value.constructor.name}`
|
|||
|
}
|
|||
|
|
|||
|
return `${inspect(value, {depth: -1})}`
|
|||
|
}
|
|||
|
|
|||
|
let inspected = inspect(value, {colors: false})
|
|||
|
|
|||
|
if (inspected.length > 28) {
|
|||
|
inspected = `${inspected.slice(0, 25)}...`
|
|||
|
}
|
|||
|
|
|||
|
return `type ${typeof value} (${inspected})`
|
|||
|
}
|