@@ -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