Skip to content

Commit 5d06c1e

Browse files
BridgeARtargos
authored andcommitted
assert: move AssertionError into own file
This moves the `assert` parts from `internal/errors` into an own file. `internal/errors` got bigger and bigger and it was difficult to keep a good overview of what was going on. While doing so it also removes the `internalAssert` function and just lazy loads `assert`. PR-URL: #20486 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
1 parent 01abed1 commit 5d06c1e

File tree

5 files changed

+301
-306
lines changed

5 files changed

+301
-306
lines changed

lib/assert.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,12 @@ const {
2525
isDeepEqual,
2626
isDeepStrictEqual
2727
} = require('internal/util/comparisons');
28-
const {
29-
AssertionError,
30-
errorCache,
31-
codes: {
32-
ERR_AMBIGUOUS_ARGUMENT,
33-
ERR_INVALID_ARG_TYPE,
34-
ERR_INVALID_RETURN_VALUE
35-
}
36-
} = require('internal/errors');
28+
const { codes: {
29+
ERR_AMBIGUOUS_ARGUMENT,
30+
ERR_INVALID_ARG_TYPE,
31+
ERR_INVALID_RETURN_VALUE
32+
} } = require('internal/errors');
33+
const { AssertionError, errorCache } = require('internal/assert');
3734
const { openSync, closeSync, readSync } = require('fs');
3835
const { inspect, types: { isPromise, isRegExp } } = require('util');
3936
const { EOL } = require('os');

