1
1
'use strict' ;
2
2
3
3
const {
4
- ArrayPrototypeShift,
5
4
Error,
6
5
ErrorCaptureStackTrace,
7
- FunctionPrototypeBind,
8
- RegExpPrototypeSymbolReplace,
9
- SafeMap,
10
6
StringPrototypeCharCodeAt,
11
- StringPrototypeIncludes,
12
7
StringPrototypeReplace,
13
- StringPrototypeSlice,
14
- StringPrototypeSplit,
15
- StringPrototypeStartsWith,
16
8
} = primordials ;
17
9
18
- const { Buffer } = require ( 'buffer' ) ;
19
10
const {
20
11
isErrorStackTraceLimitWritable,
21
- overrideStackTrace,
22
12
} = require ( 'internal/errors' ) ;
23
13
const 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' ) ;
27
14
const { isError } = require ( 'internal/util' ) ;
28
15
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' ) ;
36
19
37
20
// Escape control characters but not \n and \t to keep the line breaks and
38
21
// indentation intact.
@@ -50,111 +33,7 @@ const meta = [
50
33
51
34
const escapeFn = ( str ) => meta [ StringPrototypeCharCodeAt ( str , 0 ) ] ;
52
35
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 ) {
158
37
const tmpLimit = Error . stackTraceLimit ;
159
38
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable ( ) ;
160
39
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
@@ -166,85 +45,10 @@ function getErrMessage(message, fn) {
166
45
ErrorCaptureStackTrace ( err , fn ) ;
167
46
if ( errorStackTraceLimitIsWritable ) Error . stackTraceLimit = tmpLimit ;
168
47
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` ;
248
52
}
249
53
}
250
54
@@ -257,7 +61,7 @@ function innerOk(fn, argLen, value, message) {
257
61
message = 'No value argument passed to `assert.ok()`' ;
258
62
} else if ( message == null ) {
259
63
generatedMessage = true ;
260
- message = getErrMessage ( message , fn ) ;
64
+ message = getErrMessage ( fn ) ;
261
65
} else if ( isError ( message ) ) {
262
66
throw message ;
263
67
}
0 commit comments