Skip to content

Commit 046887d

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 e60801a commit 046887d

File tree

5 files changed

+151
-106
lines changed

5 files changed

+151
-106
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
@@ -1271,6 +1271,49 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) {
12711271
}
12721272
}
12731273

1274+
function markNodeModules(ctx, line) {
1275+
let tempLine = '';
1276+
let nodeModule;
1277+
let pos = 0;
1278+
while (nodeModule = nodeModulesRegExp.exec(line)) {
1279+
// '/node_modules/'.length === 14
1280+
tempLine += line.slice(pos, nodeModule.index + 14);
1281+
tempLine += ctx.stylize(nodeModule[1], 'module');
1282+
pos = nodeModule.index + nodeModule[0].length;
1283+
}
1284+
if (pos !== 0) {
1285+
line = tempLine + line.slice(pos);
1286+
}
1287+
return line;
1288+
}
1289+
1290+
function markCwd(ctx, line, workingDirectory) {
1291+
const cwdStartPos = line.indexOf(workingDirectory);
1292+
let tempLine = '';
1293+
if (cwdStartPos !== -1) {
1294+
const start = line[cwdStartPos - 1] === '(' ? cwdStartPos - 1 : cwdStartPos;
1295+
const end = start !== cwdStartPos && line.endsWith(')') ? -1 : line.length;
1296+
const workingDirectoryEndPos = cwdStartPos + workingDirectory.length + 1;
1297+
const cwdSlice = line.slice(start, workingDirectoryEndPos);
1298+
1299+
tempLine += line.slice(0, start);
1300+
tempLine += ctx.stylize(cwdSlice, 'undefined');
1301+
tempLine += line.slice(workingDirectoryEndPos, end);
1302+
if (end === -1) {
1303+
tempLine += ctx.stylize(')', 'undefined');
1304+
}
1305+
} else {
1306+
tempLine += line;
1307+
}
1308+
return tempLine;
1309+
}
1310+
1311+
function safeGetCWD() {
1312+
try {
1313+
return process.cwd();
1314+
} catch {}
1315+
}
1316+
12741317
function formatError(err, constructor, tag, ctx, keys) {
12751318
const name = err.name != null ? String(err.name) : 'Error';
12761319
let stack = getStackString(err);
@@ -1296,22 +1339,20 @@ function formatError(err, constructor, tag, ctx, keys) {
12961339
const lines = getStackFrames(ctx, err, stack.slice(stackStart + 1));
12971340
if (ctx.colors) {
12981341
// Highlight userland code and node modules.
1299-
for (const line of lines) {
1342+
const workingDirectory = safeGetCWD();
1343+
for (let line of lines) {
13001344
const core = line.match(coreModuleRegExp);
13011345
if (core !== null && NativeModule.exists(core[1])) {
13021346
newStack += `\n${ctx.stylize(line, 'undefined')}`;
13031347
} else {
1304-
// This adds underscores to all node_modules to quickly identify them.
1305-
let nodeModule;
13061348
newStack += '\n';
1307-
let pos = 0;
1308-
while (nodeModule = nodeModulesRegExp.exec(line)) {
1309-
// '/node_modules/'.length === 14
1310-
newStack += line.slice(pos, nodeModule.index + 14);
1311-
newStack += ctx.stylize(nodeModule[1], 'module');
1312-
pos = nodeModule.index + nodeModule[0].length;
1349+
1350+
line = markNodeModules(ctx, line);
1351+
if (workingDirectory !== undefined) {
1352+
line = markCwd(ctx, line, workingDirectory);
13131353
}
1314-
newStack += pos === 0 ? line : line.slice(pos);
1354+
1355+
newStack += line;
13151356
}
13161357
}
13171358
} else {
@@ -2267,10 +2308,11 @@ function stripVTControlCharacters(str) {
22672308
}
22682309

22692310
module.exports = {
2311+
identicalSequenceRange,
22702312
inspect,
2313+
inspectDefaultOptions,
22712314
format,
22722315
formatWithOptions,
22732316
getStringWidth,
2274-
inspectDefaultOptions,
2275-
stripVTControlCharacters
2317+
stripVTControlCharacters,
22762318
};

test/parallel/test-util-inspect.js

+55-28
Original file line numberDiff line numberDiff line change
@@ -2772,35 +2772,62 @@ assert.strictEqual(
27722772
}
27732773

27742774
{
2775-
// Use a fake stack to verify the expected colored outcome.
2776-
const stack = [
2777-
'TypedError: Wonderful message!',
2778-
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
2779-
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
2780-
' at Fancy (node:vm:697:32)',
2781-
// This file is not an actual Node.js core file.
2782-
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2783-
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2784-
// This file is not an actual Node.js core file.
2785-
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
2786-
' at require (node:internal/modules/cjs/helpers:14:16)',
2787-
' at /test/test-util-inspect.js:2239:9',
2788-
' at getActual (node:assert:592:5)',
2789-
];
2790-
const isNodeCoreFile = [
2791-
false, false, true, true, false, true, false, true, false, true,
2792-
];
2793-
const err = new TypeError('Wonderful message!');
2794-
err.stack = stack.join('\n');
2795-
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2796-
let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => {
2797-
return `node_modules/\u001b[4m${m}\u001b[24m`;
2798-
});
2799-
if (isNodeCoreFile[i]) {
2800-
actual = `\u001b[90m${actual}\u001b[39m`;
2775+
const originalCWD = process.cwd();
2776+
2777+
for (const os of ['windows', 'unix']) {
2778+
process.cwd = () => (os === 'windows' ?
2779+
'C:\\workspace\\node-test-binary-windows-js-suites\\node' :
2780+
'/home/user/repository/node');
2781+
2782+
// Use a fake stack to verify the expected colored outcome.
2783+
const stack = [
2784+
'TypedError: Wonderful message!',
2785+
' at A.<anonymous> (/t/node_modules/foo/node_modules/bar/baz.js:2:7)',
2786+
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
2787+
' at Fancy (node:vm:697:32)',
2788+
// This file is not an actual Node.js core file.
2789+
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
2790+
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
2791+
// This file is not an actual Node.js core file.
2792+
' at Module.require [as weird/name] (node:internal/aaa/loader:735:19)',
2793+
' at require (node:internal/modules/cjs/helpers:14:16)',
2794+
' at Array.forEach (<anonymous>)',
2795+
` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`,
2796+
` at Object.<anonymous> (${process.cwd()}/node_modules/hyper_module` +
2797+
'/folder/file.js:2753:10)',
2798+
' at /test/test-util-inspect.js:2239:9',
2799+
' at getActual (node:assert:592:5)',
2800+
];
2801+
const err = new TypeError('Wonderful message!');
2802+
err.stack = stack.join('\n');
2803+
if (os === 'windows') {
2804+
err.stack = stack.map((frame) => (frame.includes('node:') ?
2805+
frame :
2806+
frame.replaceAll('/', '\\'))
2807+
).join('\n');
28012808
}
2802-
assert.strictEqual(actual, line);
2803-
});
2809+
const escapedCWD = util.inspect(process.cwd()).slice(1, -1);
2810+
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
2811+
let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => {
2812+
return `node_modules/\u001b[4m${m}\u001b[24m`;
2813+
}).replaceAll(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, 'gi'), (_, m) => {
2814+
return `\x1B[90m${m}\x1B[39m`;
2815+
});
2816+
if (expected.includes(process.cwd()) && expected.endsWith(')')) {
2817+
expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`;
2818+
}
2819+
if (line.includes('node:')) {
2820+
if (!line.includes('foo') && !line.includes('aaa')) {
2821+
expected = `\u001b[90m${expected}\u001b[39m`;
2822+
}
2823+
} else if (os === 'windows') {
2824+
expected = expected.replaceAll('/', '\\');
2825+
}
2826+
assert.strictEqual(line, expected);
2827+
});
2828+
}
2829+
2830+
process.cwd = originalCWD;
28042831
}
28052832

28062833
{

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

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

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

55
TypeError: foobar
6-
at Object.<anonymous> (*test-fatal-error.js:*)
7-
*[90m at *(node:internal*loader:*:*)*[39m
8-
*[90m at *(node:internal*loader:*:*)*[39m
9-
*[90m at *(node:internal*loader:*:*)*[39m
10-
*[90m at *(node:internal*loader:*:*)*[39m
11-
*[90m at *[39m
12-
*[90m at *[39m {
13-
bla: *[33mtrue*[39m
6+
at Object.<anonymous> [90m(*test-fatal-error.js:*:*[90m)[39m
7+
[90m at *(node:internal*loader:*:*)[39m
8+
[90m at *(node:internal*loader:*:*)[39m
9+
[90m at *(node:internal*loader:*:*)[39m
10+
[90m at *(node:internal*loader:*:*)[39m
11+
[90m at *[39m
12+
[90m at *[39m {
13+
bla: [33mtrue[39m
1414
}
1515

1616
Node.js *

0 commit comments

Comments
 (0)