diff --git a/lib/internal/assert/utils.js b/lib/internal/assert/utils.js index 13e41d67c635c2..cf7e45fe375f3e 100644 --- a/lib/internal/assert/utils.js +++ b/lib/internal/assert/utils.js @@ -1,38 +1,21 @@ 'use strict'; const { - ArrayPrototypeShift, Error, ErrorCaptureStackTrace, - FunctionPrototypeBind, - RegExpPrototypeSymbolReplace, - SafeMap, StringPrototypeCharCodeAt, - StringPrototypeIncludes, StringPrototypeReplace, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeStartsWith, } = primordials; -const { Buffer } = require('buffer'); const { isErrorStackTraceLimitWritable, - overrideStackTrace, } = require('internal/errors'); const AssertionError = require('internal/assert/assertion_error'); -const { openSync, closeSync, readSync } = require('fs'); -const { EOL } = require('internal/constants'); -const { BuiltinModule } = require('internal/bootstrap/realm'); const { isError } = require('internal/util'); -const errorCache = new SafeMap(); -const { fileURLToPath } = require('internal/url'); - -let parseExpressionAt; -let findNodeAround; -let tokenizer; -let decoder; +const { + getErrorSourceExpression, +} = require('internal/errors/error_source'); // Escape control characters but not \n and \t to keep the line breaks and // indentation intact. @@ -50,111 +33,7 @@ const meta = [ const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)]; -function findColumn(fd, column, code) { - if (code.length > column + 100) { - try { - return parseCode(code, column); - } catch { - // End recursion in case no code could be parsed. The expression should - // have been found after 2500 characters, so stop trying. - if (code.length - column > 2500) { - // eslint-disable-next-line no-throw-literal - throw null; - } - } - } - // Read up to 2500 bytes more than necessary in columns. That way we address - // multi byte characters and read enough data to parse the code. - const bytesToRead = column - code.length + 2500; - const buffer = Buffer.allocUnsafe(bytesToRead); - const bytesRead = readSync(fd, buffer, 0, bytesToRead); - code += decoder.write(buffer.slice(0, bytesRead)); - // EOF: fast path. - if (bytesRead < bytesToRead) { - return parseCode(code, column); - } - // Read potentially missing code. - return findColumn(fd, column, code); -} - -function getCode(fd, line, column) { - let bytesRead = 0; - if (line === 0) { - // Special handle line number one. This is more efficient and simplifies the - // rest of the algorithm. Read more than the regular column number in bytes - // to prevent multiple reads in case multi byte characters are used. - return findColumn(fd, column, ''); - } - let lines = 0; - // Prevent blocking the event loop by limiting the maximum amount of - // data that may be read. - let maxReads = 32; // bytesPerRead * maxReads = 512 KiB - const bytesPerRead = 16384; - // Use a single buffer up front that is reused until the call site is found. - let buffer = Buffer.allocUnsafe(bytesPerRead); - while (maxReads-- !== 0) { - // Only allocate a new buffer in case the needed line is found. All data - // before that can be discarded. - buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead); - bytesRead = readSync(fd, buffer, 0, bytesPerRead); - // Read the buffer until the required code line is found. - for (let i = 0; i < bytesRead; i++) { - if (buffer[i] === 10 && ++lines === line) { - // If the end of file is reached, directly parse the code and return. - if (bytesRead < bytesPerRead) { - return parseCode(buffer.toString('utf8', i + 1, bytesRead), column); - } - // Check if the read code is sufficient or read more until the whole - // expression is read. Make sure multi byte characters are preserved - // properly by using the decoder. - const code = decoder.write(buffer.slice(i + 1, bytesRead)); - return findColumn(fd, column, code); - } - } - } -} - -function parseCode(code, offset) { - // Lazy load acorn. - if (parseExpressionAt === undefined) { - const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser; - ({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk')); - - parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser); - tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser); - } - let node; - let start; - // Parse the read code until the correct expression is found. - for (const token of tokenizer(code, { ecmaVersion: 'latest' })) { - start = token.start; - if (start > offset) { - // No matching expression found. This could happen if the assert - // expression is bigger than the provided buffer. - break; - } - try { - node = parseExpressionAt(code, start, { ecmaVersion: 'latest' }); - // Find the CallExpression in the tree. - node = findNodeAround(node, offset, 'CallExpression'); - if (node?.node.end >= offset) { - return [ - node.node.start, - StringPrototypeReplace(StringPrototypeSlice(code, - node.node.start, node.node.end), - escapeSequencesRegExp, escapeFn), - ]; - } - // eslint-disable-next-line no-unused-vars - } catch (err) { - continue; - } - } - // eslint-disable-next-line no-throw-literal - throw null; -} - -function getErrMessage(message, fn) { +function getErrMessage(fn) { const tmpLimit = Error.stackTraceLimit; const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable(); // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it @@ -166,85 +45,10 @@ function getErrMessage(message, fn) { ErrorCaptureStackTrace(err, fn); if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit; - overrideStackTrace.set(err, (_, stack) => stack); - const call = err.stack[0]; - - let filename = call.getFileName(); - const line = call.getLineNumber() - 1; - let column = call.getColumnNumber() - 1; - let identifier; - - if (filename) { - identifier = `${filename}${line}${column}`; - - // Skip Node.js modules! - if (StringPrototypeStartsWith(filename, 'node:') && - BuiltinModule.exists(StringPrototypeSlice(filename, 5))) { - errorCache.set(identifier, undefined); - return; - } - } else { - return message; - } - - if (errorCache.has(identifier)) { - return errorCache.get(identifier); - } - - let fd; - try { - // Set the stack trace limit to zero. This makes sure unexpected token - // errors are handled faster. - if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0; - - if (decoder === undefined) { - const { StringDecoder } = require('string_decoder'); - decoder = new StringDecoder('utf8'); - } - - // ESM file prop is a file proto. Convert that to path. - // This ensure opensync will not throw ENOENT for ESM files. - const fileProtoPrefix = 'file://'; - if (StringPrototypeStartsWith(filename, fileProtoPrefix)) { - filename = fileURLToPath(filename); - } - - fd = openSync(filename, 'r', 0o666); - // Reset column and message. - ({ 0: column, 1: message } = getCode(fd, line, column)); - // Flush unfinished multi byte characters. - decoder.end(); - - // Always normalize indentation, otherwise the message could look weird. - if (StringPrototypeIncludes(message, '\n')) { - if (EOL === '\r\n') { - message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n'); - } - const frames = StringPrototypeSplit(message, '\n'); - message = ArrayPrototypeShift(frames); - for (let i = 0; i < frames.length; i++) { - const frame = frames[i]; - let pos = 0; - while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) { - pos++; - } - message += `\n ${StringPrototypeSlice(frame, pos)}`; - } - } - message = `The expression evaluated to a falsy value:\n\n ${message}\n`; - // Make sure to always set the cache! No matter if the message is - // undefined or not - errorCache.set(identifier, message); - - return message; - } catch { - // Invalidate cache to prevent trying to read this part again. - errorCache.set(identifier, undefined); - } finally { - // Reset limit. - if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit; - if (fd !== undefined) - closeSync(fd); + let source = getErrorSourceExpression(err); + if (source) { + source = StringPrototypeReplace(source, escapeSequencesRegExp, escapeFn); + return `The expression evaluated to a falsy value:\n\n ${source}\n`; } } @@ -257,7 +61,7 @@ function innerOk(fn, argLen, value, message) { message = 'No value argument passed to `assert.ok()`'; } else if (message == null) { generatedMessage = true; - message = getErrMessage(message, fn); + message = getErrMessage(fn); } else if (isError(message)) { throw message; } diff --git a/lib/internal/errors/error_source.js b/lib/internal/errors/error_source.js new file mode 100644 index 00000000000000..eddd6af230801b --- /dev/null +++ b/lib/internal/errors/error_source.js @@ -0,0 +1,165 @@ +'use strict'; + +const { + FunctionPrototypeBind, + StringPrototypeSlice, +} = primordials; + +const { + getErrorSourcePositions, +} = internalBinding('errors'); +const { + getSourceMapsSupport, + findSourceMap, + getSourceLine, +} = require('internal/source_map/source_map_cache'); + +/** + * Get the source location of an error. If source map is enabled, resolve the source location + * based on the source map. + * + * The `error.stack` must not have been accessed. The resolution is based on the structured + * error stack data. + * @param {Error|object} error An error object, or an object being invoked with ErrorCaptureStackTrace + * @returns {{sourceLine: string, startColumn: number}|undefined} + */ +function getErrorSourceLocation(error) { + const pos = getErrorSourcePositions(error); + const { + sourceLine, + scriptResourceName, + lineNumber, + startColumn, + } = pos; + + // Source map is not enabled. Return the source line directly. + if (!getSourceMapsSupport().enabled) { + return { sourceLine, startColumn }; + } + + const sm = findSourceMap(scriptResourceName); + if (sm === undefined) { + return; + } + const { + originalLine, + originalColumn, + originalSource, + } = sm.findEntry(lineNumber - 1, startColumn); + const originalSourceLine = getSourceLine(sm, originalSource, originalLine, originalColumn); + + if (!originalSourceLine) { + return; + } + + return { + sourceLine: originalSourceLine, + startColumn: originalColumn, + }; +} + +const memberAccessTokens = [ '.', '?.', '[', ']' ]; +const memberNameTokens = [ 'name', 'string', 'num' ]; +let tokenizer; +/** + * Get the first expression in a code string at the startColumn. + * @param {string} code source code line + * @param {number} startColumn which column the error is constructed + * @returns {string} + */ +function getFirstExpression(code, startColumn) { + // Lazy load acorn. + if (tokenizer === undefined) { + const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser; + tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser); + } + + let lastToken; + let firstMemberAccessNameToken; + let terminatingCol; + let parenLvl = 0; + // Tokenize the line to locate the expression at the startColumn. + // The source line may be an incomplete JavaScript source, so do not parse the source line. + for (const token of tokenizer(code, { ecmaVersion: 'latest' })) { + // Peek before the startColumn. + if (token.start < startColumn) { + // There is a semicolon. This is a statement before the startColumn, so reset the memo. + if (token.type.label === ';') { + firstMemberAccessNameToken = null; + continue; + } + // Try to memo the member access expressions before the startColumn, so that the + // returned source code contains more info: + // assert.ok(value) + // ^ startColumn + // The member expression can also be like + // assert['ok'](value) or assert?.ok(value) + // ^ startColumn ^ startColumn + if (memberAccessTokens.includes(token.type.label) && lastToken?.type.label === 'name') { + // First member access name token must be a 'name'. + firstMemberAccessNameToken ??= lastToken; + } else if (!memberAccessTokens.includes(token.type.label) && + !memberNameTokens.includes(token.type.label)) { + // Reset the memo if it is not a simple member access. + // For example: assert[(() => 'ok')()](value) + // ^ startColumn + firstMemberAccessNameToken = null; + } + lastToken = token; + continue; + } + // Now after the startColumn, this must be an expression. + if (token.type.label === '(') { + parenLvl++; + continue; + } + if (token.type.label === ')') { + parenLvl--; + if (parenLvl === 0) { + // A matched closing parenthesis found after the startColumn, + // terminate here. Include the token. + // (assert.ok(false), assert.ok(true)) + // ^ startColumn + terminatingCol = token.start + 1; + break; + } + continue; + } + if (token.type.label === ';') { + // A semicolon found after the startColumn, terminate here. + // assert.ok(false); assert.ok(true)); + // ^ startColumn + terminatingCol = token; + break; + } + // If no semicolon found after the startColumn. The string after the + // startColumn must be the expression. + // assert.ok(false) + // ^ startColumn + } + const start = firstMemberAccessNameToken?.start ?? startColumn; + return StringPrototypeSlice(code, start, terminatingCol); +} + +/** + * Get the source expression of an error. If source map is enabled, resolve the source location + * based on the source map. + * + * The `error.stack` must not have been accessed, or the source location may be incorrect. The + * resolution is based on the structured error stack data. + * @param {Error|object} error An error object, or an object being invoked with ErrorCaptureStackTrace + * @returns {string|undefined} + */ +function getErrorSourceExpression(error) { + const loc = getErrorSourceLocation(error); + if (loc === undefined) { + return; + } + const { sourceLine, startColumn } = loc; + return getFirstExpression(sourceLine, startColumn); +} + +module.exports = { + getErrorSourceLocation, + getErrorSourceExpression, +}; diff --git a/lib/internal/source_map/prepare_stack_trace.js b/lib/internal/source_map/prepare_stack_trace.js index 814ea396f60144..da2f9dcd74b2bb 100644 --- a/lib/internal/source_map/prepare_stack_trace.js +++ b/lib/internal/source_map/prepare_stack_trace.js @@ -1,11 +1,9 @@ 'use strict'; const { - ArrayPrototypeIndexOf, ArrayPrototypeJoin, ArrayPrototypeMap, ErrorPrototypeToString, - RegExpPrototypeSymbolSplit, SafeStringIterator, StringPrototypeRepeat, StringPrototypeSlice, @@ -16,8 +14,7 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => { debug = fn; }); const { getStringWidth } = require('internal/util/inspect'); -const { readFileSync } = require('fs'); -const { findSourceMap } = require('internal/source_map/source_map_cache'); +const { findSourceMap, getSourceLine } = require('internal/source_map/source_map_cache'); const { kIsNodeError, } = require('internal/errors'); @@ -155,21 +152,13 @@ function getErrorSource( originalLine, originalColumn, ) { - const originalSourcePathNoScheme = - StringPrototypeStartsWith(originalSourcePath, 'file://') ? - fileURLToPath(originalSourcePath) : originalSourcePath; - const source = getOriginalSource( - sourceMap.payload, - originalSourcePath, - ); - if (typeof source !== 'string') { - return; - } - const lines = RegExpPrototypeSymbolSplit(/\r?\n/, source, originalLine + 1); - const line = lines[originalLine]; + const line = getSourceLine(sourceMap, originalSourcePath, originalLine); if (!line) { return; } + const originalSourcePathNoScheme = + StringPrototypeStartsWith(originalSourcePath, 'file://') ? + fileURLToPath(originalSourcePath) : originalSourcePath; // Display ^ in appropriate position, regardless of whether tabs or // spaces are used: @@ -182,39 +171,10 @@ function getErrorSource( prefix = StringPrototypeSlice(prefix, 0, -1); // The last character is '^'. const exceptionLine = - `${originalSourcePathNoScheme}:${originalLine + 1}\n${line}\n${prefix}^\n\n`; + `${originalSourcePathNoScheme}:${originalLine + 1}\n${line}\n${prefix}^\n`; return exceptionLine; } -/** - * Retrieve the original source code from the source map's `sources` list or disk. - * @param {import('internal/source_map/source_map').SourceMap.payload} payload - * @param {string} originalSourcePath - path or url of the original source - * @returns {string | undefined} - the source content or undefined if file not found - */ -function getOriginalSource(payload, originalSourcePath) { - let source; - // payload.sources has been normalized to be an array of absolute urls. - const sourceContentIndex = - ArrayPrototypeIndexOf(payload.sources, originalSourcePath); - if (payload.sourcesContent?.[sourceContentIndex]) { - // First we check if the original source content was provided in the - // source map itself: - source = payload.sourcesContent[sourceContentIndex]; - } else if (StringPrototypeStartsWith(originalSourcePath, 'file://')) { - // If no sourcesContent was found, attempt to load the original source - // from disk: - debug(`read source of ${originalSourcePath} from filesystem`); - const originalSourcePathNoScheme = fileURLToPath(originalSourcePath); - try { - source = readFileSync(originalSourcePathNoScheme, 'utf8'); - } catch (err) { - debug(err); - } - } - return source; -} - /** * Retrieve exact line in the original source code from the source map's `sources` list or disk. * @param {string} fileName - actual file name diff --git a/lib/internal/source_map/source_map_cache.js b/lib/internal/source_map/source_map_cache.js index 670e53890a3099..a4a95ad0ae49c3 100644 --- a/lib/internal/source_map/source_map_cache.js +++ b/lib/internal/source_map/source_map_cache.js @@ -1,13 +1,16 @@ 'use strict'; const { + ArrayPrototypeIndexOf, ArrayPrototypePush, JSONParse, ObjectFreeze, RegExpPrototypeExec, + RegExpPrototypeSymbolSplit, SafeMap, StringPrototypeCodePointAt, StringPrototypeSplit, + StringPrototypeStartsWith, } = primordials; // See https://tc39.es/ecma426/ for SourceMap V3 specification. @@ -16,6 +19,7 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => { debug = fn; }); +const { readFileSync } = require('fs'); const { validateBoolean, validateObject } = require('internal/validators'); const { setSourceMapsEnabled: setSourceMapsNative, @@ -277,8 +281,7 @@ function lineLengths(content) { */ function sourceMapFromFile(mapURL) { try { - const fs = require('fs'); - const content = fs.readFileSync(fileURLToPath(mapURL), 'utf8'); + const content = readFileSync(fileURLToPath(mapURL), 'utf8'); const data = JSONParse(content); return sourcesToAbsolute(mapURL, data); } catch (err) { @@ -400,8 +403,62 @@ function findSourceMap(sourceURL) { } } +/** + * Retrieve the original source code from the source map's `sources` list or disk. + * @param {import('internal/source_map/source_map').SourceMap.payload} payload + * @param {string} originalSourcePath - path or url of the original source + * @returns {string | undefined} - the source content or undefined if file not found + */ +function getOriginalSource(payload, originalSourcePath) { + let source; + // payload.sources has been normalized to be an array of absolute urls. + const sourceContentIndex = + ArrayPrototypeIndexOf(payload.sources, originalSourcePath); + if (payload.sourcesContent?.[sourceContentIndex]) { + // First we check if the original source content was provided in the + // source map itself: + source = payload.sourcesContent[sourceContentIndex]; + } else if (StringPrototypeStartsWith(originalSourcePath, 'file://')) { + // If no sourcesContent was found, attempt to load the original source + // from disk: + debug(`read source of ${originalSourcePath} from filesystem`); + const originalSourcePathNoScheme = fileURLToPath(originalSourcePath); + try { + source = readFileSync(originalSourcePathNoScheme, 'utf8'); + } catch (err) { + debug(err); + } + } + return source; +} + +/** + * Get the line of source in the source map. + * @param {import('internal/source_map/source_map').SourceMap} sourceMap + * @param {string} originalSourcePath path or url of the original source + * @param {number} originalLine line number in the original source + * @returns {string|undefined} source line if found + */ +function getSourceLine( + sourceMap, + originalSourcePath, + originalLine, +) { + const source = getOriginalSource( + sourceMap.payload, + originalSourcePath, + ); + if (typeof source !== 'string') { + return; + } + const lines = RegExpPrototypeSymbolSplit(/\r?\n/, source, originalLine + 1); + const line = lines[originalLine]; + return line; +} + module.exports = { findSourceMap, + getSourceLine, getSourceMapsSupport, setSourceMapsSupport, maybeCacheSourceMap, diff --git a/src/node_errors.cc b/src/node_errors.cc index 5f51add4cdf68a..ae8553ee2022d6 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -1041,6 +1041,46 @@ void PerIsolateMessageListener(Local message, Local error) { } } +void GetErrorSourcePositions(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Realm* realm = Realm::GetCurrent(args); + Local context = realm->context(); + + CHECK(args[0]->IsObject()); + + Local msg = Exception::CreateMessage(isolate, args[0]); + + // Message::GetEndColumn may not reflect the actual end column in all cases. + // So only expose startColumn to JS land. + Local names[] = { + OneByteString(isolate, "sourceLine"), + OneByteString(isolate, "scriptResourceName"), + OneByteString(isolate, "lineNumber"), + OneByteString(isolate, "startColumn"), + }; + + Local source_line; + if (!msg->GetSourceLine(context).ToLocal(&source_line)) { + return; + } + int line_number; + if (!msg->GetLineNumber(context).To(&line_number)) { + return; + } + + Local values[] = { + source_line, + msg->GetScriptOrigin().ResourceName(), + v8::Integer::New(isolate, line_number), + v8::Integer::New(isolate, msg->GetStartColumn()), + }; + Local info = + Object::New(isolate, v8::Null(isolate), names, values, arraysize(names)); + + args.GetReturnValue().Set(info); +} + void SetPrepareStackTraceCallback(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); CHECK(args[0]->IsFunction()); @@ -1106,6 +1146,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(SetEnhanceStackForFatalException); registry->Register(NoSideEffectsToString); registry->Register(TriggerUncaughtException); + registry->Register(GetErrorSourcePositions); } void Initialize(Local target, @@ -1133,6 +1174,8 @@ void Initialize(Local target, context, target, "noSideEffectsToString", NoSideEffectsToString); SetMethod( context, target, "triggerUncaughtException", TriggerUncaughtException); + SetMethod( + context, target, "getErrorSourcePositions", GetErrorSourcePositions); Isolate* isolate = context->GetIsolate(); Local exit_codes = Object::New(isolate); diff --git a/test/fixtures/source-map/output/source_map_assert_source_line.snapshot b/test/fixtures/source-map/output/source_map_assert_source_line.snapshot new file mode 100644 index 00000000000000..fe11794e9c032d --- /dev/null +++ b/test/fixtures/source-map/output/source_map_assert_source_line.snapshot @@ -0,0 +1,20 @@ +AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value: + + assert(false) + + at Object. (*/test/fixtures/source-map/output/source_map_assert_source_line.ts:11:3) + * + * + * + * + at TracingChannel.traceSync (node:diagnostics_channel:322:14) + * + * + * + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: false, + expected: true, + operator: '==', + diff: 'simple' +} diff --git a/test/fixtures/source-map/output/source_map_assert_source_line.ts b/test/fixtures/source-map/output/source_map_assert_source_line.ts new file mode 100644 index 00000000000000..f4d242aa508248 --- /dev/null +++ b/test/fixtures/source-map/output/source_map_assert_source_line.ts @@ -0,0 +1,14 @@ +// Flags: --enable-source-maps --experimental-transform-types --no-warnings + +require('../../../common'); +const assert = require('node:assert'); + +enum Bar { + makeSureTransformTypes, +} + +try { + assert(false); +} catch (e) { + console.log(e); +} diff --git a/test/fixtures/source-map/output/source_map_enclosing_function.snapshot b/test/fixtures/source-map/output/source_map_enclosing_function.snapshot index b60f988be3214e..a215a1ab9c0d2b 100644 --- a/test/fixtures/source-map/output/source_map_enclosing_function.snapshot +++ b/test/fixtures/source-map/output/source_map_enclosing_function.snapshot @@ -2,7 +2,6 @@ throw err ^ - Error: an error! at functionD (*/test/fixtures/source-map/enclosing-call-site.js:16:17) at functionC (*/test/fixtures/source-map/enclosing-call-site.js:10:3) diff --git a/test/fixtures/source-map/output/source_map_eval.snapshot b/test/fixtures/source-map/output/source_map_eval.snapshot index ff636e44063aa7..c808fb414473d6 100644 --- a/test/fixtures/source-map/output/source_map_eval.snapshot +++ b/test/fixtures/source-map/output/source_map_eval.snapshot @@ -2,7 +2,6 @@ alert "I knew it!" ^ - ReferenceError: alert is not defined at Object.eval (*/synthesized/workspace/tabs-source-url.coffee:26:2) at eval (*/synthesized/workspace/tabs-source-url.coffee:1:14) diff --git a/test/fixtures/source-map/output/source_map_reference_error_tabs.snapshot b/test/fixtures/source-map/output/source_map_reference_error_tabs.snapshot index 2043bd0a88e897..fce296c1d56848 100644 --- a/test/fixtures/source-map/output/source_map_reference_error_tabs.snapshot +++ b/test/fixtures/source-map/output/source_map_reference_error_tabs.snapshot @@ -2,7 +2,6 @@ alert "I knew it!" ^ - ReferenceError: alert is not defined at Object. (*/test/fixtures/source-map/tabs.coffee:26:2) at Object. (*/test/fixtures/source-map/tabs.coffee:1:14) diff --git a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot index f53aec68ce8bb3..2c9bd70cf4d45f 100644 --- a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot @@ -2,7 +2,6 @@ throw new Error('message') ^ - Error: message at Throw (*/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts:13:9) at async Promise.all (index 3) diff --git a/test/fixtures/source-map/output/source_map_throw_construct.snapshot b/test/fixtures/source-map/output/source_map_throw_construct.snapshot index dc28053240aa0b..292eb0b2eac353 100644 --- a/test/fixtures/source-map/output/source_map_throw_construct.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_construct.snapshot @@ -2,7 +2,6 @@ throw new Error('message'); ^ - Error: message at new Foo (*/test/fixtures/source-map/output/source_map_throw_construct.mts:13:11) at (*/test/fixtures/source-map/output/source_map_throw_construct.mts:17:1) diff --git a/test/fixtures/source-map/output/source_map_throw_first_tick.snapshot b/test/fixtures/source-map/output/source_map_throw_first_tick.snapshot index e129d73ef1581c..29346a81836160 100644 --- a/test/fixtures/source-map/output/source_map_throw_first_tick.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_first_tick.snapshot @@ -3,7 +3,6 @@ reachable throw Error('an exception'); ^ - Error: an exception at branch (*/test/fixtures/source-map/typescript-throw.ts:18:11) at Object. (*/test/fixtures/source-map/typescript-throw.ts:24:1) diff --git a/test/fixtures/source-map/output/source_map_throw_icu.snapshot b/test/fixtures/source-map/output/source_map_throw_icu.snapshot index 4b2853479b9576..8b556a584fec2e 100644 --- a/test/fixtures/source-map/output/source_map_throw_icu.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_icu.snapshot @@ -2,7 +2,6 @@ ("あ 🐕 🐕", throw Error("an error")); ^ - Error: an error at Object.createElement (*/test/fixtures/source-map/icu.jsx:3:23) at Object. (*/test/fixtures/source-map/icu.jsx:9:5) diff --git a/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot b/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot index 4cf4d52a16ea93..053ed5692d6a80 100644 --- a/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot @@ -2,7 +2,6 @@ throw Error('goodbye'); ^ - Error: goodbye at Hello (*/test/fixtures/source-map/uglify-throw-original.js:5:9) at Immediate. (*/test/fixtures/source-map/uglify-throw-original.js:9:3) diff --git a/test/parallel/test-assert-builtins-not-read-from-filesystem.js b/test/parallel/test-assert-builtins-not-read-from-filesystem.js deleted file mode 100644 index 7a713a2ea432c1..00000000000000 --- a/test/parallel/test-assert-builtins-not-read-from-filesystem.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -// Do not read filesystem when creating AssertionError messages for code in -// builtin modules. - -require('../common'); -const assert = require('assert'); -const EventEmitter = require('events'); -const e = new EventEmitter(); -e.on('hello', assert); - -if (process.argv[2] !== 'child') { - const tmpdir = require('../common/tmpdir'); - tmpdir.refresh(); - const { spawnSync } = require('child_process'); - - let threw = false; - try { - e.emit('hello', false); - } catch (err) { - const frames = err.stack.split('\n'); - const [, filename, line, column] = frames[1].match(/\((.+):(\d+):(\d+)\)/); - // Spawn a child process to avoid the error having been cached in the assert - // module's `errorCache` Map. - - const { output, status, error } = - spawnSync(process.execPath, - [process.argv[1], 'child', filename, line, column], - { cwd: tmpdir.path, env: process.env }); - assert.ifError(error); - assert.strictEqual(status, 0, `Exit code: ${status}\n${output}`); - threw = true; - } - assert.ok(threw); -} else { - const { writeFileSync } = require('fs'); - const [, , , filename, line, column] = process.argv; - const data = `${'\n'.repeat(line - 1)}${' '.repeat(column - 1)}` + - 'ok(failed(badly));'; - - writeFileSync(filename, data); - assert.throws( - () => e.emit('hello', false), - { - message: 'false == true' - } - ); -} diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index ae3df139cd68b7..ee48c0e1e24817 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -624,7 +624,7 @@ test('Test strict assert', () => { code: 'ERR_ASSERTION', constructor: strict.AssertionError, message: 'The expression evaluated to a falsy value:\n\n ' + - "strict.ok(\n typeof 123 === 'string'\n )\n" + 'strict.ok(\n' } ); Error.stackTraceLimit = tmpLimit; @@ -1017,20 +1017,20 @@ test('Additional asserts', () => { } ); - // Works in eval. + // Works in eval. Source code in eval can be shown. assert.throws( () => new Function('assert', 'assert(1 === 2);')(assert), { code: 'ERR_ASSERTION', constructor: assert.AssertionError, - message: 'false == true' + message: 'The expression evaluated to a falsy value:\n\n assert(1 === 2)\n' } ); assert.throws( () => eval('console.log("FOO");\nassert.ok(1 === 2);'), { code: 'ERR_ASSERTION', - message: 'false == true' + message: 'The expression evaluated to a falsy value:\n\n assert.ok(1 === 2)\n' } ); diff --git a/test/parallel/test-node-output-sourcemaps.mjs b/test/parallel/test-node-output-sourcemaps.mjs index c11c2c36735dae..2d0e784e206b0b 100644 --- a/test/parallel/test-node-output-sourcemaps.mjs +++ b/test/parallel/test-node-output-sourcemaps.mjs @@ -14,6 +14,7 @@ describe('sourcemaps output', { concurrency: !process.env.TEST_PARALLEL }, () => ); const tests = [ + { name: 'source-map/output/source_map_assert_source_line.ts' }, { name: 'source-map/output/source_map_disabled_by_api.js' }, { name: 'source-map/output/source_map_disabled_by_process_api.js' }, { name: 'source-map/output/source_map_enabled_by_api.js' },