@@ -24,31 +24,21 @@ const { |
24 | 24 | ArrayPrototypeIndexOf, |
25 | 25 | ArrayPrototypeJoin, |
26 | 26 | ArrayPrototypePush, |
27 | | - ArrayPrototypeShift, |
28 | 27 | ArrayPrototypeSlice, |
29 | 28 | Error, |
30 | | - ErrorCaptureStackTrace, |
31 | | - FunctionPrototypeBind, |
32 | 29 | NumberIsNaN, |
33 | 30 | ObjectAssign, |
34 | 31 | ObjectIs, |
35 | 32 | ObjectKeys, |
36 | 33 | ObjectPrototypeIsPrototypeOf, |
37 | 34 | ReflectApply, |
38 | 35 | RegExpPrototypeExec, |
39 | | - RegExpPrototypeSymbolReplace, |
40 | | - SafeMap, |
41 | 36 | String, |
42 | | - StringPrototypeCharCodeAt, |
43 | | - StringPrototypeIncludes, |
44 | 37 | StringPrototypeIndexOf, |
45 | | - StringPrototypeReplace, |
46 | 38 | StringPrototypeSlice, |
47 | 39 | StringPrototypeSplit, |
48 | | - StringPrototypeStartsWith, |
49 | 40 | } = primordials; |
50 | 41 |
|
51 | | -const { Buffer } = require('buffer'); |
52 | 42 | const { |
53 | 43 | codes: { |
54 | 44 | ERR_AMBIGUOUS_ARGUMENT, |
@@ -57,53 +47,27 @@ const { |
57 | 47 | ERR_INVALID_RETURN_VALUE, |
58 | 48 | ERR_MISSING_ARGS, |
59 | 49 | }, |
60 | | - isErrorStackTraceLimitWritable, |
61 | | - overrideStackTrace, |
62 | 50 | } = require('internal/errors'); |
63 | 51 | const AssertionError = require('internal/assert/assertion_error'); |
64 | | -const { openSync, closeSync, readSync } = require('fs'); |
65 | 52 | const { inspect } = require('internal/util/inspect'); |
66 | 53 | const { isPromise, isRegExp } = require('internal/util/types'); |
67 | | -const { EOL } = require('internal/constants'); |
68 | | -const { BuiltinModule } = require('internal/bootstrap/realm'); |
69 | 54 | const { isError, deprecate } = require('internal/util'); |
| 55 | +const { innerOk } = require('internal/assert/utils'); |
70 | 56 |
|
71 | | -const errorCache = new SafeMap(); |
72 | 57 | const CallTracker = require('internal/assert/calltracker'); |
73 | 58 | const { |
74 | 59 | validateFunction, |
75 | 60 | } = require('internal/validators'); |
76 | | -const { fileURLToPath } = require('internal/url'); |
77 | 61 |
|
78 | 62 | let isDeepEqual; |
79 | 63 | let isDeepStrictEqual; |
80 | | -let parseExpressionAt; |
81 | | -let findNodeAround; |
82 | | -let tokenizer; |
83 | | -let decoder; |
84 | 64 |
|
85 | 65 | function lazyLoadComparison() { |
86 | 66 | const comparison = require('internal/util/comparisons'); |
87 | 67 | isDeepEqual = comparison.isDeepEqual; |
88 | 68 | isDeepStrictEqual = comparison.isDeepStrictEqual; |
89 | 69 | } |
90 | 70 |
|
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 | | - |
107 | 71 | let warned = false; |
108 | 72 |
|
109 | 73 | // The assert module provides functions that throw |
@@ -178,237 +142,6 @@ assert.fail = fail; |
178 | 142 | // The AssertionError is defined in internal/error. |
179 | 143 | assert.AssertionError = AssertionError; |
180 | 144 |
|
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 | | - |
412 | 145 | /** |
413 | 146 | * Pure assertion tests whether a value is truthy, as determined |
414 | 147 | * by !!value. |
|