/** * @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) => string} MessageFunction */ // Manually “tree shaken” from: // // 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} 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} */ 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} 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} */ const types = [] /** @type {Array} */ const instances = [] /** @type {Array} */ 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) => 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} 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} 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})` }