util: fix error's namespaced node_modules highlighting using inspect

When inspecting errors, node_modules are highlighted with an
underscore. So far namespaced modules only highlighted the namespace
but not the rest of the module name. This is fixed by matching the
full name.
As drive-by it improves the performance slightly by removing the
regular expression in favor of indexOf to identify the right spot.

PR-URL: https://github.com/nodejs/node/pull/59446
Reviewed-By: Jordan Harband <ljharb@gmail.com>
This commit is contained in:
Ruben Bridgewater authored and Richard Lau committed 2025-09-20 03:33:55 +00:00
1 parent f64d0def94
commit a2d2003daa
2 files changed
+40 -12

No files matched your search

+38 -10
View File
@@ -250,7 +250,6 @@ const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
const numberRegExp = /^(0|[1-9][0-9]*)$/;
const coreModuleRegExp = /^ {4}at (?:[^/\\(]+ \(|)node:(.+):\d+:\d+\)?$/;
const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
const classRegExp = /^(\s+[^(]*?)\s*{/;
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
@@ -1423,16 +1422,45 @@ function removeDuplicateErrorKeys(ctx, keys, err, stack) {
function markNodeModules(ctx, line) {
let tempLine = '';
let nodeModule;
let pos = 0;
while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {
// '/node_modules/'.length === 14
tempLine += StringPrototypeSlice(line, pos, nodeModule.index + 14);
tempLine += ctx.stylize(nodeModule[1], 'module');
pos = nodeModule.index + nodeModule[0].length;
let lastPos = 0;
let searchFrom = 0;
while (true) {
const nodeModulePosition = StringPrototypeIndexOf(line, 'node_modules', searchFrom);
if (nodeModulePosition === -1) {
break;
}
// Ensure it's a path segment: must have a path separator before and after
const separator = line[nodeModulePosition - 1];
const after = line[nodeModulePosition + 12]; // 'node_modules'.length === 12
if ((after !== '/' && after !== '\\') || (separator !== '/' && separator !== '\\')) {
// Not a proper segment; continue searching
searchFrom = nodeModulePosition + 1;
continue;
}
const moduleStart = nodeModulePosition + 13; // Include trailing separator
// Append up to and including '/node_modules/'
tempLine += StringPrototypeSlice(line, lastPos, moduleStart);
let moduleEnd = StringPrototypeIndexOf(line, separator, moduleStart);
if (line[moduleStart] === '@') {
// Namespaced modules have an extra slash: @namespace/package
moduleEnd = StringPrototypeIndexOf(line, separator, moduleEnd + 1);
}
const nodeModule = StringPrototypeSlice(line, moduleStart, moduleEnd);
tempLine += ctx.stylize(nodeModule, 'module');
lastPos = moduleEnd;
searchFrom = moduleEnd;
}
if (pos !== 0) {
line = tempLine + StringPrototypeSlice(line, pos);
if (lastPos !== 0) {
line = tempLine + StringPrototypeSlice(line, lastPos);
}
return line;
}
+2 -2
View File
@@ -2835,7 +2835,7 @@ assert.strictEqual(
// Use a fake stack to verify the expected colored outcome.
const stack = [
'Error: CWD is grayed out, even cwd that are percent encoded!',
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
' at A.<anonymous> (/test/node_modules/foo/node_modules/@namespace/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.
@@ -2860,7 +2860,7 @@ assert.strictEqual(
}
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) => {
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`;