@@ -24,31 +24,21 @@ const {
2424 ArrayPrototypeIndexOf,
2525 ArrayPrototypeJoin,
2626 ArrayPrototypePush,
27- ArrayPrototypeShift,
2827 ArrayPrototypeSlice,
2928 Error,
30- ErrorCaptureStackTrace,
31- FunctionPrototypeBind,
3229 NumberIsNaN,
3330 ObjectAssign,
3431 ObjectIs,
3532 ObjectKeys,
3633 ObjectPrototypeIsPrototypeOf,
3734 ReflectApply,
3835 RegExpPrototypeExec,
39- RegExpPrototypeSymbolReplace,
40- SafeMap,
4136 String,
42- StringPrototypeCharCodeAt,
43- StringPrototypeIncludes,
4437 StringPrototypeIndexOf,
45- StringPrototypeReplace,
4638 StringPrototypeSlice,
4739 StringPrototypeSplit,
48- StringPrototypeStartsWith,
4940} = primordials;
5041
51- const { Buffer } = require('buffer');
5242const {
5343 codes: {
5444 ERR_AMBIGUOUS_ARGUMENT,
@@ -57,53 +47,27 @@ const {
5747 ERR_INVALID_RETURN_VALUE,
5848 ERR_MISSING_ARGS,
5949 },
60- isErrorStackTraceLimitWritable,
61- overrideStackTrace,
6250} = require('internal/errors');
6351const AssertionError = require('internal/assert/assertion_error');
64- const { openSync, closeSync, readSync } = require('fs');
6552const { inspect } = require('internal/util/inspect');
6653const { isPromise, isRegExp } = require('internal/util/types');
67- const { EOL } = require('internal/constants');
68- const { BuiltinModule } = require('internal/bootstrap/realm');
6954const { isError, deprecate } = require('internal/util');
55+ const { innerOk } = require('internal/assert/utils');
7056
71- const errorCache = new SafeMap();
7257const CallTracker = require('internal/assert/calltracker');
7358const {
7459 validateFunction,
7560} = require('internal/validators');
76- const { fileURLToPath } = require('internal/url');
7761
7862let isDeepEqual;
7963let isDeepStrictEqual;
80- let parseExpressionAt;
81- let findNodeAround;
82- let tokenizer;
83- let decoder;
8464
8565function lazyLoadComparison() {
8666 const comparison = require('internal/util/comparisons');
8767 isDeepEqual = comparison.isDeepEqual;
8868 isDeepStrictEqual = comparison.isDeepStrictEqual;
8969}
9070
91- // Escape control characters but not \n and \t to keep the line breaks and
92- // indentation intact.
93- // eslint-disable-next-line no-control-regex
94- const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
95- const meta = [
96- '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
97- '\\u0005', '\\u0006', '\\u0007', '\\b', '',
98- '', '\\u000b', '\\f', '', '\\u000e',
99- '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
100- '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
101- '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
102- '\\u001e', '\\u001f',
103- ];
104-
105- const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
106-
10771let warned = false;
10872
10973// The assert module provides functions that throw
@@ -178,237 +142,6 @@ assert.fail = fail;
178142// The AssertionError is defined in internal/error.
179143assert.AssertionError = AssertionError;
180144
181- function findColumn(fd, column, code) {
182- if (code.length > column + 100) {
183- try {
184- return parseCode(code, column);
185- } catch {
186- // End recursion in case no code could be parsed. The expression should
187- // have been found after 2500 characters, so stop trying.
188- if (code.length - column > 2500) {
189- // eslint-disable-next-line no-throw-literal
190- throw null;
191- }
192- }
193- }
194- // Read up to 2500 bytes more than necessary in columns. That way we address
195- // multi byte characters and read enough data to parse the code.
196- const bytesToRead = column - code.length + 2500;
197- const buffer = Buffer.allocUnsafe(bytesToRead);
198- const bytesRead = readSync(fd, buffer, 0, bytesToRead);
199- code += decoder.write(buffer.slice(0, bytesRead));
200- // EOF: fast path.
201- if (bytesRead < bytesToRead) {
202- return parseCode(code, column);
203- }
204- // Read potentially missing code.
205- return findColumn(fd, column, code);
206- }
207-
208- function getCode(fd, line, column) {
209- let bytesRead = 0;
210- if (line === 0) {
211- // Special handle line number one. This is more efficient and simplifies the
212- // rest of the algorithm. Read more than the regular column number in bytes
213- // to prevent multiple reads in case multi byte characters are used.
214- return findColumn(fd, column, '');
215- }
216- let lines = 0;
217- // Prevent blocking the event loop by limiting the maximum amount of
218- // data that may be read.
219- let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
220- const bytesPerRead = 16384;
221- // Use a single buffer up front that is reused until the call site is found.
222- let buffer = Buffer.allocUnsafe(bytesPerRead);
223- while (maxReads-- !== 0) {
224- // Only allocate a new buffer in case the needed line is found. All data
225- // before that can be discarded.
226- buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
227- bytesRead = readSync(fd, buffer, 0, bytesPerRead);
228- // Read the buffer until the required code line is found.
229- for (let i = 0; i < bytesRead; i++) {
230- if (buffer[i] === 10 && ++lines === line) {
231- // If the end of file is reached, directly parse the code and return.
232- if (bytesRead < bytesPerRead) {
233- return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
234- }
235- // Check if the read code is sufficient or read more until the whole
236- // expression is read. Make sure multi byte characters are preserved
237- // properly by using the decoder.
238- const code = decoder.write(buffer.slice(i + 1, bytesRead));
239- return findColumn(fd, column, code);
240- }
241- }
242- }
243- }
244-
245- function parseCode(code, offset) {
246- // Lazy load acorn.
247- if (parseExpressionAt === undefined) {
248- const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
249- ({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
250-
251- parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
252- tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
253- }
254- let node;
255- let start;
256- // Parse the read code until the correct expression is found.
257- for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
258- start = token.start;
259- if (start > offset) {
260- // No matching expression found. This could happen if the assert
261- // expression is bigger than the provided buffer.
262- break;
263- }
264- try {
265- node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
266- // Find the CallExpression in the tree.
267- node = findNodeAround(node, offset, 'CallExpression');
268- if (node?.node.end >= offset) {
269- return [
270- node.node.start,
271- StringPrototypeReplace(StringPrototypeSlice(code,
272- node.node.start, node.node.end),
273- escapeSequencesRegExp, escapeFn),
274- ];
275- }
276- // eslint-disable-next-line no-unused-vars
277- } catch (err) {
278- continue;
279- }
280- }
281- // eslint-disable-next-line no-throw-literal
282- throw null;
283- }
284-
285- function getErrMessage(message, fn) {
286- const tmpLimit = Error.stackTraceLimit;
287- const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
288- // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
289- // does to much work.
290- if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
291- // We only need the stack trace. To minimize the overhead use an object
292- // instead of an error.
293- const err = {};
294- ErrorCaptureStackTrace(err, fn);
295- if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
296-
297- overrideStackTrace.set(err, (_, stack) => stack);
298- const call = err.stack[0];
299-
300- let filename = call.getFileName();
301- const line = call.getLineNumber() - 1;
302- let column = call.getColumnNumber() - 1;
303- let identifier;
304- let code;
305-
306- if (filename) {
307- identifier = `${filename}${line}${column}`;
308-
309- // Skip Node.js modules!
310- if (StringPrototypeStartsWith(filename, 'node:') &&
311- BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
312- errorCache.set(identifier, undefined);
313- return;
314- }
315- } else {
316- return message;
317- }
318-
319- if (errorCache.has(identifier)) {
320- return errorCache.get(identifier);
321- }
322-
323- let fd;
324- try {
325- // Set the stack trace limit to zero. This makes sure unexpected token
326- // errors are handled faster.
327- if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
328-
329- if (filename) {
330- if (decoder === undefined) {
331- const { StringDecoder } = require('string_decoder');
332- decoder = new StringDecoder('utf8');
333- }
334-
335- // ESM file prop is a file proto. Convert that to path.
336- // This ensure opensync will not throw ENOENT for ESM files.
337- const fileProtoPrefix = 'file://';
338- if (StringPrototypeStartsWith(filename, fileProtoPrefix)) {
339- filename = fileURLToPath(filename);
340- }
341-
342- fd = openSync(filename, 'r', 0o666);
343- // Reset column and message.
344- ({ 0: column, 1: message } = getCode(fd, line, column));
345- // Flush unfinished multi byte characters.
346- decoder.end();
347- } else {
348- for (let i = 0; i < line; i++) {
349- code = StringPrototypeSlice(code,
350- StringPrototypeIndexOf(code, '\n') + 1);
351- }
352- ({ 0: column, 1: message } = parseCode(code, column));
353- }
354- // Always normalize indentation, otherwise the message could look weird.
355- if (StringPrototypeIncludes(message, '\n')) {
356- if (EOL === '\r\n') {
357- message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
358- }
359- const frames = StringPrototypeSplit(message, '\n');
360- message = ArrayPrototypeShift(frames);
361- for (const frame of frames) {
362- let pos = 0;
363- while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
364- pos++;
365- }
366- message += `\n ${StringPrototypeSlice(frame, pos)}`;
367- }
368- }
369- message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
370- // Make sure to always set the cache! No matter if the message is
371- // undefined or not
372- errorCache.set(identifier, message);
373-
374- return message;
375- } catch {
376- // Invalidate cache to prevent trying to read this part again.
377- errorCache.set(identifier, undefined);
378- } finally {
379- // Reset limit.
380- if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
381- if (fd !== undefined)
382- closeSync(fd);
383- }
384- }
385-
386- function innerOk(fn, argLen, value, message) {
387- if (!value) {
388- let generatedMessage = false;
389-
390- if (argLen === 0) {
391- generatedMessage = true;
392- message = 'No value argument passed to `assert.ok()`';
393- } else if (message == null) {
394- generatedMessage = true;
395- message = getErrMessage(message, fn);
396- } else if (isError(message)) {
397- throw message;
398- }
399-
400- const err = new AssertionError({
401- actual: value,
402- expected: true,
403- message,
404- operator: '==',
405- stackStartFn: fn,
406- });
407- err.generatedMessage = generatedMessage;
408- throw err;
409- }
410- }
411-
412145/**
413146 * Pure assertion tests whether a value is truthy, as determined
414147 * by !!value.
0 commit comments