Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b366755
util: port node v26.3.0 styleText, getCallSites, and regexp highlighting
cirospaciari Jul 17, 2026
05fc2be
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
4fdad1f
util: address review — fix boxed-string deepEquals perf and getCallSi…
cirospaciari Jul 17, 2026
e4914bf
util: fix CI failures — lint, stale styleText assertions, styletext T…
cirospaciari Jul 17, 2026
521eb17
util: vm module namespaces get a null prototype, port v26 inspect fixes
cirospaciari Jul 17, 2026
b63bf90
util: address review — keep loose deepEquals on node's behavior, lazy…
cirospaciari Jul 17, 2026
6dd7fa7
util: fix lint, sync the vendored inspect test to v26's bracketed get…
cirospaciari Jul 17, 2026
8072986
util: give the styletext tests a deterministic color environment
cirospaciari Jul 17, 2026
7491ac7
util: keep hex coverage bun-side, gate the wrapper check on ObjectType
cirospaciari Jul 17, 2026
e6dfacd
fs: don't throw from WriteStream when the fd has no writable sink
cirospaciari Jul 17, 2026
7ff738e
Merge branch 'main' into claude/node-util-v26-compat
cirospaciari Jul 17, 2026
be5cfc8
util: address review findings from the merge round
robobun Jul 17, 2026
f626428
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
9151219
util: drop upstream's speculative groupType note from highlightRegExp
robobun Jul 17, 2026
c46c84b
Merge branch 'main' into claude/node-util-v26-compat
cirospaciari Jul 19, 2026
2a1ad47
deepEquals: run byte checks before routing typed-arrays-with-extras t…
robobun Jul 19, 2026
3afe0ab
Merge branch 'main' into claude/node-util-v26-compat
dylan-conway Jul 21, 2026
55ab9dc
Merge remote-tracking branch 'origin/main' into claude/node-util-v26-…
robobun Jul 22, 2026
7b1d0c7
Merge branch 'main' into claude/node-util-v26-compat
cirospaciari Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 244 additions & 2 deletions src/js/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,11 +870,15 @@
symbol: "green",
date: "magenta",
// "name": intentionally not styling
// TODO(BridgeAR): Highlight regular expressions properly.
regexp: "red",
regexp: highlightRegExp,
Comment thread
robobun marked this conversation as resolved.
module: "underline",
};

