diff --git a/errors-browser.js b/errors-browser.js index 26c5d2635e..ef7d85bbee 100644 --- a/errors-browser.js +++ b/errors-browser.js @@ -1,5 +1,7 @@ 'use strict'; +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } @@ -13,21 +15,21 @@ function createErrorType(code, message, Base) { Base = Error; } - function getMessage(arg1, arg2) { + function getMessage(arg1, arg2, arg3) { if (typeof message === 'string') { return message; } else { - return message(arg1, arg2); + return message(arg1, arg2, arg3); } } var NodeError = function (_Base) { _inherits(NodeError, _Base); - function NodeError(arg1, arg2) { + function NodeError(arg1, arg2, arg3) { _classCallCheck(this, NodeError); - return _possibleConstructorReturn(this, (NodeError.__proto__ || Object.getPrototypeOf(NodeError)).call(this, getMessage(arg1, arg2))); + return _possibleConstructorReturn(this, (NodeError.__proto__ || Object.getPrototypeOf(NodeError)).call(this, getMessage(arg1, arg2, arg3))); } return NodeError; @@ -39,16 +41,84 @@ function createErrorType(code, message, Base) { codes[code] = NodeError; } +// https://github.com/nodejs/node/blob/v10.8.0/lib/internal/errors.js +function oneOf(expected, thing) { + if (Array.isArray(expected)) { + var len = expected.length; + expected = expected.map(function (i) { + return String(i); + }); + if (len > 2) { + return 'one of ' + thing + ' ' + expected.slice(0, len - 1).join(', ') + ', or ' + expected[len - 1]; + } else if (len === 2) { + return 'one of ' + thing + ' ' + expected[0] + ' or ' + expected[1]; + } else { + return 'of ' + thing + ' ' + expected[0]; + } + } else { + return 'of ' + thing + ' ' + String(expected); + } +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith +function startsWith(str, search, pos) { + return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +function endsWith(str, search, this_len) { + if (this_len === undefined || this_len > str.length) { + this_len = str.length; + } + return str.substring(this_len - search.length, this_len) === search; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes +function includes(str, search, start) { + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > str.length) { + return false; + } else { + return str.indexOf(search, start) !== -1; + } +} + createErrorType('ERR_INVALID_OPT_VALUE', function (name, value) { return 'The value "' + value + '" is invalid for option "' + name + '"'; }, TypeError); -createErrorType('ERR_INVALID_ARG_TYPE', 'argument must be of the right type', TypeError); +createErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) { + // determiner: 'must be' or 'must not be' + var determiner = void 0; + if (typeof expected === 'string' && startsWith(expected, 'not ')) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + var msg = void 0; + if (endsWith(name, ' argument')) { + // For cases like 'first argument' + msg = 'The ' + name + ' ' + determiner + ' ' + oneOf(expected, 'type'); + } else { + var type = includes(name, '.') ? 'property' : 'argument'; + msg = 'The "' + name + '" ' + type + ' ' + determiner + ' ' + oneOf(expected, 'type'); + } + + msg += '. Received type ' + (typeof actual === 'undefined' ? 'undefined' : _typeof(actual)); + return msg; +}, TypeError); createErrorType('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF'); createErrorType('ERR_METHOD_NOT_IMPLEMENTED', function (name) { return 'The ' + name + ' method is not implemented'; }); -createErrorType('ERR_STREAM_PREMATURE_CLOSE', 'premature close'); -createErrorType('ERR_STREAM_DESTROYED', 'the stream was destroyed'); +createErrorType('ERR_STREAM_PREMATURE_CLOSE', 'Premature close'); +createErrorType('ERR_STREAM_DESTROYED', function (name) { + return 'Cannot call ' + name + ' after a stream was destroyed'; +}); createErrorType('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times'); createErrorType('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable'); createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end'); diff --git a/errors.js b/errors.js index 428398360b..8471526d6e 100644 --- a/errors.js +++ b/errors.js @@ -7,17 +7,17 @@ function createErrorType(code, message, Base) { Base = Error } - function getMessage (arg1, arg2) { + function getMessage (arg1, arg2, arg3) { if (typeof message === 'string') { return message } else { - return message(arg1, arg2) + return message(arg1, arg2, arg3) } } class NodeError extends Base { - constructor (arg1, arg2) { - super(getMessage(arg1, arg2)); + constructor (arg1, arg2, arg3) { + super(getMessage(arg1, arg2, arg3)); } } @@ -27,16 +27,83 @@ function createErrorType(code, message, Base) { codes[code] = NodeError; } +// https://github.com/nodejs/node/blob/v10.8.0/lib/internal/errors.js +function oneOf(expected, thing) { + if (Array.isArray(expected)) { + const len = expected.length; + expected = expected.map((i) => String(i)); + if (len > 2) { + return `one of ${thing} ${expected.slice(0, len - 1).join(', ')}, or ` + + expected[len - 1]; + } else if (len === 2) { + return `one of ${thing} ${expected[0]} or ${expected[1]}`; + } else { + return `of ${thing} ${expected[0]}`; + } + } else { + return `of ${thing} ${String(expected)}`; + } +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith +function startsWith(str, search, pos) { + return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +function endsWith(str, search, this_len) { + if (this_len === undefined || this_len > str.length) { + this_len = str.length; + } + return str.substring(this_len - search.length, this_len) === search; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes +function includes(str, search, start) { + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > str.length) { + return false; + } else { + return str.indexOf(search, start) !== -1; + } +} + createErrorType('ERR_INVALID_OPT_VALUE', function (name, value) { return 'The value "' + value + '" is invalid for option "' + name + '"' }, TypeError); -createErrorType('ERR_INVALID_ARG_TYPE', 'argument must be of the right type', TypeError); +createErrorType('ERR_INVALID_ARG_TYPE', function (name, expected, actual) { + // determiner: 'must be' or 'must not be' + let determiner; + if (typeof expected === 'string' && startsWith(expected, 'not ')) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + let msg; + if (endsWith(name, ' argument')) { + // For cases like 'first argument' + msg = `The ${name} ${determiner} ${oneOf(expected, 'type')}`; + } else { + const type = includes(name, '.') ? 'property' : 'argument'; + msg = `The "${name}" ${type} ${determiner} ${oneOf(expected, 'type')}`; + } + + msg += `. Received type ${typeof actual}`; + return msg; +}, TypeError); createErrorType('ERR_STREAM_PUSH_AFTER_EOF', 'stream.push() after EOF'); createErrorType('ERR_METHOD_NOT_IMPLEMENTED', function (name) { return 'The ' + name + ' method is not implemented' }); -createErrorType('ERR_STREAM_PREMATURE_CLOSE', 'premature close'); -createErrorType('ERR_STREAM_DESTROYED', 'the stream was destroyed'); +createErrorType('ERR_STREAM_PREMATURE_CLOSE', 'Premature close'); +createErrorType('ERR_STREAM_DESTROYED', function (name) { + return 'Cannot call ' + name + ' after a stream was destroyed'; +}); createErrorType('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times'); createErrorType('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable'); createErrorType('ERR_STREAM_WRITE_AFTER_END', 'write after end'); diff --git a/test/ours/errors.js b/test/ours/errors.js new file mode 100644 index 0000000000..fc32605923 --- /dev/null +++ b/test/ours/errors.js @@ -0,0 +1,148 @@ +var tap = require('tap'); +var assert = require('assert'); +var errors = require('../../errors').codes; + +function expect (err, Base, name, code, message) { + assert(err instanceof Base); + assert.strictEqual(err.name, name); + assert.strictEqual(err.code, code); + assert.strictEqual(err.message, message); +} + +expect( + new errors.ERR_INVALID_OPT_VALUE('name', 0), + TypeError, + 'TypeError', + 'ERR_INVALID_OPT_VALUE', + 'The value "0" is invalid for option "name"' +); + +expect( + new errors.ERR_INVALID_OPT_VALUE('name', undefined), + TypeError, + 'TypeError', + 'ERR_INVALID_OPT_VALUE', + 'The value "undefined" is invalid for option "name"' +); + +expect( + new errors.ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer', 'Uint8Array'], 0), + TypeError, + 'TypeError', + 'ERR_INVALID_ARG_TYPE', + 'The "chunk" argument must be one of type string, Buffer, or Uint8Array. Received type number' +); + +expect( + new errors.ERR_INVALID_ARG_TYPE('first argument', 'not string', 'foo'), + TypeError, + 'TypeError', + 'ERR_INVALID_ARG_TYPE', + 'The first argument must not be of type string. Received type string' +); + +expect( + new errors.ERR_INVALID_ARG_TYPE('obj.prop', 'string', undefined), + TypeError, + 'TypeError', + 'ERR_INVALID_ARG_TYPE', + 'The "obj.prop" property must be of type string. Received type undefined' +); + +expect( + new errors.ERR_STREAM_PUSH_AFTER_EOF(), + Error, + 'Error', + 'ERR_STREAM_PUSH_AFTER_EOF', + 'stream.push() after EOF' +); + +expect( + new errors.ERR_METHOD_NOT_IMPLEMENTED('_read()'), + Error, + 'Error', + 'ERR_METHOD_NOT_IMPLEMENTED', + 'The _read() method is not implemented' +); + +expect( + new errors.ERR_METHOD_NOT_IMPLEMENTED('_write()'), + Error, + 'Error', + 'ERR_METHOD_NOT_IMPLEMENTED', + 'The _write() method is not implemented' +); + +expect( + new errors.ERR_STREAM_PREMATURE_CLOSE(), + Error, + 'Error', + 'ERR_STREAM_PREMATURE_CLOSE', + 'Premature close' +); + +expect( + new errors.ERR_STREAM_DESTROYED('pipe'), + Error, + 'Error', + 'ERR_STREAM_DESTROYED', + 'Cannot call pipe after a stream was destroyed' +); + +expect( + new errors.ERR_STREAM_DESTROYED('write'), + Error, + 'Error', + 'ERR_STREAM_DESTROYED', + 'Cannot call write after a stream was destroyed' +); + +expect( + new errors.ERR_MULTIPLE_CALLBACK(), + Error, + 'Error', + 'ERR_MULTIPLE_CALLBACK', + 'Callback called multiple times' +); + +expect( + new errors.ERR_STREAM_CANNOT_PIPE(), + Error, + 'Error', + 'ERR_STREAM_CANNOT_PIPE', + 'Cannot pipe, not readable' +); + +expect( + new errors.ERR_STREAM_WRITE_AFTER_END(), + Error, + 'Error', + 'ERR_STREAM_WRITE_AFTER_END', + 'write after end' +); + +expect( + new errors.ERR_STREAM_NULL_VALUES(), + TypeError, + 'TypeError', + 'ERR_STREAM_NULL_VALUES', + 'May not write null values to stream' +); + +expect( + new errors.ERR_UNKNOWN_ENCODING('foo'), + TypeError, + 'TypeError', + 'ERR_UNKNOWN_ENCODING', + 'Unknown encoding: foo' +); + +expect( + new errors.ERR_STREAM_UNSHIFT_AFTER_END_EVENT(), + Error, + 'Error', + 'ERR_STREAM_UNSHIFT_AFTER_END_EVENT', + 'stream.unshift() after end event' +); + +require('tap').pass('sync done');