|
1 | 1 | /** |
2 | | - * Export error constructors (or work-alikes) from lib/internal/errors.js |
| 2 | + * Caller-visible Errors thrown by this module. |
3 | 3 | * |
4 | | - * These are ugly hacks. Hopefully the constructors will be exposed in a |
5 | | - * future version: https://github.com/nodejs/node/issues/14554 |
| 4 | + * Based on Node.js core errors in lib/internal/errors.js @ v15.0.1. |
6 | 5 | * |
| 6 | + * Hopefully the constructors will be exposed in a future version: |
| 7 | + * https://github.com/nodejs/node/issues/14554 |
| 8 | + * |
| 9 | + * Copies are already proliferating: |
| 10 | + * https://github.com/nodejs/readable-stream/blob/v3.6.0/errors.js |
| 11 | + * https://github.com/streamich/memfs/blob/v3.2.0/src/internal/errors.ts |
| 12 | + * |
| 13 | + * Looks like there was an attempt to create a standalone module: |
| 14 | + * https://github.com/jasnell/internal-errors |
| 15 | + * |
| 16 | + * @copyright Copyright Joyent, Inc. and other Node contributors. |
7 | 17 | * @copyright Copyright 2020 Kevin Locke <kevin@kevinlocke.name> |
8 | 18 | * @license MIT |
9 | 19 | */ |
10 | 20 |
|
11 | 21 | 'use strict'; |
12 | 22 |
|
| 23 | +const ArrayIsArray = Array.isArray; |
| 24 | +const ObjectDefineProperty = Object.defineProperty; |
| 25 | + |
| 26 | +const messages = new Map(); |
| 27 | +const codes = exports; |
| 28 | + |
| 29 | +const classRegExp = /^([A-Z][a-z0-9]*)+$/; |
| 30 | +// Sorted by a rough estimate on most frequently used entries. |
| 31 | +const kTypes = [ |
| 32 | + 'string', |
| 33 | + 'function', |
| 34 | + 'number', |
| 35 | + 'object', |
| 36 | + // Accept 'Function' and 'Object' as alternative to the lower cased version. |
| 37 | + 'Function', |
| 38 | + 'Object', |
| 39 | + 'boolean', |
| 40 | + 'bigint', |
| 41 | + 'symbol' |
| 42 | +]; |
| 43 | + |
| 44 | +let excludedStackFn; |
| 45 | + |
| 46 | +let internalUtilInspect = null; |
| 47 | +function lazyInternalUtilInspect() { |
| 48 | + if (!internalUtilInspect) { |
| 49 | + internalUtilInspect = require('util'); |
| 50 | + } |
| 51 | + return internalUtilInspect; |
| 52 | +} |
| 53 | + |
13 | 54 | const assert = require('assert'); |
14 | | -const { kMaxLength } = require('buffer'); |
15 | | -const { Readable, finished } = require('stream'); |
16 | | -const { Deflate, deflate } = require('zlib'); |
17 | | - |
18 | | -// Get ERR_BUFFER_TOO_LARGE by monkey-patching .end() to pretend more than |
19 | | -// kMaxLength bytes have been read. |
20 | | -assert( |
21 | | - !hasOwnProperty.call(Deflate.prototype, 'end'), |
22 | | - 'Deflate.prototype does not define end', |
23 | | -); |
24 | | -Deflate.prototype.end = function() { |
25 | | - this.nread = kMaxLength + 1; |
26 | | - // Hit check in zlibBufferOnData for node >= 14.6.0 (ec804f231f) |
27 | | - this.emit('data', Buffer.alloc(0)); |
28 | | - // Hit check in zlibBufferOnEnd for node < 14.6.0 (ec804f231f) |
29 | | - this.close = () => {}; |
30 | | - this.emit('end'); |
31 | | -}; |
32 | | -try { |
33 | | - deflate(Buffer.alloc(0), (err) => { |
34 | | - assert.strictEqual(err && err.code, 'ERR_BUFFER_TOO_LARGE'); |
35 | | - exports.ERR_BUFFER_TOO_LARGE = err.constructor; |
36 | | - }); |
37 | | -} finally { |
38 | | - delete Deflate.prototype.end; |
| 55 | + |
| 56 | +function makeNodeErrorWithCode(Base, key) { |
| 57 | + return function NodeError(...args) { |
| 58 | + let error; |
| 59 | + if (excludedStackFn === undefined) { |
| 60 | + error = new Base(); |
| 61 | + } else { |
| 62 | + const limit = Error.stackTraceLimit; |
| 63 | + Error.stackTraceLimit = 0; |
| 64 | + error = new Base(); |
| 65 | + // Reset the limit and setting the name property. |
| 66 | + Error.stackTraceLimit = limit; |
| 67 | + } |
| 68 | + const message = getMessage(key, args, error); |
| 69 | + ObjectDefineProperty(error, 'message', { |
| 70 | + value: message, |
| 71 | + enumerable: false, |
| 72 | + writable: true, |
| 73 | + configurable: true, |
| 74 | + }); |
| 75 | + ObjectDefineProperty(error, 'toString', { |
| 76 | + value() { |
| 77 | + return `${this.name} [${key}]: ${this.message}`; |
| 78 | + }, |
| 79 | + enumerable: false, |
| 80 | + writable: true, |
| 81 | + configurable: true, |
| 82 | + }); |
| 83 | + addCodeToName(error, Base.name, key); |
| 84 | + error.code = key; |
| 85 | + return error; |
| 86 | + }; |
| 87 | +} |
| 88 | + |
| 89 | +function addCodeToName(err, name, code) { |
| 90 | + // Set the stack |
| 91 | + if (excludedStackFn !== undefined) { |
| 92 | + Error.captureStackTrace(err, excludedStackFn); |
| 93 | + } |
| 94 | + // Add the error code to the name to include it in the stack trace. |
| 95 | + err.name = `${name} [${code}]`; |
| 96 | + // Access the stack to generate the error message including the error code |
| 97 | + // from the name. |
| 98 | + // eslint-disable-next-line no-unused-expressions |
| 99 | + err.stack; |
| 100 | + // Reset the name to the actual name. |
| 101 | + if (name === 'SystemError') { |
| 102 | + ObjectDefineProperty(err, 'name', { |
| 103 | + value: name, |
| 104 | + enumerable: false, |
| 105 | + writable: true, |
| 106 | + configurable: true |
| 107 | + }); |
| 108 | + } else { |
| 109 | + delete err.name; |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +// Utility function for registering the error codes. Only used here. Exported |
| 114 | +// *only* to allow for testing. |
| 115 | +function E(sym, val, def) { |
| 116 | + messages.set(sym, val); |
| 117 | + def = makeNodeErrorWithCode(def, sym); |
| 118 | + codes[sym] = def; |
39 | 119 | } |
40 | | -assert( |
41 | | - exports.ERR_BUFFER_TOO_LARGE, |
42 | | - 'zlib.deflate calls callback immediately on error', |
43 | | -); |
44 | | - |
45 | | -// Get ERR_INVALID_ARG_TYPE by calling Buffer.alloc with an invalid type |
46 | | -try { |
47 | | - Buffer.alloc(true); |
48 | | -} catch (err) { |
49 | | - assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE'); |
50 | | - exports.ERR_INVALID_ARG_TYPE = err.constructor; |
| 120 | + |
| 121 | +function getMessage(key, args, self) { |
| 122 | + const msg = messages.get(key); |
| 123 | + |
| 124 | + if (typeof msg === 'function') { |
| 125 | + assert( |
| 126 | + msg.length <= args.length, // Default options do not count. |
| 127 | + `Code: ${key}; The provided arguments length (${args.length}) does not ` + |
| 128 | + `match the required ones (${msg.length}).` |
| 129 | + ); |
| 130 | + return msg.apply(self, args); |
| 131 | + } |
| 132 | + |
| 133 | + const expectedLength = (msg.match(/%[dfijoOs]/g) || []).length; |
| 134 | + assert( |
| 135 | + expectedLength === args.length, |
| 136 | + `Code: ${key}; The provided arguments length (${args.length}) does not ` + |
| 137 | + `match the required ones (${expectedLength}).` |
| 138 | + ); |
| 139 | + if (args.length === 0) |
| 140 | + return msg; |
| 141 | + |
| 142 | + args.unshift(msg); |
| 143 | + return lazyInternalUtilInspect().format.apply(null, args); |
51 | 144 | } |
52 | | -assert( |
53 | | - exports.ERR_INVALID_ARG_TYPE, |
54 | | - 'Buffer.alloc throws for Boolean argument', |
55 | | -); |
56 | | - |
57 | | -// Get ERR_STREAM_PREMATURE_CLOSE using stream.finish |
58 | | -const readable = new Readable(); |
59 | | -finished(readable, (err) => { |
60 | | - assert.strictEqual(err && err.code, 'ERR_STREAM_PREMATURE_CLOSE'); |
61 | | - exports.ERR_STREAM_PREMATURE_CLOSE = err.constructor; |
62 | | -}); |
63 | | -readable.emit('close'); |
64 | | -assert( |
65 | | - exports.ERR_STREAM_PREMATURE_CLOSE, |
66 | | - 'stream.finished calls callback on close', |
67 | | -); |
68 | | - |
69 | | -// eslint-disable-next-line unicorn/custom-error-definition |
70 | | -exports.ERR_SYNC_NOT_SUPPORTED = class InflateAutoError extends Error { |
| 145 | + |
| 146 | +E('ERR_BUFFER_TOO_LARGE', |
| 147 | + 'Cannot create a Buffer larger than %s bytes', |
| 148 | + RangeError); |
| 149 | +E('ERR_INVALID_ARG_TYPE', |
| 150 | + (name, expected, actual) => { |
| 151 | + assert(typeof name === 'string', "'name' must be a string"); |
| 152 | + if (!ArrayIsArray(expected)) { |
| 153 | + expected = [expected]; |
| 154 | + } |
| 155 | + |
| 156 | + let msg = 'The '; |
| 157 | + if (name.endsWith(' argument')) { |
| 158 | + // For cases like 'first argument' |
| 159 | + msg += `${name} `; |
| 160 | + } else { |
| 161 | + const type = name.includes('.') ? 'property' : 'argument'; |
| 162 | + msg += `"${name}" ${type} `; |
| 163 | + } |
| 164 | + msg += 'must be '; |
| 165 | + |
| 166 | + const types = []; |
| 167 | + const instances = []; |
| 168 | + const other = []; |
| 169 | + |
| 170 | + for (const value of expected) { |
| 171 | + assert(typeof value === 'string', |
| 172 | + 'All expected entries have to be of type string'); |
| 173 | + if (kTypes.includes(value)) { |
| 174 | + types.push(value.toLowerCase()); |
| 175 | + } else if (classRegExp.test(value)) { |
| 176 | + instances.push(value); |
| 177 | + } else { |
| 178 | + assert(value !== 'object', |
| 179 | + 'The value "object" should be written as "Object"'); |
| 180 | + other.push(value); |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + // Special handle `object` in case other instances are allowed to outline |
| 185 | + // the differences between each other. |
| 186 | + if (instances.length > 0) { |
| 187 | + const pos = types.indexOf('object'); |
| 188 | + if (pos !== -1) { |
| 189 | + types.splice(pos, 1); |
| 190 | + instances.push('Object'); |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + if (types.length > 0) { |
| 195 | + if (types.length > 2) { |
| 196 | + const last = types.pop(); |
| 197 | + msg += `one of type ${types.join(', ')}, or ${last}`; |
| 198 | + } else if (types.length === 2) { |
| 199 | + msg += `one of type ${types[0]} or ${types[1]}`; |
| 200 | + } else { |
| 201 | + msg += `of type ${types[0]}`; |
| 202 | + } |
| 203 | + if (instances.length > 0 || other.length > 0) |
| 204 | + msg += ' or '; |
| 205 | + } |
| 206 | + |
| 207 | + if (instances.length > 0) { |
| 208 | + if (instances.length > 2) { |
| 209 | + const last = instances.pop(); |
| 210 | + msg += `an instance of ${instances.join(', ')}, or ${last}`; |
| 211 | + } else { |
| 212 | + msg += `an instance of ${instances[0]}`; |
| 213 | + if (instances.length === 2) { |
| 214 | + msg += ` or ${instances[1]}`; |
| 215 | + } |
| 216 | + } |
| 217 | + if (other.length > 0) |
| 218 | + msg += ' or '; |
| 219 | + } |
| 220 | + |
| 221 | + if (other.length > 0) { |
| 222 | + if (other.length > 2) { |
| 223 | + const last = other.pop(); |
| 224 | + msg += `one of ${other.join(', ')}, or ${last}`; |
| 225 | + } else if (other.length === 2) { |
| 226 | + msg += `one of ${other[0]} or ${other[1]}`; |
| 227 | + } else { |
| 228 | + if (other[0].toLowerCase() !== other[0]) |
| 229 | + msg += 'an '; |
| 230 | + msg += `${other[0]}`; |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + if (actual == null) { |
| 235 | + msg += `. Received ${actual}`; |
| 236 | + } else if (typeof actual === 'function' && actual.name) { |
| 237 | + msg += `. Received function ${actual.name}`; |
| 238 | + } else if (typeof actual === 'object') { |
| 239 | + if (actual.constructor && actual.constructor.name) { |
| 240 | + msg += `. Received an instance of ${actual.constructor.name}`; |
| 241 | + } else { |
| 242 | + const inspected = lazyInternalUtilInspect() |
| 243 | + .inspect(actual, { depth: -1 }); |
| 244 | + msg += `. Received ${inspected}`; |
| 245 | + } |
| 246 | + } else { |
| 247 | + let inspected = lazyInternalUtilInspect() |
| 248 | + .inspect(actual, { colors: false }); |
| 249 | + if (inspected.length > 25) |
| 250 | + inspected = `${inspected.slice(0, 25)}...`; |
| 251 | + msg += `. Received type ${typeof actual} (${inspected})`; |
| 252 | + } |
| 253 | + return msg; |
| 254 | + }, TypeError); |
| 255 | +E('ERR_STREAM_PREMATURE_CLOSE', 'Premature close', Error); |
| 256 | + |
| 257 | +codes.ERR_SYNC_NOT_SUPPORTED = class InflateAutoError extends Error { |
71 | 258 | constructor(target) { |
72 | 259 | super(); |
73 | 260 | let message = 'Synchronous operation is not supported'; |
|
0 commit comments