Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: implement assert.match() #30929

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really convinced this needs to be experimental

Copy link
Member

@Trott Trott Dec 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really convinced this needs to be experimental

It may not need to be experimental, true, but at the same time, it does no harm and it allows us to take a few releases before committing to the API particulars (the method's name, the order of the arguments, whether or not to have a corresponding not method, etc.).

To be honest, I'd be a little more reluctant to approve this if it wasn't Experimental. Once it goes in as Stable, we will never be able to remove it. And while we're not there yet, I do worry about the assert API eventually becoming a million functions no one can remember instead of a small, clear, manageable API surface area.


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
Expand Down
54 changes: 49 additions & 5 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const {
ObjectIs,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
NumberIsNaN
NumberIsNaN,
RegExpPrototypeTest,
} = primordials;

const { Buffer } = require('buffer');
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -885,6 +886,49 @@ assert.ifError = function ifError(err) {
}
};

function internalMatch(string, regexp, message, fn) {
if (!isRegExp(regexp)) {
BridgeAR marked this conversation as resolved.
Show resolved Hide resolved
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' ?
BridgeAR marked this conversation as resolved.
Show resolved Hide resolved
'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);
Expand Down
100 changes: 100 additions & 0 deletions test/parallel/test-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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$/);
}