errors.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. /**
  2. * @typedef ErrnoExceptionFields
  3. * @property {number | undefined} [errnode]
  4. * @property {string | undefined} [code]
  5. * @property {string | undefined} [path]
  6. * @property {string | undefined} [syscall]
  7. * @property {string | undefined} [url]
  8. *
  9. * @typedef {Error & ErrnoExceptionFields} ErrnoException
  10. */
  11. /**
  12. * @typedef {(...parameters: Array<any>) => string} MessageFunction
  13. */
  14. // Manually “tree shaken” from:
  15. // <https://github.com/nodejs/node/blob/45f5c9b/lib/internal/errors.js>
  16. // Last checked on: Apr 29, 2024.
  17. import v8 from 'node:v8'
  18. import assert from 'node:assert'
  19. import {format, inspect} from 'node:util'
  20. const own = {}.hasOwnProperty
  21. const classRegExp = /^([A-Z][a-z\d]*)+$/
  22. // Sorted by a rough estimate on most frequently used entries.
  23. const kTypes = new Set([
  24. 'string',
  25. 'function',
  26. 'number',
  27. 'object',
  28. // Accept 'Function' and 'Object' as alternative to the lower cased version.
  29. 'Function',
  30. 'Object',
  31. 'boolean',
  32. 'bigint',
  33. 'symbol'
  34. ])
  35. export const codes = {}
  36. /**
  37. * Create a list string in the form like 'A and B' or 'A, B, ..., and Z'.
  38. * We cannot use Intl.ListFormat because it's not available in
  39. * --without-intl builds.
  40. *
  41. * @param {Array<string>} array
  42. * An array of strings.
  43. * @param {string} [type]
  44. * The list type to be inserted before the last element.
  45. * @returns {string}
  46. */
  47. function formatList(array, type = 'and') {
  48. return array.length < 3
  49. ? array.join(` ${type} `)
  50. : `${array.slice(0, -1).join(', ')}, ${type} ${array[array.length - 1]}`
  51. }
  52. /** @type {Map<string, MessageFunction | string>} */
  53. const messages = new Map()
  54. const nodeInternalPrefix = '__node_internal_'
  55. /** @type {number} */
  56. let userStackTraceLimit
  57. codes.ERR_INVALID_ARG_TYPE = createError(
  58. 'ERR_INVALID_ARG_TYPE',
  59. /**
  60. * @param {string} name
  61. * @param {Array<string> | string} expected
  62. * @param {unknown} actual
  63. */
  64. (name, expected, actual) => {
  65. assert.ok(typeof name === 'string', "'name' must be a string")
  66. if (!Array.isArray(expected)) {
  67. expected = [expected]
  68. }
  69. let message = 'The '
  70. if (name.endsWith(' argument')) {
  71. // For cases like 'first argument'
  72. message += `${name} `
  73. } else {
  74. const type = name.includes('.') ? 'property' : 'argument'
  75. message += `"${name}" ${type} `
  76. }
  77. message += 'must be '
  78. /** @type {Array<string>} */
  79. const types = []
  80. /** @type {Array<string>} */
  81. const instances = []
  82. /** @type {Array<string>} */
  83. const other = []
  84. for (const value of expected) {
  85. assert.ok(
  86. typeof value === 'string',
  87. 'All expected entries have to be of type string'
  88. )
  89. if (kTypes.has(value)) {
  90. types.push(value.toLowerCase())
  91. } else if (classRegExp.exec(value) === null) {
  92. assert.ok(
  93. value !== 'object',
  94. 'The value "object" should be written as "Object"'
  95. )
  96. other.push(value)
  97. } else {
  98. instances.push(value)
  99. }
  100. }
  101. // Special handle `object` in case other instances are allowed to outline
  102. // the differences between each other.
  103. if (instances.length > 0) {
  104. const pos = types.indexOf('object')
  105. if (pos !== -1) {
  106. types.slice(pos, 1)
  107. instances.push('Object')
  108. }
  109. }
  110. if (types.length > 0) {
  111. message += `${types.length > 1 ? 'one of type' : 'of type'} ${formatList(
  112. types,
  113. 'or'
  114. )}`
  115. if (instances.length > 0 || other.length > 0) message += ' or '
  116. }
  117. if (instances.length > 0) {
  118. message += `an instance of ${formatList(instances, 'or')}`
  119. if (other.length > 0) message += ' or '
  120. }
  121. if (other.length > 0) {
  122. if (other.length > 1) {
  123. message += `one of ${formatList(other, 'or')}`
  124. } else {
  125. if (other[0].toLowerCase() !== other[0]) message += 'an '
  126. message += `${other[0]}`
  127. }
  128. }
  129. message += `. Received ${determineSpecificType(actual)}`
  130. return message
  131. },
  132. TypeError
  133. )
  134. codes.ERR_INVALID_MODULE_SPECIFIER = createError(
  135. 'ERR_INVALID_MODULE_SPECIFIER',
  136. /**
  137. * @param {string} request
  138. * @param {string} reason
  139. * @param {string} [base]
  140. */
  141. (request, reason, base = undefined) => {
  142. return `Invalid module "${request}" ${reason}${
  143. base ? ` imported from ${base}` : ''
  144. }`
  145. },
  146. TypeError
  147. )
  148. codes.ERR_INVALID_PACKAGE_CONFIG = createError(
  149. 'ERR_INVALID_PACKAGE_CONFIG',
  150. /**
  151. * @param {string} path
  152. * @param {string} [base]
  153. * @param {string} [message]
  154. */
  155. (path, base, message) => {
  156. return `Invalid package config ${path}${
  157. base ? ` while importing ${base}` : ''
  158. }${message ? `. ${message}` : ''}`
  159. },
  160. Error
  161. )
  162. codes.ERR_INVALID_PACKAGE_TARGET = createError(
  163. 'ERR_INVALID_PACKAGE_TARGET',
  164. /**
  165. * @param {string} packagePath
  166. * @param {string} key
  167. * @param {unknown} target
  168. * @param {boolean} [isImport=false]
  169. * @param {string} [base]
  170. */
  171. (packagePath, key, target, isImport = false, base = undefined) => {
  172. const relatedError =
  173. typeof target === 'string' &&
  174. !isImport &&
  175. target.length > 0 &&
  176. !target.startsWith('./')
  177. if (key === '.') {
  178. assert.ok(isImport === false)
  179. return (
  180. `Invalid "exports" main target ${JSON.stringify(target)} defined ` +
  181. `in the package config ${packagePath}package.json${
  182. base ? ` imported from ${base}` : ''
  183. }${relatedError ? '; targets must start with "./"' : ''}`
  184. )
  185. }
  186. return `Invalid "${
  187. isImport ? 'imports' : 'exports'
  188. }" target ${JSON.stringify(
  189. target
  190. )} defined for '${key}' in the package config ${packagePath}package.json${
  191. base ? ` imported from ${base}` : ''
  192. }${relatedError ? '; targets must start with "./"' : ''}`
  193. },
  194. Error
  195. )
  196. codes.ERR_MODULE_NOT_FOUND = createError(
  197. 'ERR_MODULE_NOT_FOUND',
  198. /**
  199. * @param {string} path
  200. * @param {string} base
  201. * @param {boolean} [exactUrl]
  202. */
  203. (path, base, exactUrl = false) => {
  204. return `Cannot find ${
  205. exactUrl ? 'module' : 'package'
  206. } '${path}' imported from ${base}`
  207. },
  208. Error
  209. )
  210. codes.ERR_NETWORK_IMPORT_DISALLOWED = createError(
  211. 'ERR_NETWORK_IMPORT_DISALLOWED',
  212. "import of '%s' by %s is not supported: %s",
  213. Error
  214. )
  215. codes.ERR_PACKAGE_IMPORT_NOT_DEFINED = createError(
  216. 'ERR_PACKAGE_IMPORT_NOT_DEFINED',
  217. /**
  218. * @param {string} specifier
  219. * @param {string} packagePath
  220. * @param {string} base
  221. */
  222. (specifier, packagePath, base) => {
  223. return `Package import specifier "${specifier}" is not defined${
  224. packagePath ? ` in package ${packagePath}package.json` : ''
  225. } imported from ${base}`
  226. },
  227. TypeError
  228. )
  229. codes.ERR_PACKAGE_PATH_NOT_EXPORTED = createError(
  230. 'ERR_PACKAGE_PATH_NOT_EXPORTED',
  231. /**
  232. * @param {string} packagePath
  233. * @param {string} subpath
  234. * @param {string} [base]
  235. */
  236. (packagePath, subpath, base = undefined) => {
  237. if (subpath === '.')
  238. return `No "exports" main defined in ${packagePath}package.json${
  239. base ? ` imported from ${base}` : ''
  240. }`
  241. return `Package subpath '${subpath}' is not defined by "exports" in ${packagePath}package.json${
  242. base ? ` imported from ${base}` : ''
  243. }`
  244. },
  245. Error
  246. )
  247. codes.ERR_UNSUPPORTED_DIR_IMPORT = createError(
  248. 'ERR_UNSUPPORTED_DIR_IMPORT',
  249. "Directory import '%s' is not supported " +
  250. 'resolving ES modules imported from %s',
  251. Error
  252. )
  253. codes.ERR_UNSUPPORTED_RESOLVE_REQUEST = createError(
  254. 'ERR_UNSUPPORTED_RESOLVE_REQUEST',
  255. 'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
  256. TypeError
  257. )
  258. codes.ERR_UNKNOWN_FILE_EXTENSION = createError(
  259. 'ERR_UNKNOWN_FILE_EXTENSION',
  260. /**
  261. * @param {string} extension
  262. * @param {string} path
  263. */
  264. (extension, path) => {
  265. return `Unknown file extension "${extension}" for ${path}`
  266. },
  267. TypeError
  268. )
  269. codes.ERR_INVALID_ARG_VALUE = createError(
  270. 'ERR_INVALID_ARG_VALUE',
  271. /**
  272. * @param {string} name
  273. * @param {unknown} value
  274. * @param {string} [reason='is invalid']
  275. */
  276. (name, value, reason = 'is invalid') => {
  277. let inspected = inspect(value)
  278. if (inspected.length > 128) {
  279. inspected = `${inspected.slice(0, 128)}...`
  280. }
  281. const type = name.includes('.') ? 'property' : 'argument'
  282. return `The ${type} '${name}' ${reason}. Received ${inspected}`
  283. },
  284. TypeError
  285. // Note: extra classes have been shaken out.
  286. // , RangeError
  287. )
  288. /**
  289. * Utility function for registering the error codes. Only used here. Exported
  290. * *only* to allow for testing.
  291. * @param {string} sym
  292. * @param {MessageFunction | string} value
  293. * @param {ErrorConstructor} constructor
  294. * @returns {new (...parameters: Array<any>) => Error}
  295. */
  296. function createError(sym, value, constructor) {
  297. // Special case for SystemError that formats the error message differently
  298. // The SystemErrors only have SystemError as their base classes.
  299. messages.set(sym, value)
  300. return makeNodeErrorWithCode(constructor, sym)
  301. }
  302. /**
  303. * @param {ErrorConstructor} Base
  304. * @param {string} key
  305. * @returns {ErrorConstructor}
  306. */
  307. function makeNodeErrorWithCode(Base, key) {
  308. // @ts-expect-error It’s a Node error.
  309. return NodeError
  310. /**
  311. * @param {Array<unknown>} parameters
  312. */
  313. function NodeError(...parameters) {
  314. const limit = Error.stackTraceLimit
  315. if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0
  316. const error = new Base()
  317. // Reset the limit and setting the name property.
  318. if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit
  319. const message = getMessage(key, parameters, error)
  320. Object.defineProperties(error, {
  321. // Note: no need to implement `kIsNodeError` symbol, would be hard,
  322. // probably.
  323. message: {
  324. value: message,
  325. enumerable: false,
  326. writable: true,
  327. configurable: true
  328. },
  329. toString: {
  330. /** @this {Error} */
  331. value() {
  332. return `${this.name} [${key}]: ${this.message}`
  333. },
  334. enumerable: false,
  335. writable: true,
  336. configurable: true
  337. }
  338. })
  339. captureLargerStackTrace(error)
  340. // @ts-expect-error It’s a Node error.
  341. error.code = key
  342. return error
  343. }
  344. }
  345. /**
  346. * @returns {boolean}
  347. */
  348. function isErrorStackTraceLimitWritable() {
  349. // Do no touch Error.stackTraceLimit as V8 would attempt to install
  350. // it again during deserialization.
  351. try {
  352. if (v8.startupSnapshot.isBuildingSnapshot()) {
  353. return false
  354. }
  355. } catch {}
  356. const desc = Object.getOwnPropertyDescriptor(Error, 'stackTraceLimit')
  357. if (desc === undefined) {
  358. return Object.isExtensible(Error)
  359. }
  360. return own.call(desc, 'writable') && desc.writable !== undefined
  361. ? desc.writable
  362. : desc.set !== undefined
  363. }
  364. /**
  365. * This function removes unnecessary frames from Node.js core errors.
  366. * @template {(...parameters: unknown[]) => unknown} T
  367. * @param {T} wrappedFunction
  368. * @returns {T}
  369. */
  370. function hideStackFrames(wrappedFunction) {
  371. // We rename the functions that will be hidden to cut off the stacktrace
  372. // at the outermost one
  373. const hidden = nodeInternalPrefix + wrappedFunction.name
  374. Object.defineProperty(wrappedFunction, 'name', {value: hidden})
  375. return wrappedFunction
  376. }
  377. const captureLargerStackTrace = hideStackFrames(
  378. /**
  379. * @param {Error} error
  380. * @returns {Error}
  381. */
  382. // @ts-expect-error: fine
  383. function (error) {
  384. const stackTraceLimitIsWritable = isErrorStackTraceLimitWritable()
  385. if (stackTraceLimitIsWritable) {
  386. userStackTraceLimit = Error.stackTraceLimit
  387. Error.stackTraceLimit = Number.POSITIVE_INFINITY
  388. }
  389. Error.captureStackTrace(error)
  390. // Reset the limit
  391. if (stackTraceLimitIsWritable) Error.stackTraceLimit = userStackTraceLimit
  392. return error
  393. }
  394. )
  395. /**
  396. * @param {string} key
  397. * @param {Array<unknown>} parameters
  398. * @param {Error} self
  399. * @returns {string}
  400. */
  401. function getMessage(key, parameters, self) {
  402. const message = messages.get(key)
  403. assert.ok(message !== undefined, 'expected `message` to be found')
  404. if (typeof message === 'function') {
  405. assert.ok(
  406. message.length <= parameters.length, // Default options do not count.
  407. `Code: ${key}; The provided arguments length (${parameters.length}) does not ` +
  408. `match the required ones (${message.length}).`
  409. )
  410. return Reflect.apply(message, self, parameters)
  411. }
  412. const regex = /%[dfijoOs]/g
  413. let expectedLength = 0
  414. while (regex.exec(message) !== null) expectedLength++
  415. assert.ok(
  416. expectedLength === parameters.length,
  417. `Code: ${key}; The provided arguments length (${parameters.length}) does not ` +
  418. `match the required ones (${expectedLength}).`
  419. )
  420. if (parameters.length === 0) return message
  421. parameters.unshift(message)
  422. return Reflect.apply(format, null, parameters)
  423. }
  424. /**
  425. * Determine the specific type of a value for type-mismatch errors.
  426. * @param {unknown} value
  427. * @returns {string}
  428. */
  429. function determineSpecificType(value) {
  430. if (value === null || value === undefined) {
  431. return String(value)
  432. }
  433. if (typeof value === 'function' && value.name) {
  434. return `function ${value.name}`
  435. }
  436. if (typeof value === 'object') {
  437. if (value.constructor && value.constructor.name) {
  438. return `an instance of ${value.constructor.name}`
  439. }
  440. return `${inspect(value, {depth: -1})}`
  441. }
  442. let inspected = inspect(value, {colors: false})
  443. if (inspected.length > 28) {
  444. inspected = `${inspected.slice(0, 25)}...`
  445. }
  446. return `type ${typeof value} (${inspected})`
  447. }