lib/internal/assert.js

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
'use strict';
2+
3+
const { inspect } = require('util');
4+
const { codes: {
5+
ERR_INVALID_ARG_TYPE
6+
} } = require('internal/errors');
7+
8+
let blue = '';
9+
let green = '';
10+
let red = '';
11+
let white = '';
12+
13+
const READABLE_OPERATOR = {
14+
deepStrictEqual: 'Input A expected to strictly deep-equal input B',
15+
notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B',
16+
strictEqual: 'Input A expected to strictly equal input B',
17+
notStrictEqual: 'Input A expected to strictly not equal input B'
18+
};
19+
20+
function copyError(source) {
21+
const keys = Object.keys(source);
22+
const target = Object.create(Object.getPrototypeOf(source));
23+
for (const key of keys) {
24+
target[key] = source[key];
25+
}
26+
Object.defineProperty(target, 'message', { value: source.message });
27+
return target;
28+
}
29+
30+
function inspectValue(val) {
31+
// The util.inspect default values could be changed. This makes sure the
32+
// error messages contain the necessary information nevertheless.
33+
return inspect(
34+
val,
35+
{
36+
compact: false,
37+
customInspect: false,
38+
depth: 1000,
39+
maxArrayLength: Infinity,
40+
// Assert compares only enumerable properties (with a few exceptions).
41+
showHidden: false,
42+
// Having a long line as error is better than wrapping the line for
43+
// comparison.
44+
breakLength: Infinity,
45+
// Assert does not detect proxies currently.
46+
showProxy: false
47+
}
48+
).split('\n');
49+
}
50+
51+
function createErrDiff(actual, expected, operator) {
52+
var other = '';
53+
var res = '';
54+
var lastPos = 0;
55+
var end = '';
56+
var skipped = false;
57+
const actualLines = inspectValue(actual);
58+
const expectedLines = inspectValue(expected);
59+
const msg = READABLE_OPERATOR[operator] +
60+
`:\n${green}+ expected${white} ${red}- actual${white}`;
61+
const skippedMsg = ` ${blue}...${white} Lines skipped`;
62+
63+
// Remove all ending lines that match (this optimizes the output for
64+
// readability by reducing the number of total changed lines).
65+
var a = actualLines[actualLines.length - 1];
66+
var b = expectedLines[expectedLines.length - 1];
67+
var i = 0;
68+
while (a === b) {
69+
if (i++ < 2) {
70+
end = `\n ${a}${end}`;
71+
} else {
72+
other = a;
73+
}
74+
actualLines.pop();
75+
expectedLines.pop();
76+
if (actualLines.length === 0 || expectedLines.length === 0)
77+
break;
78+
a = actualLines[actualLines.length - 1];
79+
b = expectedLines[expectedLines.length - 1];
80+
}
81+
if (i > 3) {
82+
end = `\n${blue}...${white}${end}`;
83+
skipped = true;
84+
}
85+
if (other !== '') {
86+
end = `\n ${other}${end}`;
87+
other = '';
88+
}
89+
90+
const maxLines = Math.max(actualLines.length, expectedLines.length);
91+
var printedLines = 0;
92+
var identical = 0;
93+
for (i = 0; i < maxLines; i++) {
94+
// Only extra expected lines exist
95+
const cur = i - lastPos;
96+
if (actualLines.length < i + 1) {
97+
if (cur > 1 && i > 2) {
98+
if (cur > 4) {
99+
res += `\n${blue}...${white}`;
100+
skipped = true;
101+
} else if (cur > 3) {
102+
res += `\n ${expectedLines[i - 2]}`;
103+
printedLines++;
104+
}
105+
res += `\n ${expectedLines[i - 1]}`;
106+
printedLines++;
107+
}
108+
lastPos = i;
109+
other += `\n${green}+${white} ${expectedLines[i]}`;
110+
printedLines++;
111+
// Only extra actual lines exist
112+
} else if (expectedLines.length < i + 1) {
113+
if (cur > 1 && i > 2) {
114+
if (cur > 4) {
115+
res += `\n${blue}...${white}`;
116+
skipped = true;
117+
} else if (cur > 3) {
118+
res += `\n ${actualLines[i - 2]}`;
119+
printedLines++;
120+
}
121+
res += `\n ${actualLines[i - 1]}`;
122+
printedLines++;
123+
}
124+
lastPos = i;
125+
res += `\n${red}-${white} ${actualLines[i]}`;
126+
printedLines++;
127+
// Lines diverge
128+
} else if (actualLines[i] !== expectedLines[i]) {
129+
if (cur > 1 && i > 2) {
130+
if (cur > 4) {
131+
res += `\n${blue}...${white}`;
132+
skipped = true;
133+
} else if (cur > 3) {
134+
res += `\n ${actualLines[i - 2]}`;
135+
printedLines++;
136+
}
137+
res += `\n ${actualLines[i - 1]}`;
138+
printedLines++;
139+
}
140+
lastPos = i;
141+
res += `\n${red}-${white} ${actualLines[i]}`;
142+
other += `\n${green}+${white} ${expectedLines[i]}`;
143+
printedLines += 2;
144+
// Lines are identical
145+
} else {
146+
res += other;
147+
other = '';
148+
if (cur === 1 || i === 0) {
149+
res += `\n ${actualLines[i]}`;
150+
printedLines++;
151+
}
152+
identical++;
153+
}
154+
// Inspected object to big (Show ~20 rows max)
155+
if (printedLines > 20 && i < maxLines - 2) {
156+
return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` +
157+
`${blue}...${white}`;
158+
}
159+
}
160+
161+
// Strict equal with identical objects that are not identical by reference.
162+
if (identical === maxLines) {
163+
// E.g., assert.deepStrictEqual(Symbol(), Symbol())
164+
const base = operator === 'strictEqual' ?
165+
'Input objects identical but not reference equal:' :
166+
'Input objects not identical:';
167+
168+
// We have to get the result again. The lines were all removed before.
169+
const actualLines = inspectValue(actual);
170+
171+
// Only remove lines in case it makes sense to collapse those.
172+
// TODO: Accept env to always show the full error.
173+
if (actualLines.length > 30) {
174+
actualLines[26] = `${blue}...${white}`;
175+
while (actualLines.length > 27) {
176+
actualLines.pop();
177+
}
178+
}
179+
180+
return `${base}\n\n${actualLines.join('\n')}\n`;
181+
}
182+
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
183+
}
184+
185+
class AssertionError extends Error {
186+
constructor(options) {
187+
if (typeof options !== 'object' || options === null) {
188+
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
189+
}
190+
var {
191+
actual,
192+
expected,
193+
message,
194+
operator,
195+
stackStartFn
196+
} = options;
197+
198+
if (message != null) {
199+
super(message);
200+
} else {
201+
if (process.stdout.isTTY) {
202+
// Reset on each call to make sure we handle dynamically set environment
203+
// variables correct.
204+
if (process.stdout.getColorDepth() !== 1) {
205+
blue = '\u001b[34m';
206+
green = '\u001b[32m';
207+
white = '\u001b[39m';
208+
red = '\u001b[31m';
209+
} else {
210+
blue = '';
211+
green = '';
212+
white = '';
213+
red = '';
214+
}
215+
}
216+
// Prevent the error stack from being visible by duplicating the error
217+
// in a very close way to the original in case both sides are actually
218+
// instances of Error.
219+
if (typeof actual === 'object' && actual !== null &&
220+
typeof expected === 'object' && expected !== null &&
221+
'stack' in actual && actual instanceof Error &&
222+
'stack' in expected && expected instanceof Error) {
223+
actual = copyError(actual);
224+
expected = copyError(expected);
225+
}
226+
227+
if (operator === 'deepStrictEqual' || operator === 'strictEqual') {
228+
super(createErrDiff(actual, expected, operator));
229+
} else if (operator === 'notDeepStrictEqual' ||
230+
operator === 'notStrictEqual') {
231+
// In case the objects are equal but the operator requires unequal, show
232+
// the first object and say A equals B
233+
const res = inspectValue(actual);
234+
const base = `Identical input passed to ${operator}:`;
235+
236+
// Only remove lines in case it makes sense to collapse those.
237+
// TODO: Accept env to always show the full error.
238+
if (res.length > 30) {
239+
res[26] = `${blue}...${white}`;
240+
while (res.length > 27) {
241+
res.pop();
242+
}
243+
}
244+
245+
// Only print a single input.
246+
if (res.length === 1) {
247+
super(`${base} ${res[0]}`);
248+
} else {
249+
super(`${base}\n\n${res.join('\n')}\n`);
250+
}
251+
} else {
252+
let res = inspect(actual);
253+
let other = inspect(expected);
254+
if (res.length > 128)
255+
res = `${res.slice(0, 125)}...`;
256+
if (other.length > 128)
257+
other = `${other.slice(0, 125)}...`;
258+
super(`${res} ${operator} ${other}`);
259+
}
260+
}
261+
262+
this.generatedMessage = !message;
263+
this.name = 'AssertionError [ERR_ASSERTION]';
264+
this.code = 'ERR_ASSERTION';
265+
this.actual = actual;
266+
this.expected = expected;
267+
this.operator = operator;
268+
Error.captureStackTrace(this, stackStartFn);
269+
}
270+
}
271+
272+
module.exports = {
273+
AssertionError,
274+
errorCache: new Map()
275+
};

0 commit comments

Comments
 (0)