// Define the palette for RegExp group depth highlighting. Can be changed by users.
inspect.styles.regexp.colors = ["green", "red", "yellow", "cyan", "magenta"];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const highlightRegExpColors = inspect.styles.regexp.colors.slice();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function addQuotes(str, quotes) {
if (quotes === -1) {
return `"${str}"`;
Expand Down Expand Up @@ -952,11 +956,249 @@
return addQuotes(result, singleQuote);
}

function highlightRegExp(regexpString) {
let out = "";
let i = 0;
let depth = 0;
let inClass = false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// TODO(BridgeAR): Add group type tracking. That allows to increase the depth
// in case the same type is next to each other.
// let groupType = 0;

// Verify palette and update cache if user changed colors
const paletteNames = highlightRegExp.colors?.length > 0 ? highlightRegExp.colors : highlightRegExpColors;

const palette = [];
for (const name of paletteNames) {
const color = inspect.colors[name];
if (color) palette.push([`\u001b[${color[0]}m`, `\u001b[${color[1]}m`]);
}

Check warning on line 976 in src/js/internal/util/inspect.js

View check run for this annotation

Claude / Claude Code Review

highlightRegExp throws when user palette contains only invalid color names

If a user sets `util.inspect.styles.regexp.colors = ['not-a-color']`, the `?.length > 0` fallback is skipped but every entry is filtered out, leaving `palette` empty -- then `write()` computes `depth % 0 -> NaN`, `palette[NaN] ?? palette[0]` is `undefined`, and `color[0]` throws a TypeError out of `util.inspect`. Consider adding `if (palette.length === 0)` after the filter loop to fall back to `highlightRegExpColors` (the empty-array and `undefined` cases are already handled; only 'non-empty but
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.

function writeGroup(start, end, decreaseDepth = 1) {
let seq = "";
i++;
// Only checking for the closing delimiter is a fast heuristic for regular
// expressions without the u or v flag. A safer check would verify that the
// read characters are all alphanumeric.
while (i < regexpString.length && regexpString[i] !== end) {
seq += regexpString[i++];
}
if (i < regexpString.length) {
depth -= decreaseDepth;
write(start);
writeDepth(seq, 1, 1);
write(end);
depth += decreaseDepth;
} else {
// The group is not closed which would lead to mistakes in the output.
// This is a workaround to prevent output from being corrupted.
writeDepth(start, 1, -seq.length);
}
}

function write(str) {
const idx = depth % palette.length;
// Safeguard against bugs in the implementation.
const color = palette[idx] ?? palette[0];
out += color[0] + str + color[1];
return idx;
}

function writeDepth(str, incDepth, incI) {
depth += incDepth;
write(str);
depth -= incDepth;
i += incI;
}

// Opening '/'
write("/");
depth++;
i = 1;

// Parse pattern until next unescaped '/'
while (i < regexpString.length) {
const ch = regexpString[i];

if (inClass) {
if (ch === "\\") {
let seq = "\\";
i++;
if (i < regexpString.length) {
seq += regexpString[i++];
const next = seq[1];
if (next === "u" && regexpString[i] === "{") {
writeGroup(`${seq}{`, "}", 0);
continue;
} else if ((next === "p" || next === "P") && regexpString[i] === "{") {
writeGroup(`${seq}{`, "}", 0);
continue;
} else if (seq[1] === "x") {
seq += regexpString.slice(i, i + 2);
i += 2;
}
}
write(seq);
} else if (ch === "]") {
depth--;
write("]");
i++;
inClass = false;
} else if (
ch === "-" &&
regexpString[i - 1] !== "[" &&
i + 1 < regexpString.length &&
regexpString[i + 1] !== "]"
) {
writeDepth("-", 1, 1);
} else {
write(ch);
i++;
}
} else if (ch === "[") {
// Enter class
write("[");
depth++;
i++;
inClass = true;
} else if (ch === "(") {
write("(");
depth++;
i++;
if (i < regexpString.length && regexpString[i] === "?") {

Check failure on line 1069 in src/js/internal/util/inspect.js

View workflow job for this annotation

GitHub Actions / Lint JavaScript

bun(no-duplicate-conditional-property-access)

`regexpString.length` is read in the `if` condition and again in the body. Read it into a local first (e.g. `const { length } = regexpString`) so the property is only accessed once.
// Assertions and named groups
i++;
const a = i < regexpString.length ? regexpString[i] : "";
if (a === ":" || a === "=" || a === "!") {
writeDepth(`?${a}`, -1, 1);
} else {
const b = i + 1 < regexpString.length ? regexpString[i + 1] : "";
if (a === "<" && (b === "=" || b === "!")) {
writeDepth(`?<${b}`, -1, 2);
} else if (a === "<") {
// Named capture: write '?<name>' as a single colored token
i++; // consume '<'
const start = i;
while (i < regexpString.length && regexpString[i] !== ">") {
i++;
}
const name = regexpString.slice(start, i);
if (i < regexpString.length && regexpString[i] === ">") {
depth--;
write("?<");
writeDepth(name, 1, 0);
write(">");
depth++;
i++;
} else {
writeDepth("?<", -1, 0);
write(name);
}
} else {
write("?");
}
}
}
} else if (ch === ")") {
depth--;
write(")");
i++;
} else if (ch === "\\") {
let seq = "\\";
i++;
if (i < regexpString.length) {

Check failure on line 1110 in src/js/internal/util/inspect.js

View workflow job for this annotation

GitHub Actions / Lint JavaScript

bun(no-duplicate-conditional-property-access)

`regexpString.length` is read in the `if` condition and again in the body. Read it into a local first (e.g. `const { length } = regexpString`) so the property is only accessed once.
seq += regexpString[i++];
const next = seq[1];
if (i < regexpString.length) {

Check failure on line 1113 in src/js/internal/util/inspect.js

View workflow job for this annotation

GitHub Actions / Lint JavaScript

bun(no-duplicate-conditional-property-access)

`regexpString.length` is read in the `if` condition and again in the body. Read it into a local first (e.g. `const { length } = regexpString`) so the property is only accessed once.
if (next === "u" && regexpString[i] === "{") {
writeGroup(`${seq}{`, "}", 0);
continue;
} else if (next === "x") {
seq += regexpString.slice(i, i + 2);
i += 2;
} else if (next >= "0" && next <= "9") {
while (i < regexpString.length && regexpString[i] >= "0" && regexpString[i] <= "9") {
seq += regexpString[i++];
}
} else if (next === "k" && regexpString[i] === "<") {
writeGroup(`${seq}<`, ">");
continue;
} else if ((next === "p" || next === "P") && regexpString[i] === "{") {
// Unicode properties
writeGroup(`${seq}{`, "}", 0);
continue;
}
}
}
writeDepth(seq, 1, 0);
} else if (ch === "|" || ch === "+" || ch === "*" || ch === "?" || ch === "," || ch === "^" || ch === "$") {
writeDepth(ch, 3, 1);
} else if (ch === "{") {
i++;
let digits = "";
while (i < regexpString.length && regexpString[i] >= "0" && regexpString[i] <= "9") {
digits += regexpString[i++];
}
if (digits) {
write("{");
depth++;
writeDepth(digits, 1, 0);
}
if (i < regexpString.length) {
if (regexpString[i] === ",") {
if (!digits) {
write("{");
depth++;
}
write(",");
i++;
} else if (!digits) {
depth += 1;
write("{");
depth -= 1;
continue;
}
}
let digits2 = "";
while (i < regexpString.length && regexpString[i] >= "0" && regexpString[i] <= "9") {
digits2 += regexpString[i++];
}
if (digits2) {
writeDepth(digits2, 1, 0);
}
if (i < regexpString.length && regexpString[i] === "}") {
depth--;
write("}");
i++;
}
if (i < regexpString.length && regexpString[i] === "?") {
writeDepth("?", 3, 1);
}
} else if (ch === ".") {
writeDepth(ch, 2, 1);
} else if (ch === "/") {
// Stop at closing delimiter (unescaped, outside of character class)
break;
} else {
writeDepth(ch, 1, 1);
}
}

// Closing delimiter and flags
writeDepth("/", -1, 1);
if (i < regexpString.length) {
write(regexpString.slice(i));
}
return out;
}

function stylizeWithColor(str, styleType) {
const style = inspect.styles[styleType];
if (style !== undefined) {
const color = inspect.colors[style];
if (color !== undefined) return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`;
if (typeof style === "function") return style(str);
}
return str;
}
Expand Down
Loading
Loading