11'use strict' ;
22
33const {
4- ArrayPrototypeShift,
54 Error,
65 ErrorCaptureStackTrace,
7- FunctionPrototypeBind,
8- RegExpPrototypeSymbolReplace,
9- SafeMap,
106 StringPrototypeCharCodeAt,
11- StringPrototypeIncludes,
127 StringPrototypeReplace,
13- StringPrototypeSlice,
14- StringPrototypeSplit,
15- StringPrototypeStartsWith,
168} = primordials ;
179
18- const { Buffer } = require ( 'buffer' ) ;
1910const {
2011 isErrorStackTraceLimitWritable,
21- overrideStackTrace,
2212} = require ( 'internal/errors' ) ;
2313const AssertionError = require ( 'internal/assert/assertion_error' ) ;
24- const { openSync, closeSync, readSync } = require ( 'fs' ) ;
25- const { EOL } = require ( 'internal/constants' ) ;
26- const { BuiltinModule } = require ( 'internal/bootstrap/realm' ) ;
2714const { isError } = require ( 'internal/util' ) ;
2815
29- const errorCache = new SafeMap ( ) ;
30- const { fileURLToPath } = require ( 'internal/url' ) ;
31-
32- let parseExpressionAt ;
33- let findNodeAround ;
34- let tokenizer ;
35- let decoder ;
16+ const {
17+ getErrorSourceExpression,
18+ } = require ( 'internal/errors/error_source' ) ;
3619
3720// Escape control characters but not \n and \t to keep the line breaks and
3821// indentation intact.
@@ -50,111 +33,7 @@ const meta = [
5033
5134const escapeFn = ( str ) => meta [ StringPrototypeCharCodeAt ( str , 0 ) ] ;
5235
53- function findColumn ( fd , column , code ) {
54- if ( code . length > column + 100 ) {
55- try {
56- return parseCode ( code , column ) ;
57- } catch {
58- // End recursion in case no code could be parsed. The expression should
59- // have been found after 2500 characters, so stop trying.
60- if ( code . length - column > 2500 ) {
61- // eslint-disable-next-line no-throw-literal
62- throw null ;
63- }
64- }
65- }
66- // Read up to 2500 bytes more than necessary in columns. That way we address
67- // multi byte characters and read enough data to parse the code.
68- const bytesToRead = column - code . length + 2500 ;
69- const buffer = Buffer . allocUnsafe ( bytesToRead ) ;
70- const bytesRead = readSync ( fd , buffer , 0 , bytesToRead ) ;
71- code += decoder . write ( buffer . slice ( 0 , bytesRead ) ) ;
72- // EOF: fast path.
73- if ( bytesRead < bytesToRead ) {
74- return parseCode ( code , column ) ;
75- }
76- // Read potentially missing code.
77- return findColumn ( fd , column , code ) ;
78- }
79-
80- function getCode ( fd , line , column ) {
81- let bytesRead = 0 ;
82- if ( line === 0 ) {
83- // Special handle line number one. This is more efficient and simplifies the
84- // rest of the algorithm. Read more than the regular column number in bytes
85- // to prevent multiple reads in case multi byte characters are used.
86- return findColumn ( fd , column , '' ) ;
87- }
88- let lines = 0 ;
89- // Prevent blocking the event loop by limiting the maximum amount of
90- // data that may be read.
91- let maxReads = 32 ; // bytesPerRead * maxReads = 512 KiB
92- const bytesPerRead = 16384 ;
93- // Use a single buffer up front that is reused until the call site is found.
94- let buffer = Buffer . allocUnsafe ( bytesPerRead ) ;
95- while ( maxReads -- !== 0 ) {
96- // Only allocate a new buffer in case the needed line is found. All data
97- // before that can be discarded.
98- buffer = lines < line ? buffer : Buffer . allocUnsafe ( bytesPerRead ) ;
99- bytesRead = readSync ( fd , buffer , 0 , bytesPerRead ) ;
100- // Read the buffer until the required code line is found.
101- for ( let i = 0 ; i < bytesRead ; i ++ ) {
102- if ( buffer [ i ] === 10 && ++ lines === line ) {
103- // If the end of file is reached, directly parse the code and return.
104- if ( bytesRead < bytesPerRead ) {
105- return parseCode ( buffer . toString ( 'utf8' , i + 1 , bytesRead ) , column ) ;
106- }
107- // Check if the read code is sufficient or read more until the whole
108- // expression is read. Make sure multi byte characters are preserved
109- // properly by using the decoder.
110- const code = decoder . write ( buffer . slice ( i + 1 , bytesRead ) ) ;
111- return findColumn ( fd , column , code ) ;
112- }
113- }
114- }
115- }
116-
117- function parseCode ( code , offset ) {
118- // Lazy load acorn.
119- if ( parseExpressionAt === undefined ) {
120- const Parser = require ( 'internal/deps/acorn/acorn/dist/acorn' ) . Parser ;
121- ( { findNodeAround } = require ( 'internal/deps/acorn/acorn-walk/dist/walk' ) ) ;
122-
123- parseExpressionAt = FunctionPrototypeBind ( Parser . parseExpressionAt , Parser ) ;
124- tokenizer = FunctionPrototypeBind ( Parser . tokenizer , Parser ) ;
125- }
126- let node ;
127- let start ;
128- // Parse the read code until the correct expression is found.
129- for ( const token of tokenizer ( code , { ecmaVersion : 'latest' } ) ) {
130- start = token . start ;
131- if ( start > offset ) {
132- // No matching expression found. This could happen if the assert
133- // expression is bigger than the provided buffer.
134- break ;
135- }
136- try {
137- node = parseExpressionAt ( code , start , { ecmaVersion : 'latest' } ) ;
138- // Find the CallExpression in the tree.
139- node = findNodeAround ( node , offset , 'CallExpression' ) ;
140- if ( node ?. node . end >= offset ) {
141- return [
142- node . node . start ,
143- StringPrototypeReplace ( StringPrototypeSlice ( code ,
144- node . node . start , node . node . end ) ,
145- escapeSequencesRegExp , escapeFn ) ,
146- ] ;
147- }
148- // eslint-disable-next-line no-unused-vars
149- } catch ( err ) {
150- continue ;
151- }
152- }
153- // eslint-disable-next-line no-throw-literal
154- throw null ;
155- }
156-
157- function getErrMessage ( message , fn ) {
36+ function getErrMessage ( fn ) {
15837 const tmpLimit = Error . stackTraceLimit ;
15938 const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable ( ) ;
16039 // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
@@ -166,85 +45,10 @@ function getErrMessage(message, fn) {
16645 ErrorCaptureStackTrace ( err , fn ) ;
16746 if ( errorStackTraceLimitIsWritable ) Error . stackTraceLimit = tmpLimit ;
16847
169- overrideStackTrace . set ( err , ( _ , stack ) => stack ) ;
170- const call = err . stack [ 0 ] ;
171-
172- let filename = call . getFileName ( ) ;
173- const line = call . getLineNumber ( ) - 1 ;
174- let column = call . getColumnNumber ( ) - 1 ;
175- let identifier ;
176-
177- if ( filename ) {
178- identifier = `${ filename } ${ line } ${ column } ` ;
179-
180- // Skip Node.js modules!
181- if ( StringPrototypeStartsWith ( filename , 'node:' ) &&
182- BuiltinModule . exists ( StringPrototypeSlice ( filename , 5 ) ) ) {
183- errorCache . set ( identifier , undefined ) ;
184- return ;
185- }
186- } else {
187- return message ;
188- }
189-
190- if ( errorCache . has ( identifier ) ) {
191- return errorCache . get ( identifier ) ;
192- }
193-
194- let fd ;
195- try {
196- // Set the stack trace limit to zero. This makes sure unexpected token
197- // errors are handled faster.
198- if ( errorStackTraceLimitIsWritable ) Error . stackTraceLimit = 0 ;
199-
200- if ( decoder === undefined ) {
201- const { StringDecoder } = require ( 'string_decoder' ) ;
202- decoder = new StringDecoder ( 'utf8' ) ;
203- }
204-
205- // ESM file prop is a file proto. Convert that to path.
206- // This ensure opensync will not throw ENOENT for ESM files.
207- const fileProtoPrefix = 'file://' ;
208- if ( StringPrototypeStartsWith ( filename , fileProtoPrefix ) ) {
209- filename = fileURLToPath ( filename ) ;
210- }
211-
212- fd = openSync ( filename , 'r' , 0o666 ) ;
213- // Reset column and message.
214- ( { 0 : column , 1 : message } = getCode ( fd , line , column ) ) ;
215- // Flush unfinished multi byte characters.
216- decoder . end ( ) ;
217-
218- // Always normalize indentation, otherwise the message could look weird.
219- if ( StringPrototypeIncludes ( message , '\n' ) ) {
220- if ( EOL === '\r\n' ) {
221- message = RegExpPrototypeSymbolReplace ( / \r \n / g, message , '\n' ) ;
222- }
223- const frames = StringPrototypeSplit ( message , '\n' ) ;
224- message = ArrayPrototypeShift ( frames ) ;
225- for ( let i = 0 ; i < frames . length ; i ++ ) {
226- const frame = frames [ i ] ;
227- let pos = 0 ;
228- while ( pos < column && ( frame [ pos ] === ' ' || frame [ pos ] === '\t' ) ) {
229- pos ++ ;
230- }
231- message += `\n ${ StringPrototypeSlice ( frame , pos ) } ` ;
232- }
233- }
234- message = `The expression evaluated to a falsy value:\n\n ${ message } \n` ;
235- // Make sure to always set the cache! No matter if the message is
236- // undefined or not
237- errorCache . set ( identifier , message ) ;
238-
239- return message ;
240- } catch {
241- // Invalidate cache to prevent trying to read this part again.
242- errorCache . set ( identifier , undefined ) ;
243- } finally {
244- // Reset limit.
245- if ( errorStackTraceLimitIsWritable ) Error . stackTraceLimit = tmpLimit ;
246- if ( fd !== undefined )
247- closeSync ( fd ) ;
48+ let source = getErrorSourceExpression ( err ) ;
49+ if ( source ) {
50+ source = StringPrototypeReplace ( source , escapeSequencesRegExp , escapeFn ) ;
51+ return `The expression evaluated to a falsy value:\n\n ${ source } \n` ;
24852 }
24953}
25054
@@ -257,7 +61,7 @@ function innerOk(fn, argLen, value, message) {
25761 message = 'No value argument passed to `assert.ok()`' ;
25862 } else if ( message == null ) {
25963 generatedMessage = true ;
260- message = getErrMessage ( message , fn ) ;
64+ message = getErrMessage ( fn ) ;
26165 } else if ( isError ( message ) ) {
26266 throw message ;
26367 }
0 commit comments