Skip to content

Commit 73bcb9f

Browse files
committed
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 <[email protected]>
1 parent ea65e1f commit 73bcb9f

File tree

5 files changed

+120
-75
lines changed

5 files changed

+120
-75
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,
@@ -32,7 +31,6 @@ const {
3231
ErrorCaptureStackTrace,
3332
FunctionPrototypeBind,
3433
FunctionPrototypeCall,
35-
MathMin,
3634
NumberIsNaN,
3735
ObjectCreate,
3836
ObjectDefineProperty,
@@ -50,7 +48,10 @@ const {
5048
SymbolAsyncIterator,
5149
} = primordials;
5250
const kRejection = SymbolFor('nodejs.rejection');
53-
const { inspect } = require('internal/util/inspect');
51+
const {
52+
inspect,
53+
identicalSequenceRange
54+
} = require('internal/util/inspect');
5455

5556
let spliceOne;
5657

@@ -282,31 +283,6 @@ EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
282283
return _getMaxListeners(this);
283284
};
284285

285-
// Returns the length and line number of the first sequence of `a` that fully
286-
// appears in `b` with a length of at least 4.
287-
function identicalSequenceRange(a, b) {
288-
for (let i = 0; i < a.length - 3; i++) {
289-
// Find the first entry of b that matches the current entry of a.
290-
const pos = ArrayPrototypeIndexOf(b, a[i]);
291-
if (pos !== -1) {
292-
const rest = b.length - pos;
293-
if (rest > 3) {
294-
let len = 1;
295-
const maxLen = MathMin(a.length - i, rest);
296-
// Count the number of consecutive entries.
297-
while (maxLen > len && a[i + len] === b[pos + len]) {
298-
len++;
299-
}
300-
if (len > 3) {
301-
return [len, i];
302-
}
303-
}
304-
}
305-
}
306-
307-
return [0, 0];
308-
}
309-
310286
function enhanceStackTrace(err, own) {
311287
let ctorInfo = '';
312288
try {
@@ -321,9 +297,9 @@ function enhanceStackTrace(err, own) {
321297
const ownStack = ArrayPrototypeSlice(
322298
StringPrototypeSplit(own.stack, '\n'), 1);
323299

324-
const { 0: len, 1: off } = identicalSequenceRange(ownStack, errStack);
300+
const { len, offset } = identicalSequenceRange(ownStack, errStack);
325301
if (len > 0) {
326-
ArrayPrototypeSplice(ownStack, off + 1, len - 2,
302+
ArrayPrototypeSplice(ownStack, offset + 1, len - 2,
327303
' [... lines matching original stack trace ...]');
328304
}
329305

lib/internal/util/inspect.js

+54-12
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,49 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) {
12591259
}
12601260
}
12611261

1262+
function markNodeModules(ctx, line) {
1263+
let tempLine = '';
1264+
let nodeModule;
1265+
let pos = 0;
1266+
while (nodeModule = nodeModulesRegExp.exec(line)) {
1267+
// '/node_modules/'.length === 14
1268+
tempLine += line.slice(pos, nodeModule.index + 14);
1269+
tempLine += ctx.stylize(nodeModule[1], 'module');
1270+
pos = nodeModule.index + nodeModule[0].length;
1271+
}
1272+
if (pos !== 0) {
1273+
line = tempLine + line.slice(pos);
1274+
}
1275+
return line;
1276+
}
1277+
1278+
function markCwd(ctx, line, workingDirectory) {
1279+
const cwdStartPos = line.indexOf(workingDirectory);
1280+
let tempLine = '';
1281+
if (cwdStartPos !== -1) {
1282+
const start = line[cwdStartPos - 1] === '(' ? cwdStartPos - 1 : cwdStartPos;
1283+
const end = start !== cwdStartPos && line.endsWith(')') ? -1 : line.length;
1284+
const workingDirectoryEndPos = cwdStartPos + workingDirectory.length;
1285+
const cwdSlice = line.slice(start, workingDirectoryEndPos);
1286+
1287+
tempLine += line.slice(0, start);
1288+
tempLine += ctx.stylize(cwdSlice, 'undefined');
1289+
tempLine += line.slice(workingDirectoryEndPos, end);
1290+
if (end === -1) {
1291+
tempLine += ctx.stylize(')', 'undefined');
1292+
}
1293+
} else {
1294+
tempLine += line;
1295+
}
1296+
return tempLine;
1297+
}
1298+
1299+
function safeGetCWD() {
1300+
try {
1301+
return process.cwd();
1302+
} catch {}
1303+
}
1304+
12621305
function formatError(err, constructor, tag, ctx, keys) {
12631306
const name = err.name != null ? String(err.name) : 'Error';
12641307
let stack = getStackString(err);
@@ -1284,22 +1327,20 @@ function formatError(err, constructor, tag, ctx, keys) {
12841327
const lines = getStackFrames(ctx, err, stack.slice(stackStart + 1));
12851328
if (ctx.colors) {
12861329
// Highlight userland code and node modules.
1287-
for (const line of lines) {
1330+
const workingDirectory = safeGetCWD();
1331+
for (let line of lines) {
12881332
const core = line.match(coreModuleRegExp);
12891333
if (core !== null && NativeModule.exists(core[1])) {
12901334
newStack += `\n${ctx.stylize(line, 'undefined')}`;
12911335
} else {
1292-
// This adds underscores to all node_modules to quickly identify them.
1293-
let nodeModule;
12941336
newStack += '\n';
1295-
let pos = 0;
1296-
while (nodeModule = nodeModulesRegExp.exec(line)) {
1297-
// '/node_modules/'.length === 14
1298-
newStack += line.slice(pos, nodeModule.index + 14);
1299-
newStack += ctx.stylize(nodeModule[1], 'module');
1300-
pos = nodeModule.index + nodeModule[0].length;
1337+
1338+
line = markNodeModules(ctx, line);
1339+
if (workingDirectory !== undefined) {
1340+
line = markCwd(ctx, line, workingDirectory);
13011341
}
1302-
newStack += pos === 0 ? line : line.slice(pos);
1342+
1343+
newStack += line;
13031344
}
13041345
}
13051346
} else {
@@ -2255,10 +2296,11 @@ function stripVTControlCharacters(str) {
22552296
}
22562297

22572298
module.exports = {
2299+
identicalSequenceRange,
22582300
inspect,
2301+
inspectDefaultOptions,
22592302
format,
22602303
formatWithOptions,
22612304
getStringWidth,
2262-
inspectDefaultOptions,
2263-
stripVTControlCharacters
2305+
stripVTControlCharacters,
22642306
};

test/parallel/test-util-inspect.js

+55-28
Original file line numberDiff line numberDiff line change
@@ -2730,35 +2730,62 @@ assert.strictEqual(
27302730
}
27312731

27322732
{
2733-
// Use a fake stack to verify the expected colored outcome.
2734-
const stack = [
2735-
'TypedError: Wonderful message!',
2736-
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
2737-
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
2738-
' at Fancy (node:vm:697:32)',
2739-
// This file is not an actual Node.js core file.
2740-
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2741-
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2742-
// This file is not an actual Node.js core file.
2743-
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2744-
' at require (node:internal/modules/cjs/helpers:14:16)',
2745-
' at /test/test-util-inspect.js:2239:9',
2746-
' at getActual (node:assert:592:5)',
2747-
];
2748-
const isNodeCoreFile = [
2749-
false, false, true, true, false, true, false, true, false, true,
2750-
];
2751-
const err = new TypeError('Wonderful message!');
2752-
err.stack = stack.join('\n');
2753-
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2754-
let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => {
2755-
return `node_modules/\u001b[4m${m}\u001b[24m`;
2756-
});
2757-
if (isNodeCoreFile[i]) {
2758-
actual = `\u001b[90m${actual}\u001b[39m`;
2733+
const originalCWD = process.cwd();
2734+
2735+
for (const os of ['windows', 'unix']) {
2736+
process.cwd = () => (os === 'windows' ?
2737+
'C:\\workspace\\node-test-binary-windows-js-suites\\node' :
2738+
'/home/user/repository/node');
2739+
2740+
// Use a fake stack to verify the expected colored outcome.
2741+
const stack = [
2742+
'TypedError: Wonderful message!',
2743+
' at A.<anonymous> (/t/node_modules/foo/node_modules/bar/baz.js:2:7)',
2744+
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
2745+
' at Fancy (node:vm:697:32)',
2746+
// This file is not an actual Node.js core file.
2747+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2748+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2749+
// This file is not an actual Node.js core file.
2750+
' at Module.require [as weird/name] (node:internal/aaa/loader:735:19)',
2751+
' at require (node:internal/modules/cjs/helpers:14:16)',
2752+
' at Array.forEach (<anonymous>)',
2753+
` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`,
2754+
` at Object.<anonymous> (${process.cwd()}/node_modules/hyper_module` +
2755+
'/folder/file.js:2753:10)',
2756+
' at /test/test-util-inspect.js:2239:9',
2757+
' at getActual (node:assert:592:5)',
2758+
];
2759+
const err = new TypeError('Wonderful message!');
2760+
err.stack = stack.join('\n');
2761+
if (os === 'windows') {
2762+
err.stack = stack.map((frame) => (frame.includes('node:') ?
2763+
frame :
2764+
frame.replaceAll('/', '\\'))
2765+
).join('\n');
27592766
}
2760-
assert.strictEqual(actual, line);
2761-
});
2767+
const escapedCWD = util.inspect(process.cwd()).slice(1, -1);
2768+
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2769+
let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => {
2770+
return `node_modules/\u001b[4m${m}\u001b[24m`;
2771+
}).replaceAll(new RegExp(`(\\(?${escapedCWD})`, 'gi'), (_, m) => {
2772+
return `\x1B[90m${m}\x1B[39m`;
2773+
});
2774+
if (expected.includes(process.cwd()) && expected.endsWith(')')) {
2775+
expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`;
2776+
}
2777+
if (line.includes('node:')) {
2778+
if (!line.includes('foo') && !line.includes('aaa')) {
2779+
expected = `\u001b[90m${expected}\u001b[39m`;
2780+
}
2781+
} else if (os === 'windows') {
2782+
expected = expected.replaceAll('/', '\\');
2783+
}
2784+
assert.strictEqual(line, expected);
2785+
});
2786+
}
2787+
2788+
process.cwd = originalCWD;
27622789
}
27632790

27642791
{

test/pseudo-tty/console_colors.out

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ string q
55
Error: test
66
at abc (../fixtures/node_modules/bar.js:4:4)
77
foobar
8-
at * (*console_colors.js:*:*)
8+
at *[90m(*console_colors.js:*:*[90m)*[39m
99
*[90m at * (node:internal*:*:*)*[39m
1010
*[90m at *[39m
1111
*[90m at *[39m
@@ -14,22 +14,22 @@ foobar
1414
*[90m at *[39m
1515

1616
Error: Should not ever get here.
17-
at * (*node_modules*[4m*node_modules*[24m*bar.js:*:*)
17+
at *[90m(*node_modules*[4m*node_modules*[24m*bar.js:*:*[90m)*[39m
1818
*[90m at *[39m
1919
*[90m at *[39m
2020
*[90m at *[39m
2121
*[90m at *[39m
2222
*[90m at *[39m
2323
*[90m at *[39m
24-
at * (*console_colors.js:*:*)
24+
at *[90m(*console_colors.js:*:*[90m)*[39m
2525
*[90m at *[39m
2626
*[90m at *[39m
2727

2828
Error
2929
at evalmachine.<anonymous>:*:*
3030
*[90m at Script.runInThisContext (node:vm:*:*)*[39m
3131
*[90m at Object.runInThisContext (node:vm:*:*)*[39m
32-
at * (*console_colors.js:*:*)
32+
at *[90m(*console_colors.js:*:*[90m)*[39m
3333
*[90m at *[39m
3434
*[90m at *[39m
3535
*[90m at *[39m

test/pseudo-tty/test-fatal-error.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ throw err;
33
^
44

55
TypeError: foobar
6-
at Object.<anonymous> (*test-fatal-error.js:*)
6+
at Object.<anonymous> [90m(*test-fatal-error.js:*[90m)[39m
77
*[90m at *(node:internal*loader:*:*)*[39m
88
*[90m at *(node:internal*loader:*:*)*[39m
99
*[90m at *(node:internal*loader:*:*)*[39m

0 commit comments

Comments
 (0)