From d457a986a01b84c4cee3a952082caac3f118e1db Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Fri, 21 Apr 2017 11:54:58 -0700 Subject: [PATCH] url: port WHATWG URL API to internal/errors Also slightly revises grammar. PR-URL: https://github.com/nodejs/node/pull/12574 Refs: https://github.com/nodejs/node/issues/11273 Refs: https://github.com/nodejs/node/issues/11299 Reviewed-By: James M Snell Reviewed-By: Joyee Cheung Reviewed-By: Daijiro Wachi --- doc/api/errors.md | 81 ++++++++++++++++++ lib/internal/errors.js | 56 ++++++++++--- lib/internal/url.js | 83 ++++++++++--------- test/common.js | 4 +- test/parallel/test-fs-whatwg-url.js | 33 +++++--- test/parallel/test-internal-errors.js | 28 ++++++- test/parallel/test-url-format-whatwg.js | 18 ++-- test/parallel/test-whatwg-url-domainto.js | 8 +- test/parallel/test-whatwg-url-parsing.js | 8 +- .../test-whatwg-url-searchparams-append.js | 14 +++- ...est-whatwg-url-searchparams-constructor.js | 27 +++--- .../test-whatwg-url-searchparams-delete.js | 12 ++- .../test-whatwg-url-searchparams-entries.js | 14 +++- .../test-whatwg-url-searchparams-foreach.js | 6 +- .../test-whatwg-url-searchparams-get.js | 12 ++- .../test-whatwg-url-searchparams-getall.js | 12 ++- .../test-whatwg-url-searchparams-has.js | 12 ++- .../test-whatwg-url-searchparams-keys.js | 14 +++- .../test-whatwg-url-searchparams-set.js | 12 ++- ...est-whatwg-url-searchparams-stringifier.js | 6 +- .../test-whatwg-url-searchparams-values.js | 14 +++- test/parallel/test-whatwg-url-searchparams.js | 13 ++- 22 files changed, 374 insertions(+), 113 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 9adf0c2088f2c9..a46d847ca2b23f 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -563,6 +563,13 @@ found [here][online]. ## Node.js Error Codes + +### ERR_ARG_NOT_ITERABLE + +The `'ERR_ARG_NOT_ITERABLE'` error code is used generically to identify that an +iterable argument (i.e. a value that works with `for...of` loops) is required, +but not provided to a Node.js API. + ### ERR_INVALID_ARG_TYPE @@ -575,6 +582,76 @@ an argument of the wrong type has been passed to a Node.js API. The `'ERR_INVALID_CALLBACK'` error code is used generically to identify that a callback function is required and has not been provided to a Node.js API. + +### ERR_INVALID_FILE_URL_HOST + +An error with the `'ERR_INVALID_FILE_URL_HOST'` code may be thrown when a +Node.js API that consumes `file:` URLs (such as certain functions in the +[`fs`][] module) encounters a file URL with an incompatible host. Currently, +this situation can only occur on Unix-like systems, where only `localhost` or +an empty host is supported. + + +### ERR_INVALID_FILE_URL_PATH + +An error with the `'ERR_INVALID_FILE_URL_PATH'` code may be thrown when a +Node.js API that consumes `file:` URLs (such as certain functions in the +[`fs`][] module) encounters a file URL with an incompatible path. The exact +semantics for determining whether a path can be used is platform-dependent. + + +### ERR_INVALID_THIS + +The `'ERR_INVALID_THIS'` error code is used generically to identify that a +Node.js API function is called with an incompatible `this` value. + +Example: + +```js +const { URLSearchParams } = require('url'); +const urlSearchParams = new URLSearchParams('foo=bar&baz=new'); + +const buf = Buffer.alloc(1); +urlSearchParams.has.call(buf, 'foo'); + // Throws a TypeError with code 'ERR_INVALID_THIS' +``` + + +### ERR_INVALID_TUPLE + +An error with code `'ERR_INVALID_TUPLE'` is thrown when an element in the +`iterable` provided to the [WHATWG][WHATWG URL API] [`URLSearchParams` +constructor][`new URLSearchParams(iterable)`] does not represent a `[name, +value]` tuple – that is, if an element is not iterable, or does not consist of +exactly two elements. + + +### ERR_INVALID_URL + +An error using the `'ERR_INVALID_URL'` code is thrown when an invalid URL is +passed to the [WHATWG][WHATWG URL API] [`URL` constructor][`new URL(input)`] to +be parsed. The thrown error object typically has an additional property +`'input'` that contains the URL that failed to parse. + + +### ERR_INVALID_URL_SCHEME + +The code `'ERR_INVALID_URL_SCHEME'` is used generically to signify an attempt +to use a URL of an incompatible scheme (aka protocol) for a specific purpose. +It is currently only used in the [WHATWG URL API][] support in the [`fs`][] +module (which only accepts URLs with `'file'` scheme), but may be used in other +Node.js APIs as well in the future. + + +### ERR_MISSING_ARGS + +The `'ERR_MISSING_ARGS'` error code is a generic error code for instances where +a required argument of a Node.js API is not passed. This is currently only used +in the [WHATWG URL API][] for strict compliance with the specification (which +in some cases may accept `func(undefined)` but not `func()`). In most native +Node.js APIs, `func(undefined)` and `func()` are treated identically, and the +[`ERR_INVALID_ARG_TYPE`][] error code may be used instead. + ### ERR_STDERR_CLOSE @@ -627,11 +704,15 @@ likely an indication of a bug within Node.js itself. [`process.on('uncaughtException')`]: process.html#process_event_uncaughtexception [domains]: domain.html [event emitter-based]: events.html#events_class_eventemitter +[`ERR_INVALID_ARG_TYPE`]: #ERR_INVALID_ARG_TYPE [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor [Node.js Error Codes]: #nodejs-error-codes [online]: http://man7.org/linux/man-pages/man3/errno.3.html [stream-based]: stream.html [syscall]: http://man7.org/linux/man-pages/man2/syscall.2.html [try-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch +[`new URL(input)`]: url.html#url_constructor_new_url_input_base +[`new URLSearchParams(iterable)`]: url.html#url_constructor_new_urlsearchparams_iterable [V8's stack trace API]: https://github.com/v8/v8/wiki/Stack-Trace-API [vm]: vm.html +[WHATWG URL API]: url.html#url_the_whatwg_url_api diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 58cc4979392b57..c2d3bc71e9fd4e 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -79,9 +79,18 @@ module.exports = exports = { // Any error code added here should also be added to the documentation // // Note: Please try to keep these in alphabetical order +E('ERR_ARG_NOT_ITERABLE', '%s must be iterable'); E('ERR_ASSERTION', (msg) => msg); E('ERR_INVALID_ARG_TYPE', invalidArgType); E('ERR_INVALID_CALLBACK', 'callback must be a function'); +E('ERR_INVALID_FILE_URL_HOST', 'File URL host %s'); +E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s'); +E('ERR_INVALID_THIS', 'Value of "this" must be of type %s'); +E('ERR_INVALID_TUPLE', '%s must be an iterable %s tuple'); +E('ERR_INVALID_URL', 'Invalid URL: %s'); +E('ERR_INVALID_URL_SCHEME', + (expected) => `The URL must be ${oneOf(expected, 'scheme')}`); +E('ERR_MISSING_ARGS', missingArgs); E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed'); E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed'); E('ERR_UNKNOWN_STDIN_TYPE', 'Unknown stdin file type'); @@ -91,22 +100,49 @@ E('ERR_UNKNOWN_BUILTIN_MODULE', (id) => `No such built-in module: ${id}`); function invalidArgType(name, expected, actual) { assert(name, 'name is required'); + var msg = `The "${name}" argument must be ${oneOf(expected, 'type')}`; + if (arguments.length >= 3) { + msg += `. Received type ${actual !== null ? typeof actual : 'null'}`; + } + return msg; +} + +function missingArgs(...args) { + assert(args.length > 0, 'At least one arg needs to be specified'); + let msg = 'The '; + const len = args.length; + args = args.map((a) => `"${a}"`); + switch (len) { + case 1: + msg += `${args[0]} argument`; + break; + case 2: + msg += `${args[0]} and ${args[1]} arguments`; + break; + default: + msg += args.slice(0, len - 1).join(', '); + msg += `, and ${args[len - 1]} arguments`; + break; + } + return `${msg} must be specified`; +} + +function oneOf(expected, thing) { assert(expected, 'expected is required'); - var msg = `The "${name}" argument must be `; + assert(typeof thing === 'string', 'thing is required'); if (Array.isArray(expected)) { - var len = expected.length; + const len = expected.length; + assert(len > 0, 'At least one expected value needs to be specified'); expected = expected.map((i) => String(i)); - if (len > 1) { - msg += `one of type ${expected.slice(0, len - 1).join(', ')}, or ` + + 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 { - msg += `of type ${expected[0]}`; + return `of ${thing} ${expected[0]}`; } } else { - msg += `of type ${String(expected)}`; - } - if (arguments.length >= 3) { - msg += `. Received type ${actual !== null ? typeof actual : 'null'}`; + return `of ${thing} ${String(expected)}`; } - return msg; } diff --git a/lib/internal/url.js b/lib/internal/url.js index 25a4f51ba23542..659a441a6007c4 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -6,6 +6,7 @@ const { isHexTable } = require('internal/querystring'); const { getConstructorOf } = require('internal/util'); +const errors = require('internal/errors'); const binding = process.binding('url'); const context = Symbol('context'); const cannotBeBase = Symbol('cannot-be-base'); @@ -14,9 +15,9 @@ const cannotHaveUsernamePasswordPort = const special = Symbol('special'); const searchParams = Symbol('query'); const querystring = require('querystring'); -const os = require('os'); -const isWindows = process.platform === 'win32'; +const { platform } = process; +const isWindows = platform === 'win32'; const kFormat = Symbol('format'); @@ -88,7 +89,7 @@ function onParseComplete(flags, protocol, username, password, } function onParseError(flags, input) { - const error = new TypeError('Invalid URL: ' + input); + const error = new errors.TypeError('ERR_INVALID_URL', input); error.input = input; throw error; } @@ -202,7 +203,7 @@ class URL { [util.inspect.custom](depth, opts) { if (this == null || Object.getPrototypeOf(this[context]) !== URLContext.prototype) { - throw new TypeError('Value of `this` is not a URL'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URL'); } const ctx = this[context]; @@ -247,7 +248,7 @@ Object.defineProperties(URL.prototype, { // eslint-disable-next-line func-name-matching value: function format(options) { if (options && typeof options !== 'object') - throw new TypeError('options must be an object'); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'object'); options = Object.assign({ fragment: true, unicode: false, @@ -810,7 +811,7 @@ class URLSearchParams { this[searchParams] = childParams.slice(); } else if (method !== null && method !== undefined) { if (typeof method !== 'function') { - throw new TypeError('Query pairs must be iterable'); + throw new errors.TypeError('ERR_ARG_NOT_ITERABLE', 'Query pairs'); } // sequence> @@ -819,7 +820,8 @@ class URLSearchParams { for (const pair of init) { if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { - throw new TypeError('Each query pair must be iterable'); + throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair', + '[name, value]'); } pairs.push(Array.from(pair)); } @@ -827,7 +829,8 @@ class URLSearchParams { this[searchParams] = []; for (const pair of pairs) { if (pair.length !== 2) { - throw new TypeError('Each query pair must be a name/value tuple'); + throw new errors.TypeError('ERR_INVALID_TUPLE', 'Each query pair', + '[name, value]'); } const key = toUSVString(pair[0]); const value = toUSVString(pair[1]); @@ -855,7 +858,7 @@ class URLSearchParams { [util.inspect.custom](recurseTimes, ctx) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (typeof recurseTimes === 'number' && recurseTimes < 0) @@ -920,10 +923,10 @@ function merge(out, start, mid, end, lBuffer, rBuffer) { defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { append(name, value) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 2) { - throw new TypeError('"name" and "value" arguments must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name', 'value'); } name = toUSVString(name); @@ -934,10 +937,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { delete(name) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 1) { - throw new TypeError('"name" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name'); } const list = this[searchParams]; @@ -955,10 +958,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { get(name) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 1) { - throw new TypeError('"name" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name'); } const list = this[searchParams]; @@ -973,10 +976,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { getAll(name) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 1) { - throw new TypeError('"name" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name'); } const list = this[searchParams]; @@ -992,10 +995,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { has(name) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 1) { - throw new TypeError('"name" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name'); } const list = this[searchParams]; @@ -1010,10 +1013,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { set(name, value) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (arguments.length < 2) { - throw new TypeError('"name" and "value" arguments must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'name', 'value'); } const list = this[searchParams]; @@ -1098,7 +1101,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { // must be set to `entries`. entries() { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } return createSearchParamsIterator(this, 'key+value'); @@ -1106,10 +1109,10 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { forEach(callback, thisArg = undefined) { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } if (typeof callback !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('ERR_INVALID_CALLBACK'); } let list = this[searchParams]; @@ -1128,7 +1131,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { // https://heycam.github.io/webidl/#es-iterable keys() { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } return createSearchParamsIterator(this, 'key'); @@ -1136,7 +1139,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { values() { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } return createSearchParamsIterator(this, 'value'); @@ -1146,7 +1149,7 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', { // https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior toString() { if (!this || !this[searchParams] || this[searchParams][searchParams]) { - throw new TypeError('Value of `this` is not a URLSearchParams'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParams'); } return serializeParams(this[searchParams]); @@ -1178,7 +1181,7 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', { next() { if (!this || Object.getPrototypeOf(this) !== URLSearchParamsIteratorPrototype) { - throw new TypeError('Value of `this` is not a URLSearchParamsIterator'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParamsIterator'); } const { @@ -1215,7 +1218,7 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', { }, [util.inspect.custom](recurseTimes, ctx) { if (this == null || this[context] == null || this[context].target == null) - throw new TypeError('Value of `this` is not a URLSearchParamsIterator'); + throw new errors.TypeError('ERR_INVALID_THIS', 'URLSearchParamsIterator'); if (typeof recurseTimes === 'number' && recurseTimes < 0) return ctx.stylize('[Object]', 'special'); @@ -1254,7 +1257,7 @@ defineIDLClass(URLSearchParamsIteratorPrototype, 'URLSearchParamsIterator', { function domainToASCII(domain) { if (arguments.length < 1) - throw new TypeError('"domain" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'domain'); // toUSVString is not needed. return binding.domainToASCII(`${domain}`); @@ -1262,7 +1265,7 @@ function domainToASCII(domain) { function domainToUnicode(domain) { if (arguments.length < 1) - throw new TypeError('"domain" argument must be specified'); + throw new errors.TypeError('ERR_MISSING_ARGS', 'domain'); // toUSVString is not needed. return binding.domainToUnicode(`${domain}`); @@ -1299,8 +1302,9 @@ function getPathFromURLWin32(url) { var third = pathname.codePointAt(n + 2) | 0x20; if ((pathname[n + 1] === '2' && third === 102) || // 2f 2F / (pathname[n + 1] === '5' && third === 99)) { // 5c 5C \ - return new TypeError( - 'Path must not include encoded \\ or / characters'); + return new errors.TypeError( + 'ERR_INVALID_FILE_URL_PATH', + 'must not include encoded \\ or / characters'); } } } @@ -1319,7 +1323,8 @@ function getPathFromURLWin32(url) { var sep = pathname[2]; if (letter < 97 || letter > 122 || // a..z A..Z (sep !== ':')) { - return new TypeError('File URLs must specify absolute paths'); + return new errors.TypeError('ERR_INVALID_FILE_URL_PATH', + 'must be absolute'); } return pathname.slice(1); } @@ -1327,16 +1332,16 @@ function getPathFromURLWin32(url) { function getPathFromURLPosix(url) { if (url.hostname !== '') { - return new TypeError( - `File URLs on ${os.platform()} must use hostname 'localhost'` + - ' or not specify any hostname'); + return new errors.TypeError('ERR_INVALID_FILE_URL_HOST', + `must be "localhost" or empty on ${platform}`); } var pathname = url.pathname; for (var n = 0; n < pathname.length; n++) { if (pathname[n] === '%') { var third = pathname.codePointAt(n + 2) | 0x20; if (pathname[n + 1] === '2' && third === 102) { - return new TypeError('Path must not include encoded / characters'); + return new errors.TypeError('ERR_INVALID_FILE_URL_PATH', + 'must not include encoded / characters'); } } } @@ -1349,7 +1354,7 @@ function getPathFromURL(path) { return path; } if (path.protocol !== 'file:') - return new TypeError('Only `file:` URLs are supported'); + return new errors.TypeError('ERR_INVALID_URL_SCHEME', 'file'); return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path); } diff --git a/test/common.js b/test/common.js index 6fe2d4520f6a08..3a49a53a11b14b 100644 --- a/test/common.js +++ b/test/common.js @@ -617,7 +617,9 @@ exports.WPT = { assert_false: (value, message) => assert.strictEqual(value, false, message), assert_throws: (code, func, desc) => { assert.throws(func, (err) => { - return typeof err === 'object' && 'name' in err && err.name === code.name; + return typeof err === 'object' && + 'name' in err && + err.name.startsWith(code.name); }, desc); }, assert_array_equals: assert.deepStrictEqual, diff --git a/test/parallel/test-fs-whatwg-url.js b/test/parallel/test-fs-whatwg-url.js index 7a94fd68d7e4fa..ff2ee641e12a7e 100644 --- a/test/parallel/test-fs-whatwg-url.js +++ b/test/parallel/test-fs-whatwg-url.js @@ -30,9 +30,11 @@ fs.readFile(url, common.mustCall((err, data) => { // Check that using a non file:// URL reports an error const httpUrl = new URL('http://example.org'); fs.readFile(httpUrl, common.mustCall((err) => { - assert(err); - assert.strictEqual(err.message, - 'Only `file:` URLs are supported'); + common.expectsError({ + code: 'ERR_INVALID_URL_SCHEME', + type: TypeError, + message: 'The URL must be of scheme file' + })(err); })); // pct-encoded characters in the path will be decoded and checked @@ -46,25 +48,30 @@ if (common.isWindows) { // encoded back and forward slashes are not permitted on windows ['%2f', '%2F', '%5c', '%5C'].forEach((i) => { fs.readFile(new URL(`file:///c:/tmp/${i}`), common.mustCall((err) => { - assert(err); - assert.strictEqual(err.message, - 'Path must not include encoded \\ or / characters'); + common.expectsError({ + code: 'ERR_INVALID_FILE_URL_PATH', + type: TypeError, + message: 'File URL path must not include encoded \\ or / characters' + })(err); })); }); } else { // encoded forward slashes are not permitted on other platforms ['%2f', '%2F'].forEach((i) => { fs.readFile(new URL(`file:///c:/tmp/${i}`), common.mustCall((err) => { - assert(err); - assert.strictEqual(err.message, - 'Path must not include encoded / characters'); + common.expectsError({ + code: 'ERR_INVALID_FILE_URL_PATH', + type: TypeError, + message: 'File URL path must not include encoded / characters' + })(err); })); }); fs.readFile(new URL('file://hostname/a/b/c'), common.mustCall((err) => { - assert(err); - assert.strictEqual(err.message, - `File URLs on ${os.platform()} must use ` + - 'hostname \'localhost\' or not specify any hostname'); + common.expectsError({ + code: 'ERR_INVALID_FILE_URL_HOST', + type: TypeError, + message: `File URL host must be "localhost" or empty on ${os.platform()}` + })(err); })); } diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js index fbd627b0301dfd..dd4f02c1f758bf 100644 --- a/test/parallel/test-internal-errors.js +++ b/test/parallel/test-internal-errors.js @@ -137,7 +137,7 @@ assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', 'b']), assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', ['b']]), 'The "a" argument must be of type b'); assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', ['b', 'c']]), - 'The "a" argument must be one of type b, or c'); + 'The "a" argument must be one of type b or c'); assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', ['b', 'c', 'd']]), 'The "a" argument must be one of type b, c, or d'); @@ -150,3 +150,29 @@ assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', assert.strictEqual(errors.message('ERR_INVALID_ARG_TYPE', ['a', 'b', null]), 'The "a" argument must be of type b. Received type null'); + +// Test ERR_INVALID_URL_SCHEME +assert.strictEqual(errors.message('ERR_INVALID_URL_SCHEME', ['file']), + 'The URL must be of scheme file'); +assert.strictEqual(errors.message('ERR_INVALID_URL_SCHEME', [['file']]), + 'The URL must be of scheme file'); +assert.strictEqual(errors.message('ERR_INVALID_URL_SCHEME', [['http', 'ftp']]), + 'The URL must be one of scheme http or ftp'); +assert.strictEqual(errors.message('ERR_INVALID_URL_SCHEME', [['a', 'b', 'c']]), + 'The URL must be one of scheme a, b, or c'); +assert.throws( + () => errors.message('ERR_INVALID_URL_SCHEME', [[]]), + /^AssertionError: At least one expected value needs to be specified$/ +); + +// Test ERR_MISSING_ARGS +assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['name']), + 'The "name" argument must be specified'); +assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['name', 'value']), + 'The "name" and "value" arguments must be specified'); +assert.strictEqual(errors.message('ERR_MISSING_ARGS', ['a', 'b', 'c']), + 'The "a", "b", and "c" arguments must be specified'); +assert.throws( + () => errors.message('ERR_MISSING_ARGS'), + /^AssertionError: At least one arg needs to be specified$/ +); diff --git a/test/parallel/test-url-format-whatwg.js b/test/parallel/test-url-format-whatwg.js index 507d3f8419d73e..f9e5691ae5cc04 100644 --- a/test/parallel/test-url-format-whatwg.js +++ b/test/parallel/test-url-format-whatwg.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const url = require('url'); const URL = url.URL; @@ -17,11 +17,17 @@ assert.strictEqual( 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' ); -const errreg = /^TypeError: options must be an object$/; -assert.throws(() => url.format(myURL, true), errreg); -assert.throws(() => url.format(myURL, 1), errreg); -assert.throws(() => url.format(myURL, 'test'), errreg); -assert.throws(() => url.format(myURL, Infinity), errreg); +{ + const expectedErr = common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "options" argument must be of type object' + }); + assert.throws(() => url.format(myURL, true), expectedErr); + assert.throws(() => url.format(myURL, 1), expectedErr); + assert.throws(() => url.format(myURL, 'test'), expectedErr); + assert.throws(() => url.format(myURL, Infinity), expectedErr); +} // Any falsy value other than undefined will be treated as false. // Any truthy value will be treated as true. diff --git a/test/parallel/test-whatwg-url-domainto.js b/test/parallel/test-whatwg-url-domainto.js index 70b32c8dce279c..13ff9968705b98 100644 --- a/test/parallel/test-whatwg-url-domainto.js +++ b/test/parallel/test-whatwg-url-domainto.js @@ -13,10 +13,10 @@ const { domainToASCII, domainToUnicode } = require('url'); const tests = require('../fixtures/url-idna.js'); { - assert.throws(() => domainToASCII(), - /^TypeError: "domain" argument must be specified$/); - assert.throws(() => domainToUnicode(), - /^TypeError: "domain" argument must be specified$/); + const expectedError = common.expectsError( + { code: 'ERR_MISSING_ARGS', type: TypeError }); + assert.throws(() => domainToASCII(), expectedError); + assert.throws(() => domainToUnicode(), expectedError); assert.strictEqual(domainToASCII(undefined), 'undefined'); assert.strictEqual(domainToUnicode(undefined), 'undefined'); } diff --git a/test/parallel/test-whatwg-url-parsing.js b/test/parallel/test-whatwg-url-parsing.js index c82461c25e4383..0e8f3b6d77cb56 100644 --- a/test/parallel/test-whatwg-url-parsing.js +++ b/test/parallel/test-whatwg-url-parsing.js @@ -26,12 +26,18 @@ const failureTests = tests.filter((test) => test.failure).concat([ { input: common.noop } ]); +const expectedError = common.expectsError( + { code: 'ERR_INVALID_URL', type: TypeError }); + for (const test of failureTests) { assert.throws( () => new URL(test.input, test.base), (error) => { + if (!expectedError(error)) + return false; + // The input could be processed, so we don't do strict matching here - const match = (error + '').match(/^TypeError: Invalid URL: (.*)$/); + const match = (error + '').match(/Invalid URL: (.*)$/); if (!match) { return false; } diff --git a/test/parallel/test-whatwg-url-searchparams-append.js b/test/parallel/test-whatwg-url-searchparams-append.js index ff4a568c303668..9d1eaceba1223c 100644 --- a/test/parallel/test-whatwg-url-searchparams-append.js +++ b/test/parallel/test-whatwg-url-searchparams-append.js @@ -53,10 +53,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.append.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { - params.set('a'); - }, /^TypeError: "name" and "value" arguments must be specified$/); + params.append('a'); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" and "value" arguments must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-constructor.js b/test/parallel/test-whatwg-url-searchparams-constructor.js index da459fe99c7fb8..e22ceec57083d0 100644 --- a/test/parallel/test-whatwg-url-searchparams-constructor.js +++ b/test/parallel/test-whatwg-url-searchparams-constructor.js @@ -188,24 +188,31 @@ test(() => { } { + const iterableError = common.expectsError({ + code: 'ERR_ARG_NOT_ITERABLE', + type: TypeError, + message: 'Query pairs must be iterable' + }); + const tupleError = common.expectsError({ + code: 'ERR_INVALID_TUPLE', + type: TypeError, + message: 'Each query pair must be an iterable [name, value] tuple' + }); + let params; // URLSearchParams constructor, undefined and null as argument params = new URLSearchParams(undefined); assert.strictEqual(params.toString(), ''); params = new URLSearchParams(null); assert.strictEqual(params.toString(), ''); - assert.throws(() => new URLSearchParams([[1]]), - /^TypeError: Each query pair must be a name\/value tuple$/); - assert.throws(() => new URLSearchParams([[1, 2, 3]]), - /^TypeError: Each query pair must be a name\/value tuple$/); + assert.throws(() => new URLSearchParams([[1]]), tupleError); + assert.throws(() => new URLSearchParams([[1, 2, 3]]), tupleError); assert.throws(() => new URLSearchParams({ [Symbol.iterator]: 42 }), - /^TypeError: Query pairs must be iterable$/); - assert.throws(() => new URLSearchParams([{}]), - /^TypeError: Each query pair must be iterable$/); - assert.throws(() => new URLSearchParams(['a']), - /^TypeError: Each query pair must be iterable$/); + iterableError); + assert.throws(() => new URLSearchParams([{}]), tupleError); + assert.throws(() => new URLSearchParams(['a']), tupleError); assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]), - /^TypeError: Each query pair must be iterable$/); + tupleError); } { diff --git a/test/parallel/test-whatwg-url-searchparams-delete.js b/test/parallel/test-whatwg-url-searchparams-delete.js index 589fbc2f8698b5..bb3fcc9549c411 100644 --- a/test/parallel/test-whatwg-url-searchparams-delete.js +++ b/test/parallel/test-whatwg-url-searchparams-delete.js @@ -47,10 +47,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.delete.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { params.delete(); - }, /^TypeError: "name" argument must be specified$/); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" argument must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-entries.js b/test/parallel/test-whatwg-url-searchparams-entries.js index c8b8d129b01ad9..1dfcdb0338dffb 100644 --- a/test/parallel/test-whatwg-url-searchparams-entries.js +++ b/test/parallel/test-whatwg-url-searchparams-entries.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const URLSearchParams = require('url').URLSearchParams; @@ -28,7 +28,15 @@ assert.deepStrictEqual(entries.next(), { assert.throws(() => { entries.next.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParamsIterator$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParamsIterator' +})); assert.throws(() => { params.entries.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParams$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' +})); diff --git a/test/parallel/test-whatwg-url-searchparams-foreach.js b/test/parallel/test-whatwg-url-searchparams-foreach.js index 9181371ae51c89..ea85da21a32a87 100644 --- a/test/parallel/test-whatwg-url-searchparams-foreach.js +++ b/test/parallel/test-whatwg-url-searchparams-foreach.js @@ -50,5 +50,9 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.forEach.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); } diff --git a/test/parallel/test-whatwg-url-searchparams-get.js b/test/parallel/test-whatwg-url-searchparams-get.js index 5e81be4f32cc1d..cc34373528cc1f 100644 --- a/test/parallel/test-whatwg-url-searchparams-get.js +++ b/test/parallel/test-whatwg-url-searchparams-get.js @@ -38,10 +38,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.get.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { params.get(); - }, /^TypeError: "name" argument must be specified$/); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" argument must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-getall.js b/test/parallel/test-whatwg-url-searchparams-getall.js index f80f45d5427e77..9c060ecb6d97a0 100644 --- a/test/parallel/test-whatwg-url-searchparams-getall.js +++ b/test/parallel/test-whatwg-url-searchparams-getall.js @@ -42,10 +42,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.getAll.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { params.getAll(); - }, /^TypeError: "name" argument must be specified$/); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" argument must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-has.js b/test/parallel/test-whatwg-url-searchparams-has.js index f2696063b998a1..fac4364081be88 100644 --- a/test/parallel/test-whatwg-url-searchparams-has.js +++ b/test/parallel/test-whatwg-url-searchparams-has.js @@ -41,10 +41,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.has.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { params.has(); - }, /^TypeError: "name" argument must be specified$/); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" argument must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-keys.js b/test/parallel/test-whatwg-url-searchparams-keys.js index 6942b152cd78e8..cbf0766bc2e6c4 100644 --- a/test/parallel/test-whatwg-url-searchparams-keys.js +++ b/test/parallel/test-whatwg-url-searchparams-keys.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const URLSearchParams = require('url').URLSearchParams; @@ -29,7 +29,15 @@ assert.deepStrictEqual(keys.next(), { assert.throws(() => { keys.next.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParamsIterator$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParamsIterator' +})); assert.throws(() => { params.keys.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParams$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' +})); diff --git a/test/parallel/test-whatwg-url-searchparams-set.js b/test/parallel/test-whatwg-url-searchparams-set.js index acd62955d22a44..a630355b6e3178 100644 --- a/test/parallel/test-whatwg-url-searchparams-set.js +++ b/test/parallel/test-whatwg-url-searchparams-set.js @@ -39,10 +39,18 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.set.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); assert.throws(() => { params.set('a'); - }, /^TypeError: "name" and "value" arguments must be specified$/); + }, common.expectsError({ + code: 'ERR_MISSING_ARGS', + type: TypeError, + message: 'The "name" and "value" arguments must be specified' + })); const obj = { toString() { throw new Error('toString'); }, diff --git a/test/parallel/test-whatwg-url-searchparams-stringifier.js b/test/parallel/test-whatwg-url-searchparams-stringifier.js index ac09979e027b7c..327611bc68a8b9 100644 --- a/test/parallel/test-whatwg-url-searchparams-stringifier.js +++ b/test/parallel/test-whatwg-url-searchparams-stringifier.js @@ -127,5 +127,9 @@ test(function() { const params = new URLSearchParams(); assert.throws(() => { params.toString.call(undefined); - }, /^TypeError: Value of `this` is not a URLSearchParams$/); + }, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' + })); } diff --git a/test/parallel/test-whatwg-url-searchparams-values.js b/test/parallel/test-whatwg-url-searchparams-values.js index eb6fa05daf3496..96f1a0fddbfb9f 100644 --- a/test/parallel/test-whatwg-url-searchparams-values.js +++ b/test/parallel/test-whatwg-url-searchparams-values.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const URLSearchParams = require('url').URLSearchParams; @@ -29,7 +29,15 @@ assert.deepStrictEqual(values.next(), { assert.throws(() => { values.next.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParamsIterator$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParamsIterator' +})); assert.throws(() => { params.values.call(undefined); -}, /^TypeError: Value of `this` is not a URLSearchParams$/); +}, common.expectsError({ + code: 'ERR_INVALID_THIS', + type: TypeError, + message: 'Value of "this" must be of type URLSearchParams' +})); diff --git a/test/parallel/test-whatwg-url-searchparams.js b/test/parallel/test-whatwg-url-searchparams.js index c7acb7d909d98c..5bb9cf407dc1e9 100644 --- a/test/parallel/test-whatwg-url-searchparams.js +++ b/test/parallel/test-whatwg-url-searchparams.js @@ -71,10 +71,15 @@ sp.forEach(function(val, key, obj) { sp.forEach(function() { assert.strictEqual(this, m); }, m); -assert.throws(() => sp.forEach(), - /^TypeError: "callback" argument must be a function$/); -assert.throws(() => sp.forEach(1), - /^TypeError: "callback" argument must be a function$/); + +{ + const callbackErr = common.expectsError({ + code: 'ERR_INVALID_CALLBACK', + type: TypeError + }); + assert.throws(() => sp.forEach(), callbackErr); + assert.throws(() => sp.forEach(1), callbackErr); +} m.search = '?a=a&b=b'; assert.strictEqual(sp.toString(), 'a=a&b=b');