From a2d2003daa823950388c87b42c2aa5bc024d42e6 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Tue, 19 Aug 2025 06:22:25 -0400 Subject: [PATCH] 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 --- lib/internal/util/inspect.js | 48 +++++++++++++++++++++++------- test/parallel/test-util-inspect.js | 4 +-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 88f24c180e8..4b41236cc11 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -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; } diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 77834815ae8..079e541b8c4 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -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. (/test/node_modules/foo/node_modules/bar/baz.js:2:7)', + ' at A. (/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`;