diff --git a/lib/events.js b/lib/events.js index 6d838d6d112dd9..45cb60666e93ff 100644 --- a/lib/events.js +++ b/lib/events.js @@ -22,7 +22,6 @@ 'use strict'; const { - ArrayPrototypeIndexOf, ArrayPrototypeJoin, ArrayPrototypeShift, ArrayPrototypeSlice, @@ -32,7 +31,6 @@ const { ErrorCaptureStackTrace, FunctionPrototypeBind, FunctionPrototypeCall, - MathMin, NumberIsNaN, ObjectCreate, ObjectDefineProperty, @@ -50,7 +48,10 @@ const { SymbolAsyncIterator, } = primordials; const kRejection = SymbolFor('nodejs.rejection'); -const { inspect } = require('internal/util/inspect'); +const { + inspect, + identicalSequenceRange +} = require('internal/util/inspect'); let spliceOne; @@ -282,31 +283,6 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners() { return _getMaxListeners(this); }; -// Returns the length and line number of the first sequence of `a` that fully -// appears in `b` with a length of at least 4. -function identicalSequenceRange(a, b) { - for (let i = 0; i < a.length - 3; i++) { - // Find the first entry of b that matches the current entry of a. - const pos = ArrayPrototypeIndexOf(b, a[i]); - if (pos !== -1) { - const rest = b.length - pos; - if (rest > 3) { - let len = 1; - const maxLen = MathMin(a.length - i, rest); - // Count the number of consecutive entries. - while (maxLen > len && a[i + len] === b[pos + len]) { - len++; - } - if (len > 3) { - return [len, i]; - } - } - } - } - - return [0, 0]; -} - function enhanceStackTrace(err, own) { let ctorInfo = ''; try { @@ -321,9 +297,9 @@ function enhanceStackTrace(err, own) { const ownStack = ArrayPrototypeSlice( StringPrototypeSplit(own.stack, '\n'), 1); - const { 0: len, 1: off } = identicalSequenceRange(ownStack, errStack); + const { len, offset } = identicalSequenceRange(ownStack, errStack); if (len > 0) { - ArrayPrototypeSplice(ownStack, off + 1, len - 2, + ArrayPrototypeSplice(ownStack, offset + 1, len - 2, ' [... lines matching original stack trace ...]'); } diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index a4db785042965f..aabf846b375bf1 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -1271,6 +1271,49 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) { } } +function markNodeModules(ctx, line) { + let tempLine = ''; + let nodeModule; + let pos = 0; + while (nodeModule = nodeModulesRegExp.exec(line)) { + // '/node_modules/'.length === 14 + tempLine += line.slice(pos, nodeModule.index + 14); + tempLine += ctx.stylize(nodeModule[1], 'module'); + pos = nodeModule.index + nodeModule[0].length; + } + if (pos !== 0) { + line = tempLine + line.slice(pos); + } + return line; +} + +function markCwd(ctx, line, workingDirectory) { + const cwdStartPos = line.indexOf(workingDirectory); + let tempLine = ''; + if (cwdStartPos !== -1) { + const start = line[cwdStartPos - 1] === '(' ? cwdStartPos - 1 : cwdStartPos; + const end = start !== cwdStartPos && line.endsWith(')') ? -1 : line.length; + const workingDirectoryEndPos = cwdStartPos + workingDirectory.length + 1; + const cwdSlice = line.slice(start, workingDirectoryEndPos); + + tempLine += line.slice(0, start); + tempLine += ctx.stylize(cwdSlice, 'undefined'); + tempLine += line.slice(workingDirectoryEndPos, end); + if (end === -1) { + tempLine += ctx.stylize(')', 'undefined'); + } + } else { + tempLine += line; + } + return tempLine; +} + +function safeGetCWD() { + try { + return process.cwd(); + } catch {} +} + function formatError(err, constructor, tag, ctx, keys) { const name = err.name != null ? String(err.name) : 'Error'; let stack = getStackString(err); @@ -1296,22 +1339,20 @@ function formatError(err, constructor, tag, ctx, keys) { const lines = getStackFrames(ctx, err, stack.slice(stackStart + 1)); if (ctx.colors) { // Highlight userland code and node modules. - for (const line of lines) { + const workingDirectory = safeGetCWD(); + for (let line of lines) { const core = line.match(coreModuleRegExp); if (core !== null && NativeModule.exists(core[1])) { newStack += `\n${ctx.stylize(line, 'undefined')}`; } else { - // This adds underscores to all node_modules to quickly identify them. - let nodeModule; newStack += '\n'; - let pos = 0; - while (nodeModule = nodeModulesRegExp.exec(line)) { - // '/node_modules/'.length === 14 - newStack += line.slice(pos, nodeModule.index + 14); - newStack += ctx.stylize(nodeModule[1], 'module'); - pos = nodeModule.index + nodeModule[0].length; + + line = markNodeModules(ctx, line); + if (workingDirectory !== undefined) { + line = markCwd(ctx, line, workingDirectory); } - newStack += pos === 0 ? line : line.slice(pos); + + newStack += line; } } } else { @@ -2267,10 +2308,11 @@ function stripVTControlCharacters(str) { } module.exports = { + identicalSequenceRange, inspect, + inspectDefaultOptions, format, formatWithOptions, getStringWidth, - inspectDefaultOptions, - stripVTControlCharacters + stripVTControlCharacters, }; diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index aeb6359fe331b7..000a2657de109c 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -2772,35 +2772,62 @@ assert.strictEqual( } { - // Use a fake stack to verify the expected colored outcome. - const stack = [ - 'TypedError: Wonderful message!', - ' at A. (/test/node_modules/foo/node_modules/bar/baz.js:2:7)', - ' at Module._compile (node:internal/modules/cjs/loader:827:30)', - ' at Fancy (node:vm:697:32)', - // This file is not an actual Node.js core file. - ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', - ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', - // This file is not an actual Node.js core file. - ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', - ' at require (node:internal/modules/cjs/helpers:14:16)', - ' at /test/test-util-inspect.js:2239:9', - ' at getActual (node:assert:592:5)', - ]; - const isNodeCoreFile = [ - false, false, true, true, false, true, false, true, false, true, - ]; - const err = new TypeError('Wonderful message!'); - err.stack = stack.join('\n'); - util.inspect(err, { colors: true }).split('\n').forEach((line, i) => { - let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => { - return `node_modules/\u001b[4m${m}\u001b[24m`; - }); - if (isNodeCoreFile[i]) { - actual = `\u001b[90m${actual}\u001b[39m`; + const originalCWD = process.cwd(); + + for (const os of ['windows', 'unix']) { + process.cwd = () => (os === 'windows' ? + 'C:\\workspace\\node-test-binary-windows-js-suites\\node' : + '/home/user/repository/node'); + + // Use a fake stack to verify the expected colored outcome. + const stack = [ + 'TypedError: Wonderful message!', + ' at A. (/t/node_modules/foo/node_modules/bar/baz.js:2:7)', + ' at Module._compile (node:internal/modules/cjs/loader:827:30)', + ' at Fancy (node:vm:697:32)', + // This file is not an actual Node.js core file. + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', + // This file is not an actual Node.js core file. + ' at Module.require [as weird/name] (node:internal/aaa/loader:735:19)', + ' at require (node:internal/modules/cjs/helpers:14:16)', + ' at Array.forEach ()', + ` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`, + ` at Object. (${process.cwd()}/node_modules/hyper_module` + + '/folder/file.js:2753:10)', + ' at /test/test-util-inspect.js:2239:9', + ' at getActual (node:assert:592:5)', + ]; + const err = new TypeError('Wonderful message!'); + err.stack = stack.join('\n'); + if (os === 'windows') { + err.stack = stack.map((frame) => (frame.includes('node:') ? + frame : + frame.replaceAll('/', '\\')) + ).join('\n'); } - assert.strictEqual(actual, line); - }); + const escapedCWD = util.inspect(process.cwd()).slice(1, -1); + util.inspect(err, { colors: true }).split('\n').forEach((line, i) => { + let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => { + return `node_modules/\u001b[4m${m}\u001b[24m`; + }).replaceAll(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, 'gi'), (_, m) => { + return `\x1B[90m${m}\x1B[39m`; + }); + if (expected.includes(process.cwd()) && expected.endsWith(')')) { + expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`; + } + if (line.includes('node:')) { + if (!line.includes('foo') && !line.includes('aaa')) { + expected = `\u001b[90m${expected}\u001b[39m`; + } + } else if (os === 'windows') { + expected = expected.replaceAll('/', '\\'); + } + assert.strictEqual(line, expected); + }); + } + + process.cwd = originalCWD; } { diff --git a/test/pseudo-tty/console_colors.out b/test/pseudo-tty/console_colors.out index 8766302ffd7e51..006eb9edfe617d 100644 --- a/test/pseudo-tty/console_colors.out +++ b/test/pseudo-tty/console_colors.out @@ -1,38 +1,38 @@ -{ foo: *[32m'bar'*[39m } +{ foo: [32m'bar'[39m } string q -{ foo: *[32m'bar'*[39m } with object format param +{ foo: [32m'bar'[39m } with object format param Error: test at abc (../fixtures/node_modules/bar.js:4:4) foobar - at * (*console_colors.js:*:*) -*[90m at * (node:internal*:*:*)*[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m + at Object. [90m(*[39m*console_colors.js:*:*[90m)[39m +[90m at * (node:internal*:*:*)[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m Error: Should not ever get here. - at * (*node_modules*[4m*node_modules*[24m*bar.js:*:*) -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m - at * (*console_colors.js:*:*) -*[90m at *[39m -*[90m at *[39m + at Object. [90m(*node_modules*[4m*node_modules*[24m*bar.js:*:*[90m)[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m + at Object. [90m(*console_colors.js:*:*[90m)[39m +[90m at *[39m +[90m at *[39m Error at evalmachine.:*:* -*[90m at Script.runInThisContext (node:vm:*:*)*[39m -*[90m at Object.runInThisContext (node:vm:*:*)*[39m - at * (*console_colors.js:*:*) -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m -*[90m at *[39m +[90m at Script.runInThisContext (node:vm:*:*)[39m +[90m at Object.runInThisContext (node:vm:*:*)[39m + at Object. [90m(*[39m*console_colors.js:*:*[90m)[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m +[90m at *[39m diff --git a/test/pseudo-tty/test-fatal-error.out b/test/pseudo-tty/test-fatal-error.out index 72e2bbba04e573..3fe1eed39a021e 100644 --- a/test/pseudo-tty/test-fatal-error.out +++ b/test/pseudo-tty/test-fatal-error.out @@ -3,14 +3,14 @@ throw err; ^ TypeError: foobar - at Object. (*test-fatal-error.js:*) -*[90m at *(node:internal*loader:*:*)*[39m -*[90m at *(node:internal*loader:*:*)*[39m -*[90m at *(node:internal*loader:*:*)*[39m -*[90m at *(node:internal*loader:*:*)*[39m -*[90m at *[39m -*[90m at *[39m { - bla: *[33mtrue*[39m + at Object. [90m(*test-fatal-error.js:*:*[90m)[39m +[90m at *(node:internal*loader:*:*)[39m +[90m at *(node:internal*loader:*:*)[39m +[90m at *(node:internal*loader:*:*)[39m +[90m at *(node:internal*loader:*:*)[39m +[90m at *[39m +[90m at *[39m { + bla: [33mtrue[39m } Node.js *