Skip to content

Commit 6b16836

Browse files
BridgeARtargos
authored andcommitted
util: mark cwd grey while inspecting errors
This changes the util.inspect() output for errors in case stack traces contain the current working directory in their trace. If that's the case, the cwd part is marked grey to focus on the rest of the path. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de> PR-URL: #41082 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 40f51d8 commit 6b16836

File tree

5 files changed

+190
-88
lines changed

5 files changed

+190
-88
lines changed

lib/events.js

+6-30
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
'use strict';
2323

2424
const {
25-
ArrayPrototypeIndexOf,
2625
ArrayPrototypeJoin,
2726
ArrayPrototypeShift,
2827
ArrayPrototypeSlice,
@@ -33,7 +32,6 @@ const {
3332
ErrorCaptureStackTrace,
3433
FunctionPrototypeBind,
3534
FunctionPrototypeCall,
36-
MathMin,
3735
NumberIsNaN,
3836
ObjectCreate,
3937
ObjectDefineProperty,
@@ -55,7 +53,10 @@ const kRejection = SymbolFor('nodejs.rejection');
5553

5654
const { kEmptyObject } = require('internal/util');
5755

58-
const { inspect } = require('internal/util/inspect');
56+
const {
57+
inspect,
58+
identicalSequenceRange,
59+
} = require('internal/util/inspect');
5960

6061
let spliceOne;
6162

@@ -424,31 +425,6 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
424425
return _getMaxListeners(this);
425426
};
426427

427-
// Returns the length and line number of the first sequence of `a` that fully
428-
// appears in `b` with a length of at least 4.
429-
function identicalSequenceRange(a, b) {
430-
for (let i = 0; i < a.length - 3; i++) {
431-
// Find the first entry of b that matches the current entry of a.
432-
const pos = ArrayPrototypeIndexOf(b, a[i]);
433-
if (pos !== -1) {
434-
const rest = b.length - pos;
435-
if (rest > 3) {
436-
let len = 1;
437-
const maxLen = MathMin(a.length - i, rest);
438-
// Count the number of consecutive entries.
439-
while (maxLen > len && a[i + len] === b[pos + len]) {
440-
len++;
441-
}
442-
if (len > 3) {
443-
return [len, i];
444-
}
445-
}
446-
}
447-
}
448-
449-
return [0, 0];
450-
}
451-
452428
function enhanceStackTrace(err, own) {
453429
let ctorInfo = '';
454430
try {
@@ -465,9 +441,9 @@ function enhanceStackTrace(err, own) {
465441
const ownStack = ArrayPrototypeSlice(
466442
StringPrototypeSplit(own.stack, '\n'), 1);
467443

468-
const { 0: len, 1: off } = identicalSequenceRange(ownStack, errStack);
444+
const { len, offset } = identicalSequenceRange(ownStack, errStack);
469445
if (len > 0) {
470-
ArrayPrototypeSplice(ownStack, off + 1, len - 2,
446+
ArrayPrototypeSplice(ownStack, offset + 1, len - 2,
471447
' [... lines matching original stack trace ...]');
472448
}
473449

lib/internal/util/inspect.js

+77-13
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ const {
146146
} = require('internal/validators');
147147

148148
let hexSlice;
149+
let internalUrl;
150+
151+
function pathToFileUrlHref(filepath) {
152+
internalUrl ??= require('internal/url');
153+
return internalUrl.pathToFileURL(filepath).href;
154+
}
149155

150156
const builtInObjects = new SafeSet(
151157
ArrayPrototypeFilter(
@@ -1283,6 +1289,58 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) {
12831289
}
12841290
}
12851291

1292+
function markNodeModules(ctx, line) {
1293+
let tempLine = '';
1294+
let nodeModule;
1295+
let pos = 0;
1296+
while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {
1297+
// '/node_modules/'.length === 14
1298+
tempLine += line.slice(pos, nodeModule.index + 14);
1299+
tempLine += ctx.stylize(nodeModule[1], 'module');
1300+
pos = nodeModule.index + nodeModule[0].length;
1301+
}
1302+
if (pos !== 0) {
1303+
line = tempLine + line.slice(pos);
1304+
}
1305+
return line;
1306+
}
1307+
1308+
function markCwd(ctx, line, workingDirectory) {
1309+
let cwdStartPos = line.indexOf(workingDirectory);
1310+
let tempLine = '';
1311+
let cwdLength = workingDirectory.length;
1312+
if (cwdStartPos !== -1) {
1313+
if (line.slice(cwdStartPos - 7, cwdStartPos) === 'file://') {
1314+
cwdLength += 7;
1315+
cwdStartPos -= 7;
1316+
}
1317+
const start = line[cwdStartPos - 1] === '(' ? cwdStartPos - 1 : cwdStartPos;
1318+
const end = start !== cwdStartPos && line.endsWith(')') ? -1 : line.length;
1319+
const workingDirectoryEndPos = cwdStartPos + cwdLength + 1;
1320+
const cwdSlice = line.slice(start, workingDirectoryEndPos);
1321+
1322+
tempLine += line.slice(0, start);
1323+
tempLine += ctx.stylize(cwdSlice, 'undefined');
1324+
tempLine += line.slice(workingDirectoryEndPos, end);
1325+
if (end === -1) {
1326+
tempLine += ctx.stylize(')', 'undefined');
1327+
}
1328+
} else {
1329+
tempLine += line;
1330+
}
1331+
return tempLine;
1332+
}
1333+
1334+
function safeGetCWD() {
1335+
let workingDirectory;
1336+
try {
1337+
workingDirectory = process.cwd();
1338+
} catch {
1339+
return;
1340+
}
1341+
return workingDirectory;
1342+
}
1343+
12861344
function formatError(err, constructor, tag, ctx, keys) {
12871345
const name = err.name != null ? String(err.name) : 'Error';
12881346
let stack = getStackString(err);
@@ -1306,25 +1364,30 @@ function formatError(err, constructor, tag, ctx, keys) {
13061364
stack = `[${stack}]`;
13071365
} else {
13081366
let newStack = stack.slice(0, stackStart);
1309-
const lines = getStackFrames(ctx, err, stack.slice(stackStart + 1));
1367+
const stackFramePart = stack.slice(stackStart + 1);
1368+
const lines = getStackFrames(ctx, err, stackFramePart);
13101369
if (ctx.colors) {
13111370
// Highlight userland code and node modules.
1312-
for (const line of lines) {
1371+
const workingDirectory = safeGetCWD();
1372+
let esmWorkingDirectory;
1373+
for (let line of lines) {
13131374
const core = line.match(coreModuleRegExp);
13141375
if (core !== null && NativeModule.exists(core[1])) {
13151376
newStack += `\n${ctx.stylize(line, 'undefined')}`;
13161377
} else {
1317-
// This adds underscores to all node_modules to quickly identify them.
1318-
let nodeModule;
13191378
newStack += '\n';
1320-
let pos = 0;
1321-
while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {
1322-
// '/node_modules/'.length === 14
1323-
newStack += line.slice(pos, nodeModule.index + 14);
1324-
newStack += ctx.stylize(nodeModule[1], 'module');
1325-
pos = nodeModule.index + nodeModule[0].length;
1379+
1380+
line = markNodeModules(ctx, line);
1381+
if (workingDirectory !== undefined) {
1382+
let newLine = markCwd(ctx, line, workingDirectory);
1383+
if (newLine === line) {
1384+
esmWorkingDirectory ??= pathToFileUrlHref(workingDirectory);
1385+
newLine = markCwd(ctx, line, esmWorkingDirectory);
1386+
}
1387+
line = newLine;
13261388
}
1327-
newStack += pos === 0 ? line : line.slice(pos);
1389+
1390+
newStack += line;
13281391
}
13291392
}
13301393
} else {
@@ -2292,10 +2355,11 @@ function stripVTControlCharacters(str) {
22922355
}
22932356

22942357
module.exports = {
2358+
identicalSequenceRange,
22952359
inspect,
2360+
inspectDefaultOptions,
22962361
format,
22972362
formatWithOptions,
22982363
getStringWidth,
2299-
inspectDefaultOptions,
2300-
stripVTControlCharacters
2364+
stripVTControlCharacters,
23012365
};

test/parallel/test-util-inspect.js

+71-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const v8 = require('v8');
3030
const { previewEntries } = internalBinding('util');
3131
const { inspect } = util;
3232
const { MessageChannel } = require('worker_threads');
33+
const url = require('url');
3334

3435
assert.strictEqual(util.inspect(1), '1');
3536
assert.strictEqual(util.inspect(false), 'false');
@@ -2811,9 +2812,15 @@ assert.strictEqual(
28112812
}
28122813

28132814
{
2815+
const originalCWD = process.cwd();
2816+
2817+
process.cwd = () => (process.platform === 'win32' ?
2818+
'C:\\workspace\\node-test-binary-windows js-suites-%percent-encoded\\node' :
2819+
'/home/user directory/repository%encoded/node');
2820+
28142821
// Use a fake stack to verify the expected colored outcome.
28152822
const stack = [
2816-
'TypedError: Wonderful message!',
2823+
'Error: CWD is grayed out, even cwd that are percent encoded!',
28172824
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
28182825
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
28192826
' at Fancy (node:vm:697:32)',
@@ -2823,23 +2830,78 @@ assert.strictEqual(
28232830
// This file is not an actual Node.js core file.
28242831
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
28252832
' at require (node:internal/modules/cjs/helpers:14:16)',
2833+
' at Array.forEach (<anonymous>)',
2834+
` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`,
2835+
` at Object.<anonymous> (${process.cwd()}/node_modules/hyper_module/folder/file.js:2753:10)`,
28262836
' at /test/test-util-inspect.js:2239:9',
28272837
' at getActual (node:assert:592:5)',
28282838
];
2829-
const isNodeCoreFile = [
2830-
false, false, true, true, false, true, false, true, false, true,
2831-
];
2832-
const err = new TypeError('Wonderful message!');
2839+
const err = new Error('CWD is grayed out, even cwd that are percent encoded!');
28332840
err.stack = stack.join('\n');
2841+
if (process.platform === 'win32') {
2842+
err.stack = stack.map((frame) => (frame.includes('node:') ?
2843+
frame :
2844+
frame.replaceAll('/', '\\'))
2845+
).join('\n');
2846+
}
2847+
const escapedCWD = util.inspect(process.cwd()).slice(1, -1);
28342848
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2835-
let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => {
2849+
let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => {
28362850
return `node_modules/\u001b[4m${m}\u001b[24m`;
2851+
}).replaceAll(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, 'gi'), (_, m) => {
2852+
return `\x1B[90m${m}\x1B[39m`;
28372853
});
2838-
if (isNodeCoreFile[i]) {
2839-
actual = `\u001b[90m${actual}\u001b[39m`;
2854+
if (expected.includes(process.cwd()) && expected.endsWith(')')) {
2855+
expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`;
28402856
}
2841-
assert.strictEqual(actual, line);
2857+
if (line.includes('node:')) {
2858+
if (!line.includes('foo') && !line.includes('aaa')) {
2859+
expected = `\u001b[90m${expected}\u001b[39m`;
2860+
}
2861+
} else if (process.platform === 'win32') {
2862+
expected = expected.replaceAll('/', '\\');
2863+
}
2864+
assert.strictEqual(line, expected);
28422865
});
2866+
2867+
// Check ESM
2868+
const encodedCwd = url.pathToFileURL(process.cwd());
2869+
const sl = process.platform === 'win32' ? '\\' : '/';
2870+
2871+
// Use a fake stack to verify the expected colored outcome.
2872+
err.stack = 'Error: ESM and CJS mixed are both grayed out!\n' +
2873+
` at ${encodedCwd}/test/parallel/test-esm.mjs:2760:12\n` +
2874+
` at Object.<anonymous> (${encodedCwd}/node_modules/esm_module/folder/file.js:2753:10)\n` +
2875+
` at ${process.cwd()}${sl}test${sl}parallel${sl}test-cjs.js:2760:12\n` +
2876+
` at Object.<anonymous> (${process.cwd()}${sl}node_modules${sl}cjs_module${sl}folder${sl}file.js:2753:10)`;
2877+
2878+
let actual = util.inspect(err, { colors: true });
2879+
let expected = 'Error: ESM and CJS mixed are both grayed out!\n' +
2880+
` at \x1B[90m${encodedCwd}/\x1B[39mtest/parallel/test-esm.mjs:2760:12\n` +
2881+
` at Object.<anonymous> \x1B[90m(${encodedCwd}/\x1B[39mnode_modules/\x1B[4mesm_module\x1B[24m/folder/file.js:2753:10\x1B[90m)\x1B[39m\n` +
2882+
` at \x1B[90m${process.cwd()}${sl}\x1B[39mtest${sl}parallel${sl}test-cjs.js:2760:12\n` +
2883+
` at Object.<anonymous> \x1B[90m(${process.cwd()}${sl}\x1B[39mnode_modules${sl}\x1B[4mcjs_module\x1B[24m${sl}folder${sl}file.js:2753:10\x1B[90m)\x1B[39m`;
2884+
2885+
assert.strictEqual(actual, expected);
2886+
2887+
// ESM without need for encoding
2888+
process.cwd = () => (process.platform === 'win32' ?
2889+
'C:\\workspace\\node-test-binary-windows-js-suites\\node' :
2890+
'/home/user/repository/node');
2891+
let expectedCwd = process.cwd();
2892+
if (process.platform === 'win32') {
2893+
expectedCwd = `/${expectedCwd.replaceAll('\\', '/')}`;
2894+
}
2895+
// Use a fake stack to verify the expected colored outcome.
2896+
err.stack = 'Error: ESM without need for encoding!\n' +
2897+
` at file://${expectedCwd}/file.js:15:15`;
2898+
2899+
actual = util.inspect(err, { colors: true });
2900+
expected = 'Error: ESM without need for encoding!\n' +
2901+
` at \x1B[90mfile://${expectedCwd}/\x1B[39mfile.js:15:15`;
2902+
assert.strictEqual(actual, expected);
2903+
2904+
process.cwd = originalCWD;
28432905
}
28442906

28452907
{

test/pseudo-tty/console_colors.out

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
1-
{ foo: *[32m'bar'*[39m }
1+
{ foo: [32m'bar'[39m }
22
string q
3-
{ foo: *[32m'bar'*[39m } with object format param
3+
{ foo: [32m'bar'[39m } with object format param
44

55
Error: test
66
at abc (../fixtures/node_modules/bar.js:4:4)
77
foobar
8-
at * (*console_colors.js:*:*)
9-
*[90m at * (node:internal*:*:*)*[39m
10-
*[90m at *[39m
11-
*[90m at *[39m
12-
*[90m at *[39m
13-
*[90m at *[39m
14-
*[90m at *[39m
8+
at Object.<anonymous> [90m(*[39m*console_colors.js:*:*[90m)[39m
9+
[90m at * (node:internal*:*:*)[39m
10+
[90m at *[39m
11+
[90m at *[39m
12+
[90m at *[39m
13+
[90m at *[39m
14+
[90m at *[39m
1515

1616
Error: Should not ever get here.
17-
at * (*node_modules*[4m*node_modules*[24m*bar.js:*:*)
18-
*[90m at *[39m
19-
*[90m at *[39m
20-
*[90m at *[39m
21-
*[90m at *[39m
22-
*[90m at *[39m
23-
*[90m at *[39m
24-
at * (*console_colors.js:*:*)
25-
*[90m at *[39m
26-
*[90m at *[39m
17+
at Object.<anonymous> [90m(*node_modules*[4m*node_modules*[24m*bar.js:*:*[90m)[39m
18+
[90m at *[39m
19+
[90m at *[39m
20+
[90m at *[39m
21+
[90m at *[39m
22+
[90m at *[39m
23+
[90m at *[39m
24+
at Object.<anonymous> [90m(*console_colors.js:*:*[90m)[39m
25+
[90m at *[39m
26+
[90m at *[39m
2727

2828
Error
2929
at evalmachine.<anonymous>:*:*
30-
*[90m at Script.runInThisContext (node:vm:*:*)*[39m
31-
*[90m at Object.runInThisContext (node:vm:*:*)*[39m
32-
at * (*console_colors.js:*:*)
33-
*[90m at *[39m
34-
*[90m at *[39m
35-
*[90m at *[39m
36-
*[90m at *[39m
37-
*[90m at *[39m
38-
*[90m at *[39m
30+
[90m at Script.runInThisContext (node:vm:*:*)[39m
31+
[90m at Object.runInThisContext (node:vm:*:*)[39m
32+
at Object.<anonymous> [90m(*[39m*console_colors.js:*:*[90m)[39m
33+
[90m at *[39m
34+
[90m at *[39m
35+
[90m at *[39m
36+
[90m at *[39m
37+
[90m at *[39m
38+
[90m at *[39m

0 commit comments

Comments
 (0)