From 079d96d035d2face15fcc1f740b842fccca82b82 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 11 Aug 2025 16:26:37 -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. --- lib/internal/util/inspect.js | 50 +++++++++++++++++++++++------- test/parallel/test-util-inspect.js | 4 +-- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 78318243e65eca..e3e6f5ef967593 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -244,7 +244,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 @@ -1412,16 +1411,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; - } - if (pos !== 0) { - line = tempLine + StringPrototypeSlice(line, pos); + 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 (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 b06f6814e4985a..5aafb4378c18e1 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -2837,7 +2837,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. @@ -2862,7 +2862,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`;