diff --git a/doc/api/assert.md b/doc/api/assert.md index 9868601fa478c8..98b4a6837c92d3 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -430,6 +430,42 @@ parameter is undefined, a default error message is assigned. If the `message` parameter is an instance of an [`Error`][] then it will be thrown instead of the `AssertionError`. +## `assert.doesNotMatch(string, regexp[, message])` +<!-- YAML +added: REPLACEME +--> + +* `string` {string} +* `regexp` {RegExp} +* `message` {string|Error} + +> Stability: 1 - Experimental + +Expects the `string` input not to match the regular expression. + +This feature is currently experimental and the name might change or it might be +completely removed again. + +```js +const assert = require('assert').strict; + +assert.doesNotMatch('I will fail', /fail/); +// AssertionError [ERR_ASSERTION]: The input was expected to not match the ... + +assert.doesNotMatch(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.doesNotMatch('I will pass', /different/); +// OK +``` + +If the values do match, or if the `string` argument is of another type than +`string`, an [`AssertionError`][] is thrown with a `message` property set equal +to the value of the `message` parameter. If the `message` parameter is +undefined, a default error message is assigned. If the `message` parameter is an +instance of an [`Error`][] then it will be thrown instead of the +[`AssertionError`][]. + ## `assert.doesNotReject(asyncFn[, error][, message])` <!-- YAML added: v10.0.0 @@ -741,6 +777,42 @@ let err; // at errorFrame ``` +## `assert.match(string, regexp[, message])` +<!-- YAML +added: REPLACEME +--> + +* `string` {string} +* `regexp` {RegExp} +* `message` {string|Error} + +> Stability: 1 - Experimental + +Expects the `string` input to match the regular expression. + +This feature is currently experimental and the name might change or it might be +completely removed again. + +```js +const assert = require('assert').strict; + +assert.match('I will fail', /pass/); +// AssertionError [ERR_ASSERTION]: The input did not match the regular ... + +assert.match(123, /pass/); +// AssertionError [ERR_ASSERTION]: The "string" argument must be of type string. + +assert.match('I will pass', /pass/); +// OK +``` + +If the values do not match, or if the `string` argument is of another type than +`string`, an [`AssertionError`][] is thrown with a `message` property set equal +to the value of the `message` parameter. If the `message` parameter is +undefined, a default error message is assigned. If the `message` parameter is an +instance of an [`Error`][] then it will be thrown instead of the +[`AssertionError`][]. + ## `assert.notDeepEqual(actual, expected[, message])` <!-- YAML added: v0.1.21 diff --git a/lib/assert.js b/lib/assert.js index 8cf83caf3db8de..e937fb159ea448 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -25,7 +25,8 @@ const { ObjectIs, ObjectKeys, ObjectPrototypeIsPrototypeOf, - NumberIsNaN + NumberIsNaN, + RegExpPrototypeTest, } = primordials; const { Buffer } = require('buffer'); @@ -533,7 +534,7 @@ class Comparison { if (actual !== undefined && typeof actual[key] === 'string' && isRegExp(obj[key]) && - obj[key].test(actual[key])) { + RegExpPrototypeTest(obj[key], actual[key])) { this[key] = actual[key]; } else { this[key] = obj[key]; @@ -579,7 +580,7 @@ function expectedException(actual, expected, message, fn) { // Handle regular expressions. if (isRegExp(expected)) { const str = String(actual); - if (expected.test(str)) + if (RegExpPrototypeTest(expected, str)) return; if (!message) { @@ -614,7 +615,7 @@ function expectedException(actual, expected, message, fn) { for (const key of keys) { if (typeof actual[key] === 'string' && isRegExp(expected[key]) && - expected[key].test(actual[key])) { + RegExpPrototypeTest(expected[key], actual[key])) { continue; } compareExceptionKey(actual, expected, key, message, keys, fn); @@ -780,7 +781,7 @@ function hasMatchingError(actual, expected) { if (typeof expected !== 'function') { if (isRegExp(expected)) { const str = String(actual); - return expected.test(str); + return RegExpPrototypeTest(expected, str); } throw new ERR_INVALID_ARG_TYPE( 'expected', ['Function', 'RegExp'], expected @@ -885,6 +886,49 @@ assert.ifError = function ifError(err) { } }; +function internalMatch(string, regexp, message, fn) { + if (!isRegExp(regexp)) { + throw new ERR_INVALID_ARG_TYPE( + 'regexp', 'RegExp', regexp + ); + } + const match = fn.name === 'match'; + if (typeof string !== 'string' || + RegExpPrototypeTest(regexp, string) !== match) { + if (message instanceof Error) { + throw message; + } + + const generatedMessage = !message; + + // 'The input was expected to not match the regular expression ' + + message = message || (typeof string !== 'string' ? + 'The "string" argument must be of type string. Received type ' + + `${typeof string} (${inspect(string)})` : + (match ? + 'The input did not match the regular expression ' : + 'The input was expected to not match the regular expression ') + + `${inspect(regexp)}. Input:\n\n${inspect(string)}\n`); + const err = new AssertionError({ + actual: string, + expected: regexp, + message, + operator: fn.name, + stackStartFn: fn + }); + err.generatedMessage = generatedMessage; + throw err; + } +} + +assert.match = function match(string, regexp, message) { + internalMatch(string, regexp, message, match); +}; + +assert.doesNotMatch = function doesNotMatch(string, regexp, message) { + internalMatch(string, regexp, message, doesNotMatch); +}; + // Expose a strict only variant of assert function strict(...args) { innerOk(strict, args.length, ...args); diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index db2838862bf060..23e1734aff235c 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -1367,3 +1367,103 @@ assert.throws( 'prototype.\n\nError message:\n\nfoobar' } ); + +// Multiple assert.match() tests. +{ + assert.throws( + () => assert.match(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "regexp" argument must be an instance of RegExp. ' + + "Received type string ('string')" + } + ); + assert.throws( + () => assert.match('string', /abc/), + { + actual: 'string', + expected: /abc/, + operator: 'match', + message: 'The input did not match the regular expression /abc/. ' + + "Input:\n\n'string'\n", + generatedMessage: true + } + ); + assert.throws( + () => assert.match('string', /abc/, 'foobar'), + { + actual: 'string', + expected: /abc/, + operator: 'match', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.match('string', /abc/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.match({ abc: 123 }, /abc/), + { + actual: { abc: 123 }, + expected: /abc/, + operator: 'match', + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + generatedMessage: true + } + ); + assert.match('I will pass', /pass$/); +} + +// Multiple assert.doesNotMatch() tests. +{ + assert.throws( + () => assert.doesNotMatch(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "regexp" argument must be an instance of RegExp. ' + + "Received type string ('string')" + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + message: 'The input was expected to not match the regular expression ' + + "/string/. Input:\n\n'string'\n", + generatedMessage: true + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/, 'foobar'), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.doesNotMatch('string', /string/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.doesNotMatch({ abc: 123 }, /abc/), + { + actual: { abc: 123 }, + expected: /abc/, + operator: 'doesNotMatch', + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + generatedMessage: true + } + ); + assert.doesNotMatch('I will pass', /different$/); +}