diff --git a/scripts/runner.node.mjs b/scripts/runner.node.mjs index 67d86bd6fe50..5f3177cd6746 100755 --- a/scripts/runner.node.mjs +++ b/scripts/runner.node.mjs @@ -765,6 +765,18 @@ async function runTests() { NO_COLOR: "1", BUN_DEBUG_QUIET_LOGS: "1", }; + if (title.includes("test-util-styletext")) { + // These assert styleText's own color decisions against a TTY, so they need a + // color-capable environment they can then override per case. Drop the forced + // settings above, spawnBun's FORCE_COLOR, and CI (which resolves to "no color" + // in containers that don't identify the vendor), and pin TERM so every agent + // agrees. + env.FORCE_COLOR = undefined; + env.NO_COLOR = undefined; + env.NODE_DISABLE_COLORS = undefined; + env.CI = undefined; + env.TERM = "xterm-256color"; + } if (!isWindows && title.includes("/sequential/")) { // Sequential node tests share common.PORT (12346); a cluster worker // or child_process subprocess that outlives its test can keep that diff --git a/src/js/builtins/UtilInspect.ts b/src/js/builtins/UtilInspect.ts index 586f03b5783b..bc2641ba9e5b 100644 --- a/src/js/builtins/UtilInspect.ts +++ b/src/js/builtins/UtilInspect.ts @@ -5,6 +5,8 @@ export function getStylizeWithColor(inspect: Inspect) { return function stylizeWithColor(str: string, styleType: string) { const style = inspect.styles[styleType]; if (style !== undefined) { + // inspect.styles.regexp is a function (highlightRegExp), not a color name. + if (typeof style === "function") return style(str); const color = inspect.colors[style]; if (color !== undefined) return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; } diff --git a/src/js/internal/fs/streams.ts b/src/js/internal/fs/streams.ts index 140abc665b3e..44e8d040fd7b 100644 --- a/src/js/internal/fs/streams.ts +++ b/src/js/internal/fs/streams.ts @@ -470,9 +470,22 @@ function WriteStream(this: FSStream, path: string | null, options?: any): void { this.pos = start; } + // A writer cannot be opened for every fd a caller may hand us -- a read-only + // descriptor is the common case, and node's tty.WriteStream accepts one. Fall + // back to the general path there, which surfaces the failure at write time the + // way node does, rather than throwing from the constructor. + let fastWriter; + if (fastPath && fd != null) { + try { + fastWriter = Bun.file(fd).writer(); + } catch { + fastPath = false; + } + } + // Enable fast path if (fastPath) { - this[kWriteStreamFastPath] = fd ? Bun.file(fd).writer() : true; + this[kWriteStreamFastPath] = fd != null ? fastWriter : true; this._write = underscoreWriteFast; this._writev = undefined; this.write = writeFast as any; diff --git a/src/js/internal/util/inspect.js b/src/js/internal/util/inspect.js index d13654de0303..c8a020127376 100644 --- a/src/js/internal/util/inspect.js +++ b/src/js/internal/util/inspect.js @@ -342,6 +342,8 @@ function isURL(value) { return typeof value.href === "string" && value instanceof URL; } +const SymbolToPrimitive = Symbol.toPrimitive; + const builtInObjects = new SafeSet( ArrayPrototypeFilter( ObjectGetOwnPropertyNames(globalThis), @@ -844,11 +846,15 @@ inspect.styles = { symbol: "green", date: "magenta", // "name": intentionally not styling - // TODO(BridgeAR): Highlight regular expressions properly. - regexp: "red", + regexp: highlightRegExp, module: "underline", }; +// Define the palette for RegExp group depth highlighting. Can be changed by users. +inspect.styles.regexp.colors = ["green", "red", "yellow", "cyan", "magenta"]; + +const highlightRegExpColors = inspect.styles.regexp.colors.slice(); + function addQuotes(str, quotes) { if (quotes === -1) { return `"${str}"`; @@ -926,9 +932,240 @@ function strEscape(str) { return addQuotes(result, singleQuote); } +function highlightRegExp(regexpString) { + const length = regexpString.length; + let out = ""; + let i = 0; + let depth = 0; + let inClass = false; + + // 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`]); + } + + 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 < length && regexpString[i] !== end) { + seq += regexpString[i++]; + } + if (i < 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]; + } + + 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 < length) { + const ch = regexpString[i]; + + if (inClass) { + if (ch === "\\") { + let seq = "\\"; + i++; + if (i < 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 < 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 < length && regexpString[i] === "?") { + // Assertions and named groups + i++; + const a = i < length ? regexpString[i] : ""; + if (a === ":" || a === "=" || a === "!") { + writeDepth(`?${a}`, -1, 1); + } else { + const b = i + 1 < length ? regexpString[i + 1] : ""; + if (a === "<" && (b === "=" || b === "!")) { + writeDepth(`?<${b}`, -1, 2); + } else if (a === "<") { + // Named capture: write '?' as a single colored token + i++; // consume '<' + const start = i; + while (i < length && regexpString[i] !== ">") { + i++; + } + const name = regexpString.slice(start, i); + if (i < 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 < length) { + seq += regexpString[i++]; + const next = seq[1]; + if (i < length) { + 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 < 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 < length && regexpString[i] >= "0" && regexpString[i] <= "9") { + digits += regexpString[i++]; + } + if (digits) { + write("{"); + depth++; + writeDepth(digits, 1, 0); + } + if (i < length) { + if (regexpString[i] === ",") { + if (!digits) { + write("{"); + depth++; + } + write(","); + i++; + } else if (!digits) { + depth += 1; + write("{"); + depth -= 1; + continue; + } + } + let digits2 = ""; + while (i < length && regexpString[i] >= "0" && regexpString[i] <= "9") { + digits2 += regexpString[i++]; + } + if (digits2) { + writeDepth(digits2, 1, 0); + } + if (i < length && regexpString[i] === "}") { + depth--; + write("}"); + i++; + } + if (i < 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 < length) { + write(regexpString.slice(i)); + } + return out; +} + function stylizeWithColor(str, styleType) { const style = inspect.styles[styleType]; if (style !== undefined) { + // Checked first: a function style (regexp) would otherwise be stringified + // into a property key on every lookup. + if (typeof style === "function") return style(str); const color = inspect.colors[style]; if (color !== undefined) return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; } @@ -1240,6 +1477,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { let base = ""; let formatter = getEmptyFormatArray; let braces; + let extraKeys; let noIterator = true; let i = 0; const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE; @@ -1297,6 +1535,11 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { // bound function is required to reconstruct missing information. formatter = FunctionPrototypeBind(formatTypedArray, null, bound, size); extrasType = kArrayExtrasType; + + if (ctx.showHidden) { + extraKeys = ["BYTES_PER_ELEMENT", "length", "byteLength", "byteOffset", "buffer"]; + typedArray = true; + } } else if (isMapIterator(value)) { keys = getKeys(value, ctx.showHidden); braces = getIteratorBraces("Map", tag); @@ -1354,14 +1597,14 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { if (typedArray === undefined) { formatter = formatArrayBuffer; } else if (keys.length === 0 && protoProps === undefined) { - return prefix + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`; + return prefix + `{ [byteLength]: ${formatNumber(ctx.stylize, value.byteLength, false)} }`; } braces[0] = `${prefix}{`; - ArrayPrototypeUnshift(keys, "byteLength"); + extraKeys = ["byteLength"]; } else if (isDataView(value)) { braces[0] = `${getPrefix(constructor, tag, "DataView")}{`; // .buffer goes last, it's not a primitive like the others. - ArrayPrototypeUnshift(keys, "byteLength", "byteOffset", "buffer"); + extraKeys = ["byteLength", "byteOffset", "buffer"]; } else if (isPromise(value)) { braces[0] = `${getPrefix(constructor, tag, "Promise")}{`; formatter = formatPromise; @@ -1412,6 +1655,18 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { // JSC stack is too powerful it must be stopped manually if (ctx.currentDepth > 1000) throw new RangeError(ERROR_STACK_OVERFLOW_MSG); output = formatter(ctx, value, recurseTimes); + if (extraKeys !== undefined) { + for (i = 0; i < extraKeys.length; i++) { + let formatted; + try { + formatted = formatExtraProperties(ctx, value, recurseTimes, extraKeys[i], typedArray); + } catch { + const tempValue = { [extraKeys[i]]: value.buffer[extraKeys[i]] }; + formatted = formatExtraProperties(ctx, tempValue, recurseTimes, extraKeys[i], typedArray); + } + ArrayPrototypePush.$call(output, formatted); + } + } for (i = 0; i < keys.length; i++) { ArrayPrototypePush.$call(output, formatProperty(ctx, value, recurseTimes, keys[i], extrasType)); } @@ -2120,7 +2375,7 @@ function formatArray(ctx, value, recurseTimes) { return output; } -function formatTypedArray(value, length, ctx, ignored, recurseTimes) { +function formatTypedArray(value, length, ctx, _ignored, _recurseTimes) { if (Buffer.isBuffer(value)) { BufferModule ??= require("node:buffer"); const INSPECT_MAX_BYTES = $requireMap.$get("buffer")?.exports.INSPECT_MAX_BYTES ?? BufferModule.INSPECT_MAX_BYTES; @@ -2136,16 +2391,6 @@ function formatTypedArray(value, length, ctx, ignored, recurseTimes) { if (remaining > 0) { output[maxLength] = remainingText(remaining); } - if (ctx.showHidden) { - // .buffer goes last, it's not a primitive like the others. - // All besides `BYTES_PER_ELEMENT` are actually getters. - ctx.indentationLvl += 2; - for (const key of ["BYTES_PER_ELEMENT", "length", "byteLength", "byteOffset", "buffer"]) { - const str = formatValue(ctx, value[key], recurseTimes, true); - ArrayPrototypePush.$call(output, `[${key}]: ${str}`); - } - ctx.indentationLvl -= 2; - } return output; } @@ -2285,6 +2530,16 @@ function formatPromise(ctx, value, recurseTimes) { return output; } +function formatExtraProperties(ctx, value, recurseTimes, key, typedArray) { + ctx.indentationLvl += 2; + const str = formatValue(ctx, value[key], recurseTimes, typedArray); + ctx.indentationLvl -= 2; + + // These entries are mainly getters. Should they be formatted like getters? + const name = ctx.stylize(`[${key}]`, "string"); + return `${name}: ${str}`; +} + function formatProperty(ctx, value, recurseTimes, key, type, desc, original = value) { let name, str; let extra = " "; @@ -2433,6 +2688,10 @@ function reduceToSingleString(ctx, output, base, braces, extrasType, recurseTime return `${braces[0]}${ln}${ArrayPrototypeJoin(output, `,\n${indentation} `)} ${braces[1]}`; } +function returnFalse() { + return false; +} + function hasBuiltInToString(value) { // Prevent triggering proxy traps. const getFullProxy = false; @@ -2441,30 +2700,34 @@ function hasBuiltInToString(value) { if (proxyTarget === null) { return true; } - value = proxyTarget; + return hasBuiltInToString(proxyTarget); } - // Check if value has a custom Symbol.toPrimitive transformation. - if (typeof value[Symbol.toPrimitive] === "function") { - return false; - } + let hasOwnToString = ObjectPrototypeHasOwnProperty; + let hasOwnToPrimitive = ObjectPrototypeHasOwnProperty; - // Count objects that have no `toString` function as built-in. + // Count objects without `toString` and `Symbol.toPrimitive` function as built-in. if (typeof value.toString !== "function") { - return true; - } - - // The object has a own `toString` property. Thus it's not not a built-in one. - if (ObjectPrototypeHasOwnProperty(value, "toString")) { + if (typeof value[SymbolToPrimitive] !== "function") { + return true; + } else if (ObjectPrototypeHasOwnProperty(value, SymbolToPrimitive)) { + return false; + } + hasOwnToString = returnFalse; + } else if (ObjectPrototypeHasOwnProperty(value, "toString")) { + return false; + } else if (typeof value[SymbolToPrimitive] !== "function") { + hasOwnToPrimitive = returnFalse; + } else if (ObjectPrototypeHasOwnProperty(value, SymbolToPrimitive)) { return false; } - // Find the object that has the `toString` property as own property in the - // prototype chain. + // Find the object that has the `toString` property or `Symbol.toPrimitive` property + // as own property in the prototype chain. let pointer = value; do { pointer = ObjectGetPrototypeOf(pointer); - } while (!ObjectPrototypeHasOwnProperty(pointer, "toString")); + } while (!hasOwnToString(pointer, "toString") && !hasOwnToPrimitive(pointer, SymbolToPrimitive)); // Check closer if the object is a built-in. const descriptor = ObjectGetOwnPropertyDescriptor(pointer, "constructor"); diff --git a/src/js/node/util.ts b/src/js/node/util.ts index d70fcb2c56c0..b00362b6e81f 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -3,7 +3,13 @@ const types = require("node:util/types"); /** @type {import('node-inspect-extracted')} */ const utl = require("internal/util/inspect"); const { promisify } = require("internal/promisify"); -const { validateString, validateOneOf, validateBoolean } = require("internal/validators"); +const { + validateString, + validateOneOf, + validateBoolean, + validateObject, + validateInteger, +} = require("internal/validators"); const { resistStopPropagation } = require("internal/shared"); const { MIMEType, MIMEParams } = require("internal/util/mime"); const { deprecate } = require("internal/util/deprecate"); @@ -13,6 +19,9 @@ const parseEnv = $newRustFunction("node_util_binding.rs", "parseEnv", 1); const NumberIsSafeInteger = Number.isSafeInteger; const ObjectKeys = Object.keys; +const ObjectGetOwnPropertyNames = Object.getOwnPropertyNames; +const { uncurryThis, SafeMap } = require("internal/primordials"); +const RegExpPrototypeExec = uncurryThis(RegExp.prototype.exec); var cjs_exports; @@ -24,7 +33,9 @@ function isFunction(value) { } const deepEquals = Bun.deepEquals; -const isDeepStrictEqual = (a, b) => deepEquals(a, b, true); +function isDeepStrictEqual(a, b, skipPrototype) { + return deepEquals(a, b, true, skipPrototype); +} const parseArgs = $newRustFunction("parse_args.rs", "parseArgs", 1); @@ -214,30 +225,181 @@ var toUSVString = input => { return (input + "").toWellFormed(); }; -function styleText(format, text) { - validateString(text, "text"); +const kEscape = "\u001b["; +const kEscapeEnd = "m"; +const kDimCode = 2; +const kBoldCode = 1; +const kHexCloseSeq = kEscape + "39" + kEscapeEnd; +const kHexStyleCacheMax = 256; + +// Matches #RGB or #RRGGBB +const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; + +let styleCache; +let hexStyleCache; +let lazyStreamUtils; +let lazyUtilColors; - if ($isJSArray(format)) { - let left = ""; - let right = ""; - for (const key of format) { - const formatCodes = inspect.colors[key]; - if (formatCodes == null) { - validateOneOf(key, "format", ObjectKeys(inspect.colors)); +function getHexStyleCache() { + hexStyleCache ??= new SafeMap(); + return hexStyleCache; +} + +function getStyleCache() { + if (styleCache === undefined) { + styleCache = { __proto__: null }; + const colors = inspect.colors; + for (const key of ObjectGetOwnPropertyNames(colors)) { + const codes = colors[key]; + if (codes) { + const openNum = codes[0]; + const closeNum = codes[1]; + styleCache[key] = { + __proto__: null, + openSeq: kEscape + openNum + kEscapeEnd, + closeSeq: kEscape + closeNum + kEscapeEnd, + keepClose: openNum === kDimCode || openNum === kBoldCode, + }; } - left += `\u001b[${formatCodes[0]}m`; - right = `\u001b[${formatCodes[1]}m${right}`; } + } + return styleCache; +} - return `${left}${text}${right}`; +function hexToRgb(hex) { + let hexStr; + if (hex.length === 4) { + hexStr = hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; + } else if (hex.length === 7) { + hexStr = hex.slice(1); + } else { + throw $ERR_OUT_OF_RANGE("hex", "#RGB or #RRGGBB", hex); } - let formatCodes = inspect.colors[format]; + return Buffer.from(hexStr, "hex"); +} - if (formatCodes == null) { - validateOneOf(format, "format", ObjectKeys(inspect.colors)); +function getHexStyle(hex) { + const cache = getHexStyleCache(); + const cached = cache.get(hex); + if (cached !== undefined) return cached; + const rgb = hexToRgb(hex); + const style = { + __proto__: null, + openSeq: kEscape + `38;2;${rgb[0]};${rgb[1]};${rgb[2]}` + kEscapeEnd, + closeSeq: kHexCloseSeq, + }; + if (cache.size >= kHexStyleCacheMax) { + cache.delete(cache.keys().next().value); } - return `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`; + cache.set(hex, style); + return style; +} + +function replaceCloseCode(str, closeSeq, openSeq, keepClose) { + const closeLen = closeSeq.length; + let index = str.indexOf(closeSeq); + if (index === -1) return str; + + let result = ""; + let lastIndex = 0; + const replacement = keepClose ? closeSeq + openSeq : openSeq; + + do { + const afterClose = index + closeLen; + if (afterClose < str.length) { + result += str.slice(lastIndex, index) + replacement; + lastIndex = afterClose; + } else { + break; + } + index = str.indexOf(closeSeq, lastIndex); + } while (index !== -1); + + return result + str.slice(lastIndex); +} + +function styleText(format, text, options) { + const validateStream = options?.validateStream ?? true; + const cache = getStyleCache(); + + // Fast path: single format string with validateStream=false + if (!validateStream && typeof format === "string" && typeof text === "string") { + if (format === "none") return text; + const style = cache[format]; + if (style !== undefined) { + const processed = replaceCloseCode(text, style.closeSeq, style.openSeq, style.keepClose); + return style.openSeq + processed + style.closeSeq; + } + + if (format[0] === "#") { + let hexStyle = getHexStyleCache().get(format); + if (hexStyle === undefined && RegExpPrototypeExec(hexColorRegExp, format) !== null) { + hexStyle = getHexStyle(format); + } + if (hexStyle !== undefined) { + const processed = replaceCloseCode(text, hexStyle.closeSeq, hexStyle.openSeq, false); + return hexStyle.openSeq + processed + hexStyle.closeSeq; + } + } + } + + validateString(text, "text"); + if (options !== undefined) { + validateObject(options, "options"); + } + validateBoolean(validateStream, "options.validateStream"); + + let skipColorize; + if (validateStream) { + const stream = options?.stream ?? process.stdout; + lazyStreamUtils ??= require("internal/streams/utils"); + const { isNodeStream, isReadableStream, isWritableStream } = lazyStreamUtils; + if (!isReadableStream(stream) && !isWritableStream(stream) && !isNodeStream(stream)) { + throw $ERR_INVALID_ARG_TYPE("stream", ["ReadableStream", "WritableStream", "Stream"], stream); + } + lazyUtilColors ??= require("internal/util/colors"); + skipColorize = !lazyUtilColors.shouldColorize(stream); + } + + const formatArray = $isJSArray(format) ? format : [format]; + + let openCodes = ""; + let closeCodes = ""; + let processedText = text; + + for (const key of formatArray) { + if (key === "none") continue; + + if (typeof key === "string" && key[0] === "#") { + let hexStyle = getHexStyleCache().get(key); + if (hexStyle === undefined) { + if (RegExpPrototypeExec(hexColorRegExp, key) === null) { + throw $ERR_INVALID_ARG_VALUE("format", key, "must be a valid hex color (#RGB or #RRGGBB)"); + } + if (skipColorize) continue; + hexStyle = getHexStyle(key); + } else if (skipColorize) { + continue; + } + openCodes += hexStyle.openSeq; + closeCodes = hexStyle.closeSeq + closeCodes; + processedText = replaceCloseCode(processedText, hexStyle.closeSeq, hexStyle.openSeq, false); + continue; + } + + const style = cache[key]; + if (style === undefined) { + validateOneOf(key, "format", ObjectGetOwnPropertyNames(inspect.colors)); + } + openCodes += style.openSeq; + closeCodes = style.closeSeq + closeCodes; + processedText = replaceCloseCode(processedText, style.closeSeq, style.openSeq, style.keepClose); + } + + if (skipColorize) return text; + + return `${openCodes}${processedText}${closeCodes}`; } function getSystemErrorName(err: any) { @@ -246,6 +408,89 @@ function getSystemErrorName(err: any) { return internalErrorName(err); } +function prepareCallSites(_err, callSites) { + const result = []; + for (let i = 0; i < callSites.length; i++) { + const callSite = callSites[i]; + // CallSite#getColumnNumber() is 0-based here but 1-based in V8, and node + // exposes the column under both names. + const columnNumber = (callSite.getColumnNumber() ?? 0) + 1; + result.push({ + functionName: callSite.getFunctionName() ?? "", + scriptId: `${callSite.getScriptId()}`, + scriptName: callSite.getFileName() ?? "", + lineNumber: callSite.getLineNumber() ?? 0, + columnNumber, + column: columnNumber, + }); + } + return result; +} + +function validateSourceMapOption(options) { + const { sourceMap } = options; + if (sourceMap !== undefined) { + validateBoolean(sourceMap, "options.sourceMap"); + } +} + +function getCallSites(frameCount = 10, options) { + // If options is not provided check if frameCount is an object + if (options === undefined) { + if (typeof frameCount === "object" && frameCount !== null) { + // If frameCount is an object, it is the options object + options = frameCount; + validateObject(options, "options"); + validateSourceMapOption(options); + frameCount = 10; + } else { + options = {}; + } + } else { + validateObject(options, "options"); + validateSourceMapOption(options); + } + + // Using kDefaultMaxCallStackSizeToCapture as reference + validateInteger(frameCount, "frameCount", 1, 200); + + // Capture with our own prepareStackTrace so a user-installed + // Error.prepareStackTrace is never invoked, and so Error.stackTraceLimit + // does not influence the number of frames returned. + const target = {}; + const savedPrepareStackTrace = Error.prepareStackTrace; + const savedStackTraceLimit = Error.stackTraceLimit; + try { + Error.prepareStackTrace = prepareCallSites; + // User code may have made stackTraceLimit non-writable; best-effort so the + // capture still runs and prepareStackTrace is always restored. + try { + Error.stackTraceLimit = frameCount; + } catch {} + Error.captureStackTrace(target, getCallSites); + return target.stack; + } finally { + Error.prepareStackTrace = savedPrepareStackTrace; + try { + Error.stackTraceLimit = savedStackTraceLimit; + } catch {} + } +} + +let lazySignals; +function getSignals() { + lazySignals ??= require("node:os").constants.signals; + return lazySignals; +} + +function convertProcessSignalToExitCode(signalCode) { + const signals = getSignals(); + validateOneOf(signalCode, "signalCode", ObjectKeys(signals)); + + // POSIX standard: exit code for signal termination is 128 + signal number. + return 128 + signals[signalCode]; +} + let lazyAbortedRegistry: FinalizationRegistry<{ ref: WeakRef; unregisterToken: (...args: any[]) => void; @@ -320,14 +565,14 @@ cjs_exports = { // _exceptionWithHostPort, _extend, callbackify, + convertProcessSignalToExitCode, debug: debuglog, debuglog, deprecate, format, styleText, formatWithOptions, - // getCallSite, - // getCallSites, + getCallSites, // getSystemErrorMap, getSystemErrorName, // getSystemErrorMessage, diff --git a/src/jsc/bindings/BunObject.cpp b/src/jsc/bindings/BunObject.cpp index 02a1127e00df..696460b194a6 100644 --- a/src/jsc/bindings/BunObject.cpp +++ b/src/jsc/bindings/BunObject.cpp @@ -698,11 +698,17 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject, JSC::JSValue arg1 = callFrame->uncheckedArgument(0); JSC::JSValue arg2 = callFrame->uncheckedArgument(1); JSC::JSValue strict = callFrame->argument(2); + JSC::JSValue skipPrototype = callFrame->argument(3); Vector, 16> stack; MarkedArgumentBuffer gcBuffer; if (strict.isBoolean() && strict.asBoolean()) { + if (skipPrototype.isBoolean() && skipPrototype.asBoolean()) { + bool isEqual = Bun__deepEquals(globalObject, arg1, arg2, gcBuffer, stack, scope, true); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsBoolean(isEqual)); + } bool isEqual = Bun__deepEquals(globalObject, arg1, arg2, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/jsc/bindings/CallSite.cpp b/src/jsc/bindings/CallSite.cpp index ffaaa5323cbc..dbfee33bc406 100644 --- a/src/jsc/bindings/CallSite.cpp +++ b/src/jsc/bindings/CallSite.cpp @@ -58,6 +58,7 @@ void CallSite::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStac m_functionName.set(vm, this, stackFrame.functionName()); m_sourceURL.set(vm, this, stackFrame.sourceURL()); + m_sourceID = stackFrame.sourceID(); const auto* sourcePositions = stackFrame.getSourcePositions(); if (sourcePositions) { diff --git a/src/jsc/bindings/CallSite.h b/src/jsc/bindings/CallSite.h index 4d63a3b167b3..2920e9fe0bce 100644 --- a/src/jsc/bindings/CallSite.h +++ b/src/jsc/bindings/CallSite.h @@ -8,6 +8,7 @@ #include "ErrorStackTrace.h" #include +#include #include "BunClientData.h" #include "wtf/text/OrdinalNumber.h" @@ -37,6 +38,7 @@ class CallSite final : public JSC::JSNonFinalObject { JSC::WriteBarrier m_sourceURL; OrdinalNumber m_lineNumber; OrdinalNumber m_columnNumber; + intptr_t m_sourceID; unsigned int m_flags; public: @@ -76,6 +78,7 @@ class CallSite final : public JSC::JSNonFinalObject { JSC::JSValue sourceURL() const { return m_sourceURL.get(); } OrdinalNumber lineNumber() const { return m_lineNumber; } OrdinalNumber columnNumber() const { return m_columnNumber; } + intptr_t sourceID() const { return m_sourceID; } bool isEval() const { return m_flags & static_cast(Flags::IsEval); } bool isConstructor() const { return m_flags & static_cast(Flags::IsConstructor); } bool isStrict() const { return m_flags & static_cast(Flags::IsStrict); } @@ -93,6 +96,7 @@ class CallSite final : public JSC::JSNonFinalObject { : Base(vm, structure) , m_lineNumber(OrdinalNumber::beforeFirst()) , m_columnNumber(OrdinalNumber::beforeFirst()) + , m_sourceID(JSC::noSourceID) , m_flags(0) { } diff --git a/src/jsc/bindings/CallSitePrototype.cpp b/src/jsc/bindings/CallSitePrototype.cpp index 5a537ecccb62..ad31e65330b2 100644 --- a/src/jsc/bindings/CallSitePrototype.cpp +++ b/src/jsc/bindings/CallSitePrototype.cpp @@ -27,6 +27,7 @@ JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetMethodName); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetFileName); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetLineNumber); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetColumnNumber); +JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetScriptId); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetEvalOrigin); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncGetScriptNameOrSourceURL); JSC_DECLARE_HOST_FUNCTION(callSiteProtoFuncIsToplevel); @@ -69,6 +70,7 @@ static const HashTableValue CallSitePrototypeTableValues[] { "getFileName"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetFileName, 0 } }, { "getLineNumber"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetLineNumber, 0 } }, { "getColumnNumber"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetColumnNumber, 0 } }, + { "getScriptId"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetScriptId, 0 } }, { "getEvalOrigin"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetEvalOrigin, 0 } }, { "getScriptNameOrSourceURL"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncGetScriptNameOrSourceURL, 0 } }, { "isToplevel"_s, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Function, NoIntrinsic, { HashTableValue::NativeFunctionType, callSiteProtoFuncIsToplevel, 0 } }, @@ -145,6 +147,12 @@ JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetColumnNumber, (JSGlobalObject * glo return JSC::JSValue::encode(jsNumber(std::max(callSite->columnNumber().zeroBasedInt(), 0))); } +JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetScriptId, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + ENTER_PROTO_FUNC(); + return JSC::JSValue::encode(jsNumber(static_cast(callSite->sourceID()))); +} + // TODO: JSC_DEFINE_HOST_FUNCTION(callSiteProtoFuncGetEvalOrigin, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { diff --git a/src/jsc/bindings/NodeVMModule.cpp b/src/jsc/bindings/NodeVMModule.cpp index 23292142dc32..ebb0e31a5990 100644 --- a/src/jsc/bindings/NodeVMModule.cpp +++ b/src/jsc/bindings/NodeVMModule.cpp @@ -390,6 +390,10 @@ JSModuleNamespaceObject* NodeVMModule::namespaceObject(JSC::JSGlobalObject* glob object = amr->getModuleNamespace(globalObject); RETURN_IF_EXCEPTION(scope, {}); if (object) { + // The shared module namespace structure carries an __esModule accessor for + // CJS interop, but a Module Namespace Exotic Object is specified to have a + // null [[Prototype]], which is what vm modules must expose. + object->setPrototypeDirect(vm, jsNull()); namespaceObject(vm, object); } RETURN_IF_EXCEPTION(scope, {}); diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index e371e1ee400a..97fbbf7b30fd 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -52,6 +52,7 @@ #include "JavaScriptCore/JSFunction.h" #include "JavaScriptCore/ErrorInstanceInlines.h" #include "JavaScriptCore/BigIntObject.h" +#include "JavaScriptCore/SymbolObject.h" #include "JavaScriptCore/JSOrderedHashTableHelper.h" #include "JavaScriptCore/JSCallbackObject.h" @@ -652,10 +653,22 @@ JSValue getIndexWithoutAccessors(JSGlobalObject* globalObject, JSObject* obj, ui return JSValue(); } -template +template std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, MarkedArgumentBuffer& gcBuffer, Vector, 16>& stack, ThrowScope& scope, JSCell* _Nonnull c1, JSCell* _Nonnull c2); -template +// Typed array elements and boxed string characters are synthesized by +// getOwnPropertySlot instead of being stored in the structure, so a structure with +// no named properties means nothing is left to compare once the contents match. +// Checking this keeps those comparisons off the index-enumerating slow path. +// Indexed storage counts too: an out-of-range index (`new String("ab")[5] = "x"`) +// is an own property node compares but the contents check would miss. +static ALWAYS_INLINE bool hasExtraOwnProperties(JSC::Structure* structure) +{ + return structure->outOfLineSize() != 0 || structure->inlineSize() != 0 + || hasIndexedProperties(structure->indexingType()); +} + +template bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, MarkedArgumentBuffer& gcBuffer, Vector, 16>& stack, ThrowScope& scope, bool addToStack) { VM& vm = globalObject->vm(); @@ -734,10 +747,11 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, JSCell* c2 = v2.asCell(); ASSERT(c1); ASSERT(c2); - std::optional isSpecialEqual = specialObjectsDequal(globalObject, gcBuffer, stack, scope, c1, c2); + std::optional isSpecialEqual = specialObjectsDequal(globalObject, gcBuffer, stack, scope, c1, c2); RETURN_IF_EXCEPTION(scope, false); if (isSpecialEqual.has_value()) return WTF::move(*isSpecialEqual); - isSpecialEqual = specialObjectsDequal(globalObject, gcBuffer, stack, scope, c2, c1); + isSpecialEqual = specialObjectsDequal(globalObject, gcBuffer, stack, scope, c2, c1); + RETURN_IF_EXCEPTION(scope, false); if (isSpecialEqual.has_value()) return WTF::move(*isSpecialEqual); JSObject* o1 = v1.getObject(); JSObject* o2 = v2.getObject(); @@ -784,7 +798,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, } } - auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); + auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, false); if (!eql) return false; } @@ -839,7 +853,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, return false; } - auto eql = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); + auto eql = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, false); if (!eql) return false; } @@ -849,7 +863,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, return true; } - if constexpr (isStrict) { + if constexpr (isStrict && !skipPrototype) { if (!equal(JSObject::calculatedClassName(o1), JSObject::calculatedClassName(o2))) { return false; } @@ -887,7 +901,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, RETURN_IF_EXCEPTION(scope, false); if (same) return true; - auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); + auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, false); if (!eql) { result = false; @@ -905,7 +919,20 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, count++; JSValue left = o1->getDirect(entry.offset()); - JSValue right = o2->getDirect(vm, JSC::PropertyName(entry.key())); + JSValue right; + if constexpr (isStrict) { + // Only an enumerable property on o2 can match an enumerable one on o1. + // getDirect() alone would also find a non-enumerable property, which the + // reverse loop skips, so the two objects would compare equal. Loose + // comparison keeps matching either, as node does. + unsigned o2Attributes = 0; + PropertyOffset o2Offset = o2Structure->get(vm, JSC::PropertyName(entry.key()), o2Attributes); + if (o2Offset != invalidOffset && !(o2Attributes & PropertyAttribute::DontEnum)) { + right = o2->getDirect(o2Offset); + } + } else { + right = o2->getDirect(vm, JSC::PropertyName(entry.key())); + } if constexpr (!isStrict) { if (left.isUndefined() && right.isEmpty()) { @@ -923,7 +950,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, RETURN_IF_EXCEPTION(scope, false); if (same) return true; - auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); + auto eql = Bun__deepEquals(globalObject, left, right, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, false); if (!eql) { result = false; @@ -1011,7 +1038,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, return false; } - auto eql = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); + auto eql = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, false); if (!eql) return false; } @@ -1032,7 +1059,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, return true; } -template +template std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, MarkedArgumentBuffer& gcBuffer, Vector, 16>& stack, ThrowScope& scope, JSCell* _Nonnull c1, JSCell* _Nonnull c2) { VM& vm = globalObject->vm(); @@ -1069,7 +1096,7 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark JSValue key2; bool foundMatchingKey = false; while (iter2->next(globalObject, key2)) { - bool equal = Bun__deepEquals(globalObject, key1, key2, gcBuffer, stack, scope, false); + bool equal = Bun__deepEquals(globalObject, key1, key2, gcBuffer, stack, scope, false); RETURN_IF_EXCEPTION(scope, {}); if (equal) { foundMatchingKey = true; @@ -1112,7 +1139,7 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark JSValue key2; bool foundMatchingKey = false; while (iter2->nextKeyValue(globalObject, key2, value2)) { - bool keysEqual = Bun__deepEquals(globalObject, key1, key2, gcBuffer, stack, scope, false); + bool keysEqual = Bun__deepEquals(globalObject, key1, key2, gcBuffer, stack, scope, false); RETURN_IF_EXCEPTION(scope, {}); if (keysEqual) { foundMatchingKey = true; @@ -1127,7 +1154,7 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark // Compare both values below. } - bool valuesEqual = Bun__deepEquals(globalObject, value1, value2, gcBuffer, stack, scope, false); + bool valuesEqual = Bun__deepEquals(globalObject, value1, value2, gcBuffer, stack, scope, false); RETURN_IF_EXCEPTION(scope, {}); if (!valuesEqual) { return false; @@ -1264,7 +1291,7 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark RETURN_IF_EXCEPTION(scope, {}); auto rightCause = right->get(globalObject, cause); RETURN_IF_EXCEPTION(scope, {}); - bool causesEqual = Bun__deepEquals(globalObject, leftCause, rightCause, gcBuffer, stack, scope, true); + bool causesEqual = Bun__deepEquals(globalObject, leftCause, rightCause, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, {}); if (!causesEqual) { return false; @@ -1314,7 +1341,7 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark return false; } - bool propertiesEqual = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); + bool propertiesEqual = Bun__deepEquals(globalObject, prop1, prop2, gcBuffer, stack, scope, true); RETURN_IF_EXCEPTION(scope, {}); if (!propertiesEqual) { return false; @@ -1354,12 +1381,22 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark if (!isTypedArrayType(static_cast(c2Type)) || c1Type != c2Type) { return false; } + auto info = c1->classInfo(); auto info2 = c2->classInfo(); if (!info || !info2) { return false; } + // Strict mode also compares own non-index properties (e.g. symbols); loose + // ignores them. The byte checks below still run first so a mismatch stays + // O(bytes) and node's byte-level semantics (NaN payload bits) are preserved; + // only the "bytes equal" exits defer to the property walk when extras exist. + bool compareOwnProperties = false; + if constexpr (isStrict) { + compareOwnProperties = hasExtraOwnProperties(c1->structure()) || hasExtraOwnProperties(c2->structure()); + } + JSC::JSArrayBufferView* left = uncheckedDowncast(c1); JSC::JSArrayBufferView* right = uncheckedDowncast(c2); size_t byteLength = left->byteLength(); @@ -1368,8 +1405,10 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark return false; } - if (byteLength == 0) + if (byteLength == 0) { + if (compareOwnProperties) break; return true; + } if (right->isDetached() || left->isDetached()) [[unlikely]] { return false; @@ -1381,8 +1420,10 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark return false; } - if (vector == rightVector) [[unlikely]] + if (vector == rightVector) [[unlikely]] { + if (compareOwnProperties) break; return true; + } // For Float32Array and Float64Array, when not in strict mode, we need to // handle +0 and -0 as equal, and NaN as not equal to itself. @@ -1423,15 +1464,28 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark } } - return (memcmp(vector, rightVector, byteLength) == 0); + if (memcmp(vector, rightVector, byteLength) != 0) { + return false; + } + if (compareOwnProperties) break; + return true; } case StringObjectType: { if (c2Type != StringObjectType) { - return false; + // A String subclass instance is DerivedStringObjectType. Only skipPrototype + // mode, where the constructor is ignored, treats it as an equivalent boxed + // string; every other mode keeps the existing "different type" answer. + if constexpr (!(isStrict && skipPrototype)) { + return false; + } else if (c2Type != DerivedStringObjectType) { + return false; + } } - if (!equal(JSObject::calculatedClassName(c1->getObject()), JSObject::calculatedClassName(c2->getObject()))) { - return false; + if constexpr (!skipPrototype) { + if (!equal(JSObject::calculatedClassName(c1->getObject()), JSObject::calculatedClassName(c2->getObject()))) { + return false; + } } JSString* s1 = c1->toStringInline(globalObject); @@ -1441,7 +1495,17 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark bool stringsEqual = s1->equal(globalObject, s2); RETURN_IF_EXCEPTION(scope, {}); - return stringsEqual; + if (!stringsEqual) return false; + + if constexpr (isStrict) { + // Only strict mode compares extra own properties on boxed primitives. + // Guarded so a plain boxed string does not fall through to the property + // walk, which would enumerate every character index. + if (hasExtraOwnProperties(c1->structure()) || hasExtraOwnProperties(c2->structure())) { + break; + } + } + return true; } case JSFunctionType: { return false; @@ -1591,9 +1655,38 @@ std::optional specialObjectsDequal(JSC::JSGlobalObject* globalObject, Mark default: break; } + + // Symbol and BigInt wrapper objects are plain ObjectType in JSC, so they are not + // reachable from the switch above. Like Number and Boolean wrappers, they must be + // the same kind of wrapper and hold the same internal value. Everything else -- + // object literals, arrays -- has its own JSType and skips this. + if (c1Type == ObjectType) { + JSObject* obj1 = c1->getObject(); + JSObject* obj2 = c2->getObject(); + if (obj1 && obj2) { + const bool isSymbol1 = obj1->inherits(); + const bool isBigInt1 = obj1->inherits(); + if (isSymbol1 || isBigInt1) { + if (isSymbol1 != obj2->inherits() || isBigInt1 != obj2->inherits()) { + return false; + } + JSValue val1 = uncheckedDowncast(obj1)->internalValue(); + JSValue val2 = uncheckedDowncast(obj2)->internalValue(); + bool same = JSC::sameValue(globalObject, val1, val2); + RETURN_IF_EXCEPTION(scope, {}); + if (!same) return false; + // Fall through to check own properties + } + } + } return std::nullopt; } +// The other combinations are instantiated by their uses in this file. This one is +// only reached from `Bun.deepEquals(a, b, true, true)` in BunObject.cpp, which +// backs `util.isDeepStrictEqual(a, b, skipPrototype)`. +template bool Bun__deepEquals(JSC::JSGlobalObject*, JSValue, JSValue, MarkedArgumentBuffer&, Vector, 16>&, ThrowScope&, bool); + /** * @brief `Bun.deepMatch(a, b)` * diff --git a/src/jsc/bindings/headers-handwritten.h b/src/jsc/bindings/headers-handwritten.h index e421d79b93f7..d765bfb946ad 100644 --- a/src/jsc/bindings/headers-handwritten.h +++ b/src/jsc/bindings/headers-handwritten.h @@ -429,7 +429,7 @@ extern "C" void Bun__EventLoop__runCallback2(JSC::JSGlobalObject* global, JSC::E extern "C" void Bun__EventLoop__runCallback3(JSC::JSGlobalObject* global, JSC::EncodedJSValue callback, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue arg1, JSC::EncodedJSValue arg2, JSC::EncodedJSValue arg3); /// @note throws a JS exception and returns false if a stack overflow occurs -template +template bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSC::JSValue v1, JSC::JSValue v2, JSC::MarkedArgumentBuffer&, Vector, 16>& stack, JSC::ThrowScope& scope, bool addToStack); /** diff --git a/test/js/node/assert/deep-equal.test.ts b/test/js/node/assert/deep-equal.test.ts index 9a507e1ceb04..2948f9bc7655 100644 --- a/test/js/node/assert/deep-equal.test.ts +++ b/test/js/node/assert/deep-equal.test.ts @@ -23,6 +23,13 @@ interface Case { } const sym = Symbol("shared"); +const sharedArrayBuffer = new ArrayBuffer(4); + +function float64WithNaNPayload(bits: bigint) { + const arr = new Float64Array(1); + new BigUint64Array(arr.buffer)[0] = bits; + return arr; +} class WithPrototypeGetter { get a() { @@ -117,6 +124,34 @@ const cases: Case[] = [ loose: true, }, { name: "two boxed bigints", a: () => Object(1n), b: () => Object(1n), strict: true, loose: true }, + { + name: "two boxed symbols wrapping distinct symbols", + a: () => Object(Symbol("s")), + b: () => Object(Symbol("s")), + strict: false, + loose: false, + }, + { name: "two boxed unequal bigints", a: () => Object(1n), b: () => Object(2n), strict: false, loose: false }, + { + name: "a boxed string with an extra own property", + a: () => withExtraProperty(new String("test")), + b: () => new String("test"), + strict: false, + loose: false, + looseBug: "reports equal", + }, + { + name: "a boxed string with an out-of-range indexed own property", + a: () => { + const boxed = new String("ab"); + boxed[5] = "x"; + return boxed; + }, + b: () => new String("ab"), + strict: false, + loose: false, + looseBug: "reports equal", + }, // Undefined-valued and missing properties. Both modes compare own key counts. { @@ -247,6 +282,20 @@ const cases: Case[] = [ strict: true, loose: true, }, + { + name: "an enumerable symbol key and a non-enumerable one", + a: () => ({ [sym]: 1 }), + b: () => Object.defineProperty({}, sym, { value: 1, enumerable: false }), + strict: false, + loose: true, + }, + { + name: "typed arrays differing only in a symbol property", + a: () => Object.assign(new Uint8Array([1]), { [sym]: true }), + b: () => Object.assign(new Uint8Array([1]), { [sym]: false }), + strict: false, + loose: true, + }, { name: "distinct symbols with the same description", a: () => ({ [Symbol("s")]: 1 }), @@ -498,9 +547,32 @@ const cases: Case[] = [ b: () => new Uint8Array([1]), strict: false, loose: false, - strictBug: "reports equal", looseBug: "reports equal", }, + { + name: "an empty typed array with an extra own property", + a: () => withExtraProperty(new Uint8Array(0)), + b: () => new Uint8Array(0), + strict: false, + loose: false, + looseBug: "reports equal", + }, + { + name: "two views over the same ArrayBuffer, one with an extra own property", + a: () => withExtraProperty(new Uint8Array(sharedArrayBuffer)), + b: () => new Uint8Array(sharedArrayBuffer), + strict: false, + loose: false, + looseBug: "reports equal", + }, + { + // Strict mode compares the bytes; the property walk would see both as NaN and accept them. + name: "Float64Arrays with distinct NaN payloads and an extra own property", + a: () => withExtraProperty(float64WithNaNPayload(0x7ff8000000000001n)), + b: () => withExtraProperty(float64WithNaNPayload(0x7ff8000000000002n)), + strict: false, + loose: false, + }, // Arrays. { name: "[1] and { 0: 1 }", a: () => [1], b: () => ({ 0: 1 }), strict: false, loose: false }, @@ -617,6 +689,50 @@ describe("util.isDeepStrictEqual", () => { expect(util.isDeepStrictEqual(null, undefined)).toBe(false); expect(util.isDeepStrictEqual(Object.create(null), Object.create(null))).toBe(true); }); + + // The third argument was added in Node v26. + describe("skipPrototype", () => { + class Foo { + constructor(value) { + this.value = value; + } + } + class Bar { + constructor(value) { + this.value = value; + } + } + + test("ignores differing constructors when set", () => { + expect(util.isDeepStrictEqual(new Foo(42), new Bar(42))).toBe(false); + expect(util.isDeepStrictEqual(new Foo(42), new Bar(42), true)).toBe(true); + }); + + test("still compares values", () => { + expect(util.isDeepStrictEqual(new Foo(42), new Bar(99), true)).toBe(false); + }); + + test.each([ + ["object property", () => ({ inner: new Foo(1) }), () => ({ inner: new Bar(1) })], + ["array element", () => [new Foo(1)], () => [new Bar(1)]], + ["Map value", () => new Map([["k", new Foo(1)]]), () => new Map([["k", new Bar(1)]])], + ["Set element", () => new Set([new Foo(1)]), () => new Set([new Bar(1)])], + ["Error cause", () => new Error("e", { cause: new Foo(1) }), () => new Error("e", { cause: new Bar(1) })], + ])("propagates through %s", (_name, makeA, makeB) => { + expect(util.isDeepStrictEqual(makeA(), makeB())).toBe(false); + expect(util.isDeepStrictEqual(makeA(), makeB(), true)).toBe(true); + }); + + test("ignores the boxed-primitive subclass distinction", () => { + class S extends String {} + expect(util.isDeepStrictEqual(new String("a"), new S("a"))).toBe(false); + expect(util.isDeepStrictEqual(new String("a"), new S("a"), true)).toBe(true); + }); + + test("does not leak into assert.deepStrictEqual", () => { + expect(() => assert.deepStrictEqual(new Foo(42), new Bar(42))).toThrow(); + }); + }); }); describe("detached ArrayBuffer", () => { diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 8bb673e4bf9b..65d94b53c996 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -3302,7 +3302,9 @@ for (let withOverridenBufferWrite of [false, true]) { }); // Node.js throws here, but we can handle it just fine buf.fill(""); - expect(buf).toStrictEqual(Buffer.from([0, 0, 0, 0])); + // `length` is an own enumerable property on `buf` now, which node counts as a + // difference, so compare the bytes rather than the objects. + expect(Array.from(buf)).toEqual([0, 0, 0, 0]); }); it("allocUnsafeSlow().fill()", () => { diff --git a/test/js/node/test/parallel/test-util-convert-signal-to-exit-code.mjs b/test/js/node/test/parallel/test-util-convert-signal-to-exit-code.mjs new file mode 100644 index 000000000000..561c8f1e5ae9 --- /dev/null +++ b/test/js/node/test/parallel/test-util-convert-signal-to-exit-code.mjs @@ -0,0 +1,61 @@ +import { mustCall, mustNotCall, isWindows } from '../common/index.mjs'; +import assert from 'assert'; +import { convertProcessSignalToExitCode } from 'util'; +import { spawn } from 'child_process'; +import { constants } from 'os'; +const { signals } = constants; + +{ + + assert.strictEqual(convertProcessSignalToExitCode('SIGTERM'), 128 + signals.SIGTERM); + assert.strictEqual(convertProcessSignalToExitCode('SIGKILL'), 128 + signals.SIGKILL); + assert.strictEqual(convertProcessSignalToExitCode('SIGINT'), 128 + signals.SIGINT); + assert.strictEqual(convertProcessSignalToExitCode('SIGHUP'), 128 + signals.SIGHUP); + assert.strictEqual(convertProcessSignalToExitCode('SIGABRT'), 128 + signals.SIGABRT); +} + +{ + [ + 'INVALID', + '', + 'SIG', + undefined, + null, + 123, + true, + false, + {}, + [], + Symbol('test'), + () => {}, + ].forEach((value) => { + assert.throws( + () => convertProcessSignalToExitCode(value), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + } + ); + }); +} + +{ + const cat = spawn(isWindows ? 'cmd' : 'cat'); + cat.stdout.on('end', mustCall()); + cat.stderr.on('data', mustNotCall()); + cat.stderr.on('end', mustCall()); + + cat.on('exit', mustCall((code, signal) => { + assert.strictEqual(code, null); + assert.strictEqual(signal, 'SIGTERM'); + assert.strictEqual(cat.signalCode, 'SIGTERM'); + + const exitCode = convertProcessSignalToExitCode(signal); + assert.strictEqual(exitCode, 143); + })); + + assert.strictEqual(cat.signalCode, null); + assert.strictEqual(cat.killed, false); + cat[Symbol.dispose](); + assert.strictEqual(cat.killed, true); +} diff --git a/test/js/node/test/parallel/test-util-getcallsites-preparestacktrace.js b/test/js/node/test/parallel/test-util-getcallsites-preparestacktrace.js new file mode 100644 index 000000000000..ce4ac23e096a --- /dev/null +++ b/test/js/node/test/parallel/test-util-getcallsites-preparestacktrace.js @@ -0,0 +1,14 @@ +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const { getCallSites } = require('node:util'); + +// Asserts that util.getCallSites() does not invoke +// Error.prepareStackTrace. + +Error.prepareStackTrace = common.mustNotCall(); + +const sites = getCallSites(1); +assert.strictEqual(sites.length, 1); +assert.strictEqual(sites[0].scriptName, __filename); diff --git a/test/js/node/test/parallel/test-util-inspect-namespace.js b/test/js/node/test/parallel/test-util-inspect-namespace.js new file mode 100644 index 000000000000..244166f38198 --- /dev/null +++ b/test/js/node/test/parallel/test-util-inspect-namespace.js @@ -0,0 +1,20 @@ +// Flags: --experimental-vm-modules +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { SourceTextModule } = require('vm'); +const { inspect } = require('util'); + +(async () => { + const m = new SourceTextModule('export const a = 1; export var b = 2'); + await m.link(() => 0); + assert.strictEqual( + inspect(m.namespace), + '[Module: null prototype] { a: , b: undefined }'); + await m.evaluate(); + assert.strictEqual( + inspect(m.namespace), + '[Module: null prototype] { a: 1, b: 2 }' + ); +})().then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-util-inspect-regexp.js b/test/js/node/test/parallel/test-util-inspect-regexp.js new file mode 100644 index 000000000000..1875ce0fd15b --- /dev/null +++ b/test/js/node/test/parallel/test-util-inspect-regexp.js @@ -0,0 +1,150 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); + +util.inspect.defaultOptions.compact = 5; +util.inspect.defaultOptions.breakLength = Infinity; + +function expectColored([regexp, expected]) { + if (!common.hasIntl && !regexp) { + return; + } + const colored = util.inspect(regexp, { colors: true }); + const plain = util.inspect(regexp, { colors: false }); + try { + assert.strictEqual(util.stripVTControlCharacters(colored), plain); + assert.strictEqual(colored, expected, `${regexp} failed`); + } catch (error) { + console.log('\nInspecting regular expression', colored, '\n'); + throw error; + } +} + +function createRegExp(string, flags) { + if (common.hasIntl) { + return new RegExp(string, flags); + } +} + +/* eslint-disable node-core/no-unescaped-regexp-dot */ +/* eslint-disable @stylistic/js/max-len */ + +// Comprehensive set of regexes covering branches in highlightRegExp +const tests = [ + [/a/, '\x1B[32m/\x1B[39m\x1B[33ma\x1B[39m\x1B[32m/\x1B[39m'], + [/a|b/, '\x1B[32m/\x1B[39m\x1B[33ma\x1B[39m\x1B[35m|\x1B[39m\x1B[33mb\x1B[39m\x1B[32m/\x1B[39m'], + [/^$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?\d{4})-(?0[1-9]|1[0-2])-(?0[1-9]|[12]\d|3[01])$/u, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33myear\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mmon\x1B[39m\x1B[31m>\x1B[39m\x1B[36m0\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[32m|\x1B[39m\x1B[36m1\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m2\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mday\x1B[39m\x1B[31m>\x1B[39m\x1B[36m0\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[32m|\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[36m2\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m|\x1B[39m\x1B[36m3\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[36m1\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'], + [/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\s]).{12,}$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\d\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m\\w\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[36m.\x1B[39m\x1B[31m{\x1B[39m\x1B[36m12\x1B[39m\x1B[33m,\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(?(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3})$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mip\x1B[39m\x1B[31m>\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m2\x1B[39m\x1B[35m5\x1B[39m\x1B[36m[\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m5\x1B[39m\x1B[36m]\x1B[39m\x1B[31m|\x1B[39m\x1B[35m2\x1B[39m\x1B[36m[\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m4\x1B[39m\x1B[36m]\x1B[39m\x1B[35m\\d\x1B[39m\x1B[31m|\x1B[39m\x1B[35m1\x1B[39m\x1B[31m?\x1B[39m\x1B[35m\\d\x1B[39m\x1B[31m?\x1B[39m\x1B[35m\\d\x1B[39m\x1B[33m)\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m\\.\x1B[39m\x1B[36m(\x1B[39m\x1B[36m?:\x1B[39m\x1B[32m2\x1B[39m\x1B[32m5\x1B[39m\x1B[35m[\x1B[39m\x1B[32m0\x1B[39m\x1B[31m-\x1B[39m\x1B[32m5\x1B[39m\x1B[35m]\x1B[39m\x1B[33m|\x1B[39m\x1B[32m2\x1B[39m\x1B[35m[\x1B[39m\x1B[32m0\x1B[39m\x1B[31m-\x1B[39m\x1B[32m4\x1B[39m\x1B[35m]\x1B[39m\x1B[32m\\d\x1B[39m\x1B[33m|\x1B[39m\x1B[32m1\x1B[39m\x1B[33m?\x1B[39m\x1B[32m\\d\x1B[39m\x1B[33m?\x1B[39m\x1B[32m\\d\x1B[39m\x1B[36m)\x1B[39m\x1B[33m)\x1B[39m\x1B[33m{\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?[0-1]?\d|2[0-3]):(?[0-5]\d)(?::(?[0-5]\d))?$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mh\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m1\x1B[39m\x1B[33m]\x1B[39m\x1B[32m?\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m|\x1B[39m\x1B[36m2\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m3\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[33m:\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mm\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m5\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\d\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?<\x1B[39m\x1B[36ms\x1B[39m\x1B[33m>\x1B[39m\x1B[36m[\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m5\x1B[39m\x1B[36m]\x1B[39m\x1B[35m\\d\x1B[39m\x1B[33m)\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?:(?!cat).)*$/s, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?!\x1B[39m\x1B[35mc\x1B[39m\x1B[35ma\x1B[39m\x1B[35mt\x1B[39m\x1B[33m)\x1B[39m\x1B[35m.\x1B[39m\x1B[31m)\x1B[39m\x1B[35m*\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31ms\x1B[39m'], + [/^(?:\+?\d{1,3}[-.\s]?)?(?:\(?\d{2,4}\)?[-.\s]?)?\d{3,4}[-.\s]?\d{3,4}$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\+\x1B[39m\x1B[32m?\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m1\x1B[39m\x1B[36m,\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[33m[\x1B[39m\x1B[36m-\x1B[39m\x1B[36m.\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\(\x1B[39m\x1B[32m?\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[36m,\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[36m\\)\x1B[39m\x1B[32m?\x1B[39m\x1B[33m[\x1B[39m\x1B[36m-\x1B[39m\x1B[36m.\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m3\x1B[39m\x1B[33m,\x1B[39m\x1B[36m4\x1B[39m\x1B[31m}\x1B[39m\x1B[31m[\x1B[39m\x1B[33m-\x1B[39m\x1B[33m.\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m]\x1B[39m\x1B[35m?\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m3\x1B[39m\x1B[33m,\x1B[39m\x1B[36m4\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(0[xX])(?[0-9A-Fa-f]+)\b/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[36m0\x1B[39m\x1B[33m[\x1B[39m\x1B[36mx\x1B[39m\x1B[36mX\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mhex\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mF\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mf\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(?\d+)\.(?\d+)\b/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mnum\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\.\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mfrac\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m'], + [/\b([A-Za-z]+)\s+\1\b/i, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[35m+\x1B[39m\x1B[33m\\1\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m\x1B[31mi\x1B[39m'], + [/^([A-Za-z]\w*)(?:\s*,\s*\1)*$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\w\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\s\x1B[39m\x1B[32m*\x1B[39m\x1B[32m,\x1B[39m\x1B[36m\\s\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\1\x1B[39m\x1B[31m)\x1B[39m\x1B[35m*\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^\s*(?!<\/?script\b).*<\/?[A-Za-z][^>]*>\s*$/is, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[33m\\s\x1B[39m\x1B[35m*\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[36m<\x1B[39m\x1B[36m\\/\x1B[39m\x1B[32m?\x1B[39m\x1B[36ms\x1B[39m\x1B[36mc\x1B[39m\x1B[36mr\x1B[39m\x1B[36mi\x1B[39m\x1B[36mp\x1B[39m\x1B[36mt\x1B[39m\x1B[36m\\b\x1B[39m\x1B[31m)\x1B[39m\x1B[36m.\x1B[39m\x1B[35m*\x1B[39m\x1B[33m<\x1B[39m\x1B[33m\\/\x1B[39m\x1B[35m?\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mZ\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[31m]\x1B[39m\x1B[31m[\x1B[39m\x1B[33m^\x1B[39m\x1B[33m>\x1B[39m\x1B[31m]\x1B[39m\x1B[35m*\x1B[39m\x1B[33m>\x1B[39m\x1B[33m\\s\x1B[39m\x1B[35m*\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mis\x1B[39m'], + [/^(?:\r\n|[\n\r\u2028\u2029])+$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\r\x1B[39m\x1B[36m\\n\x1B[39m\x1B[32m|\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\n\x1B[39m\x1B[36m\\r\x1B[39m\x1B[36m\\u\x1B[39m\x1B[36m2\x1B[39m\x1B[36m0\x1B[39m\x1B[36m2\x1B[39m\x1B[36m8\x1B[39m\x1B[36m\\u\x1B[39m\x1B[36m2\x1B[39m\x1B[36m0\x1B[39m\x1B[36m2\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?[0-7]+)$|^(?[01]+)b$|^(?[0-9A-Fa-f]+)h$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33moct\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m7\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[35m|\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mbin\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[36m1\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33mb\x1B[39m\x1B[35m$\x1B[39m\x1B[35m|\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mhex\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mF\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mf\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33mh\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?!.*(.)\1\1)[A-Za-z0-9]{8,}$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m(\x1B[39m\x1B[32m.\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\1\x1B[39m\x1B[36m\\1\x1B[39m\x1B[31m)\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mZ\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m0\x1B[39m\x1B[36m-\x1B[39m\x1B[33m9\x1B[39m\x1B[31m]\x1B[39m\x1B[31m{\x1B[39m\x1B[36m8\x1B[39m\x1B[33m,\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(?:(?:19|20)\d{2})-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b(?![^<]*>)/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m1\x1B[39m\x1B[35m9\x1B[39m\x1B[31m|\x1B[39m\x1B[35m2\x1B[39m\x1B[35m0\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m0\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[32m|\x1B[39m\x1B[36m1\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m2\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m0\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[32m|\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[36m2\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m|\x1B[39m\x1B[36m3\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[36m1\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m<\x1B[39m\x1B[33m]\x1B[39m\x1B[32m*\x1B[39m\x1B[36m>\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?[A-Za-z0-9._%+-]+)@(?[A-Za-z0-9.-]+\.[A-Za-z]{2,})$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33muser\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36m.\x1B[39m\x1B[36m_\x1B[39m\x1B[36m%\x1B[39m\x1B[36m+\x1B[39m\x1B[36m-\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m@\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mhost\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36m.\x1B[39m\x1B[36m-\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[36m\\.\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[33m]\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[36m,\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^\$(?\d{1,3}(?:,\d{3})*(?:\.\d{2})?)$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[33m\\$\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mamt\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m1\x1B[39m\x1B[36m,\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[31m,\x1B[39m\x1B[35m\\d\x1B[39m\x1B[36m{\x1B[39m\x1B[32m3\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[32m*\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m\\.\x1B[39m\x1B[35m\\d\x1B[39m\x1B[36m{\x1B[39m\x1B[32m2\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(?\d{3})-(?\d{3})-(?\d{4})\b/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33marea\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mex\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mline\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?<([A-Za-z][A-Za-z0-9:-]*)\b[^>]*>)(?[\s\S]*?)<\/\2>$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mopen\x1B[39m\x1B[31m>\x1B[39m\x1B[36m<\x1B[39m\x1B[33m(\x1B[39m\x1B[36m[\x1B[39m\x1B[35mA\x1B[39m\x1B[32m-\x1B[39m\x1B[35mZ\x1B[39m\x1B[35ma\x1B[39m\x1B[32m-\x1B[39m\x1B[35mz\x1B[39m\x1B[36m]\x1B[39m\x1B[36m[\x1B[39m\x1B[35mA\x1B[39m\x1B[32m-\x1B[39m\x1B[35mZ\x1B[39m\x1B[35ma\x1B[39m\x1B[32m-\x1B[39m\x1B[35mz\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m9\x1B[39m\x1B[35m:\x1B[39m\x1B[35m-\x1B[39m\x1B[36m]\x1B[39m\x1B[31m*\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\b\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m>\x1B[39m\x1B[33m]\x1B[39m\x1B[32m*\x1B[39m\x1B[36m>\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33minner\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\s\x1B[39m\x1B[36m\\S\x1B[39m\x1B[33m]\x1B[39m\x1B[32m*\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[33m<\x1B[39m\x1B[33m\\/\x1B[39m\x1B[33m\\2\x1B[39m\x1B[33m>\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?=.*\b(cat|dog)\b)(?=.*\b(red|blue)\b).+$/i, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\b\x1B[39m\x1B[33m(\x1B[39m\x1B[35mc\x1B[39m\x1B[35ma\x1B[39m\x1B[35mt\x1B[39m\x1B[31m|\x1B[39m\x1B[35md\x1B[39m\x1B[35mo\x1B[39m\x1B[35mg\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\b\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\b\x1B[39m\x1B[33m(\x1B[39m\x1B[35mr\x1B[39m\x1B[35me\x1B[39m\x1B[35md\x1B[39m\x1B[31m|\x1B[39m\x1B[35mb\x1B[39m\x1B[35ml\x1B[39m\x1B[35mu\x1B[39m\x1B[35me\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\b\x1B[39m\x1B[31m)\x1B[39m\x1B[36m.\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mi\x1B[39m'], + [/^[-+]?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?(?:[eE][-+]?\d+)?$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m[\x1B[39m\x1B[33m-\x1B[39m\x1B[33m+\x1B[39m\x1B[31m]\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[32m|\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m1\x1B[39m\x1B[36m,\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[31m,\x1B[39m\x1B[35m\\d\x1B[39m\x1B[36m{\x1B[39m\x1B[32m3\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\.\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36me\x1B[39m\x1B[36mE\x1B[39m\x1B[33m]\x1B[39m\x1B[33m[\x1B[39m\x1B[36m-\x1B[39m\x1B[36m+\x1B[39m\x1B[33m]\x1B[39m\x1B[32m?\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s)?\d{2}\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{4}$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35mM\x1B[39m\x1B[35mo\x1B[39m\x1B[35mn\x1B[39m\x1B[31m|\x1B[39m\x1B[35mT\x1B[39m\x1B[35mu\x1B[39m\x1B[35me\x1B[39m\x1B[31m|\x1B[39m\x1B[35mW\x1B[39m\x1B[35me\x1B[39m\x1B[35md\x1B[39m\x1B[31m|\x1B[39m\x1B[35mT\x1B[39m\x1B[35mh\x1B[39m\x1B[35mu\x1B[39m\x1B[31m|\x1B[39m\x1B[35mF\x1B[39m\x1B[35mr\x1B[39m\x1B[35mi\x1B[39m\x1B[31m|\x1B[39m\x1B[35mS\x1B[39m\x1B[35ma\x1B[39m\x1B[35mt\x1B[39m\x1B[31m|\x1B[39m\x1B[35mS\x1B[39m\x1B[35mu\x1B[39m\x1B[35mn\x1B[39m\x1B[33m)\x1B[39m\x1B[32m,\x1B[39m\x1B[36m\\s\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[31m}\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36mJ\x1B[39m\x1B[36ma\x1B[39m\x1B[36mn\x1B[39m\x1B[32m|\x1B[39m\x1B[36mF\x1B[39m\x1B[36me\x1B[39m\x1B[36mb\x1B[39m\x1B[32m|\x1B[39m\x1B[36mM\x1B[39m\x1B[36ma\x1B[39m\x1B[36mr\x1B[39m\x1B[32m|\x1B[39m\x1B[36mA\x1B[39m\x1B[36mp\x1B[39m\x1B[36mr\x1B[39m\x1B[32m|\x1B[39m\x1B[36mM\x1B[39m\x1B[36ma\x1B[39m\x1B[36my\x1B[39m\x1B[32m|\x1B[39m\x1B[36mJ\x1B[39m\x1B[36mu\x1B[39m\x1B[36mn\x1B[39m\x1B[32m|\x1B[39m\x1B[36mJ\x1B[39m\x1B[36mu\x1B[39m\x1B[36ml\x1B[39m\x1B[32m|\x1B[39m\x1B[36mA\x1B[39m\x1B[36mu\x1B[39m\x1B[36mg\x1B[39m\x1B[32m|\x1B[39m\x1B[36mS\x1B[39m\x1B[36me\x1B[39m\x1B[36mp\x1B[39m\x1B[32m|\x1B[39m\x1B[36mO\x1B[39m\x1B[36mc\x1B[39m\x1B[36mt\x1B[39m\x1B[32m|\x1B[39m\x1B[36mN\x1B[39m\x1B[36mo\x1B[39m\x1B[36mv\x1B[39m\x1B[32m|\x1B[39m\x1B[36mD\x1B[39m\x1B[36me\x1B[39m\x1B[36mc\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m4\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/^(?:0|[1-9]\d*)(?:\.\d+)?(?:(?:e|E)[-+]?\d+)?$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m0\x1B[39m\x1B[32m|\x1B[39m\x1B[33m[\x1B[39m\x1B[36m1\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\.\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35me\x1B[39m\x1B[31m|\x1B[39m\x1B[35mE\x1B[39m\x1B[33m)\x1B[39m\x1B[33m[\x1B[39m\x1B[36m-\x1B[39m\x1B[36m+\x1B[39m\x1B[33m]\x1B[39m\x1B[32m?\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'], + [/\b(?\d+)\.(?\d+)\.(?\d+)(?:-(?
[0-9A-Za-z.-]+))?(?:\+(?[0-9A-Za-z.-]+))?\b/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mmaj\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\.\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mmin\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\.\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mpatch\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m-\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?<\x1B[39m\x1B[36mpre\x1B[39m\x1B[33m>\x1B[39m\x1B[36m[\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m9\x1B[39m\x1B[35mA\x1B[39m\x1B[32m-\x1B[39m\x1B[35mZ\x1B[39m\x1B[35ma\x1B[39m\x1B[32m-\x1B[39m\x1B[35mz\x1B[39m\x1B[35m.\x1B[39m\x1B[35m-\x1B[39m\x1B[36m]\x1B[39m\x1B[31m+\x1B[39m\x1B[33m)\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\+\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?<\x1B[39m\x1B[36mbuild\x1B[39m\x1B[33m>\x1B[39m\x1B[36m[\x1B[39m\x1B[35m0\x1B[39m\x1B[32m-\x1B[39m\x1B[35m9\x1B[39m\x1B[35mA\x1B[39m\x1B[32m-\x1B[39m\x1B[35mZ\x1B[39m\x1B[35ma\x1B[39m\x1B[32m-\x1B[39m\x1B[35mz\x1B[39m\x1B[35m.\x1B[39m\x1B[35m-\x1B[39m\x1B[36m]\x1B[39m\x1B[31m+\x1B[39m\x1B[33m)\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^#[0-9A-Fa-f]{3}(?:[0-9A-Fa-f]{3})?$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[33m#\x1B[39m\x1B[31m[\x1B[39m\x1B[33m0\x1B[39m\x1B[36m-\x1B[39m\x1B[33m9\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mF\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mf\x1B[39m\x1B[31m]\x1B[39m\x1B[31m{\x1B[39m\x1B[36m3\x1B[39m\x1B[31m}\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mF\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mf\x1B[39m\x1B[33m]\x1B[39m\x1B[33m{\x1B[39m\x1B[35m3\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/\b(?:https?):\/\/(?:(?!\/{2,})[^\s])+\b/, '\x1B[32m/\x1B[39m\x1B[33m\\b\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36mh\x1B[39m\x1B[36mt\x1B[39m\x1B[36mt\x1B[39m\x1B[36mp\x1B[39m\x1B[36ms\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[33m:\x1B[39m\x1B[33m\\/\x1B[39m\x1B[33m\\/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?!\x1B[39m\x1B[35m\\/\x1B[39m\x1B[36m{\x1B[39m\x1B[32m2\x1B[39m\x1B[35m,\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m+\x1B[39m\x1B[33m\\b\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?:(?!.*\b(foo).*\b\1\b).)*$/s, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?!\x1B[39m\x1B[32m.\x1B[39m\x1B[31m*\x1B[39m\x1B[35m\\b\x1B[39m\x1B[36m(\x1B[39m\x1B[32mf\x1B[39m\x1B[32mo\x1B[39m\x1B[32mo\x1B[39m\x1B[36m)\x1B[39m\x1B[32m.\x1B[39m\x1B[31m*\x1B[39m\x1B[35m\\b\x1B[39m\x1B[35m\\1\x1B[39m\x1B[35m\\b\x1B[39m\x1B[33m)\x1B[39m\x1B[35m.\x1B[39m\x1B[31m)\x1B[39m\x1B[35m*\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31ms\x1B[39m'],
+  [/^(?[-+])?(?:Infinity|NaN|\d+(?:\.\d+)?)(?=\s*$)/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33msign\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m-\x1B[39m\x1B[36m+\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m?\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36mI\x1B[39m\x1B[36mn\x1B[39m\x1B[36mf\x1B[39m\x1B[36mi\x1B[39m\x1B[36mn\x1B[39m\x1B[36mi\x1B[39m\x1B[36mt\x1B[39m\x1B[36my\x1B[39m\x1B[32m|\x1B[39m\x1B[36mN\x1B[39m\x1B[36ma\x1B[39m\x1B[36mN\x1B[39m\x1B[32m|\x1B[39m\x1B[36m\\d\x1B[39m\x1B[32m+\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m\\.\x1B[39m\x1B[35m\\d\x1B[39m\x1B[31m+\x1B[39m\x1B[33m)\x1B[39m\x1B[32m?\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[36m\\s\x1B[39m\x1B[32m*\x1B[39m\x1B[32m$\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?=.*\d)(?=.*[^\x61-\x7F])(?=.*[A-Za-z]).{8,}$/u, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\d\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m\\x61\x1B[39m\x1B[35m-\x1B[39m\x1B[36m\\x7F\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[36m.\x1B[39m\x1B[31m{\x1B[39m\x1B[36m8\x1B[39m\x1B[33m,\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [createRegExp('^\\p{Lu}\\p{Ll}+(?:\\s\\p{Lu}\\p{Ll}+)+$', 'u'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m\\p{\x1B[39m\x1B[33mLu\x1B[39m\x1B[31m}\x1B[39m\x1B[31m\\p{\x1B[39m\x1B[33mLl\x1B[39m\x1B[31m}\x1B[39m\x1B[35m+\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mLu\x1B[39m\x1B[33m}\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mLl\x1B[39m\x1B[33m}\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [createRegExp('^(?\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F)+$', 'u'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33memoji\x1B[39m\x1B[31m>\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mEmoji_Presentation\x1B[39m\x1B[33m}\x1B[39m\x1B[32m|\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mEmoji\x1B[39m\x1B[33m}\x1B[39m\x1B[36m\\u\x1B[39m\x1B[36mF\x1B[39m\x1B[36mE\x1B[39m\x1B[36m0\x1B[39m\x1B[36mF\x1B[39m\x1B[31m)\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [createRegExp('^[\\p{Script=Greek}\\p{Nd}]+$', 'u'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m[\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mScript=Greek\x1B[39m\x1B[33m}\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mNd\x1B[39m\x1B[33m}\x1B[39m\x1B[31m]\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [createRegExp('^(?=.*\\p{Extended_Pictographic}).{1,140}$', 'su'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mExtended_Pictographic\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[36m.\x1B[39m\x1B[31m{\x1B[39m\x1B[36m1\x1B[39m\x1B[33m,\x1B[39m\x1B[36m140\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31msu\x1B[39m'],
+  [/^(?:\[(?:[^\]\\]|\.)*]|"(?:[^"\\]|\\.)*")$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\[\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[36m[\x1B[39m\x1B[35m^\x1B[39m\x1B[35m\\]\x1B[39m\x1B[35m\\\\\x1B[39m\x1B[36m]\x1B[39m\x1B[31m|\x1B[39m\x1B[35m\\.\x1B[39m\x1B[33m)\x1B[39m\x1B[32m*\x1B[39m\x1B[36m]\x1B[39m\x1B[32m|\x1B[39m\x1B[36m"\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[36m[\x1B[39m\x1B[35m^\x1B[39m\x1B[35m"\x1B[39m\x1B[35m\\\\\x1B[39m\x1B[36m]\x1B[39m\x1B[31m|\x1B[39m\x1B[35m\\\\\x1B[39m\x1B[32m.\x1B[39m\x1B[33m)\x1B[39m\x1B[32m*\x1B[39m\x1B[36m"\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?["'])(?:\.|(?!\k)[\s\S])*\k$/, `\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mquote\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m"\x1B[39m\x1B[36m'\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\.\x1B[39m\x1B[32m|\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?!\x1B[39m\x1B[33m\\k<\x1B[39m\x1B[36mquote\x1B[39m\x1B[33m>\x1B[39m\x1B[33m)\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\s\x1B[39m\x1B[36m\\S\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m*\x1B[39m\x1B[32m\\k<\x1B[39m\x1B[31mquote\x1B[39m\x1B[32m>\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m`],
+  [/^(?:[A-Za-z_]\w*|\$[A-Za-z_]\w*)$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[36m_\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\w\x1B[39m\x1B[32m*\x1B[39m\x1B[32m|\x1B[39m\x1B[36m\\$\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mZ\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[36m_\x1B[39m\x1B[33m]\x1B[39m\x1B[36m\\w\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^((?:.\.\/)+)(?!\.)[A-Za-z0-9._/-]+$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[32m.\x1B[39m\x1B[35m\\.\x1B[39m\x1B[35m\\/\x1B[39m\x1B[33m)\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[36m\\.\x1B[39m\x1B[31m)\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mZ\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m0\x1B[39m\x1B[36m-\x1B[39m\x1B[33m9\x1B[39m\x1B[33m.\x1B[39m\x1B[33m_\x1B[39m\x1B[33m/\x1B[39m\x1B[33m-\x1B[39m\x1B[31m]\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?!.*\b(\w{3,})\b.*\b\1\b)[A-Za-z\s]+$/i, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\b\x1B[39m\x1B[33m(\x1B[39m\x1B[35m\\w\x1B[39m\x1B[36m{\x1B[39m\x1B[32m3\x1B[39m\x1B[35m,\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[36m\\b\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[36m\\b\x1B[39m\x1B[36m\\1\x1B[39m\x1B[36m\\b\x1B[39m\x1B[31m)\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mZ\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m]\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mi\x1B[39m'],
+  [/^(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36mA\x1B[39m\x1B[35m-\x1B[39m\x1B[36mF\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mf\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[33m]\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36m:\x1B[39m\x1B[31m)\x1B[39m\x1B[31m{\x1B[39m\x1B[36m5\x1B[39m\x1B[31m}\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mF\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mf\x1B[39m\x1B[33m0\x1B[39m\x1B[36m-\x1B[39m\x1B[33m9\x1B[39m\x1B[31m]\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^\[(?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)\]\s(?INFO|WARN|ERROR)\s(?.*)$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[33m\\[\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mts\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[36m-\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36m-\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36mT\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36m:\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36m:\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m2\x1B[39m\x1B[33m}\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m\\.\x1B[39m\x1B[35m\\d\x1B[39m\x1B[31m+\x1B[39m\x1B[33m)\x1B[39m\x1B[32m?\x1B[39m\x1B[36mZ\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\]\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mlvl\x1B[39m\x1B[31m>\x1B[39m\x1B[36mI\x1B[39m\x1B[36mN\x1B[39m\x1B[36mF\x1B[39m\x1B[36mO\x1B[39m\x1B[32m|\x1B[39m\x1B[36mW\x1B[39m\x1B[36mA\x1B[39m\x1B[36mR\x1B[39m\x1B[36mN\x1B[39m\x1B[32m|\x1B[39m\x1B[36mE\x1B[39m\x1B[36mR\x1B[39m\x1B[36mR\x1B[39m\x1B[36mO\x1B[39m\x1B[36mR\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mmsg\x1B[39m\x1B[31m>\x1B[39m\x1B[35m.\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?\w+)\s*(?\+=|-=|\*\*=|<<=)\s*(?[^;]+);$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mlhs\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\w\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[35m*\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mop\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\+\x1B[39m\x1B[36m=\x1B[39m\x1B[32m|\x1B[39m\x1B[36m-\x1B[39m\x1B[36m=\x1B[39m\x1B[32m|\x1B[39m\x1B[36m\\*\x1B[39m\x1B[36m\\*\x1B[39m\x1B[36m=\x1B[39m\x1B[32m|\x1B[39m\x1B[36m<\x1B[39m\x1B[36m<\x1B[39m\x1B[36m=\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[35m*\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mrhs\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m;\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m;\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+  [/^(?[a-z][a-z0-9+-.]*):(?\/\/[^?#\s]+(?:\?[^#\s]*)?(?:#[^\s]*)?|[^/\s][^\s]*)$/i, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mscheme\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[33m]\x1B[39m\x1B[33m[\x1B[39m\x1B[36ma\x1B[39m\x1B[35m-\x1B[39m\x1B[36mz\x1B[39m\x1B[36m0\x1B[39m\x1B[35m-\x1B[39m\x1B[36m9\x1B[39m\x1B[36m+\x1B[39m\x1B[35m-\x1B[39m\x1B[36m.\x1B[39m\x1B[33m]\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[33m:\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mrest\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\/\x1B[39m\x1B[36m\\/\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m?\x1B[39m\x1B[36m#\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m\\?\x1B[39m\x1B[36m[\x1B[39m\x1B[35m^\x1B[39m\x1B[35m#\x1B[39m\x1B[35m\\s\x1B[39m\x1B[36m]\x1B[39m\x1B[31m*\x1B[39m\x1B[33m)\x1B[39m\x1B[32m?\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?:\x1B[39m\x1B[35m#\x1B[39m\x1B[36m[\x1B[39m\x1B[35m^\x1B[39m\x1B[35m\\s\x1B[39m\x1B[36m]\x1B[39m\x1B[31m*\x1B[39m\x1B[33m)\x1B[39m\x1B[32m?\x1B[39m\x1B[32m|\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m/\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[33m[\x1B[39m\x1B[36m^\x1B[39m\x1B[36m\\s\x1B[39m\x1B[33m]\x1B[39m\x1B[32m*\x1B[39m\x1B[31m)\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mi\x1B[39m'],
+  [createRegExp('^(?:[\\p{Letter}\\p{Mark}\\p{Number}._-]+)@(?:[\\p{Letter}\\p{Number}\\p{Mark}.-]+)\\.[\\p{Letter}]{2,}$', 'u'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mLetter\x1B[39m\x1B[36m}\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mMark\x1B[39m\x1B[36m}\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mNumber\x1B[39m\x1B[36m}\x1B[39m\x1B[36m.\x1B[39m\x1B[36m_\x1B[39m\x1B[36m-\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m@\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mLetter\x1B[39m\x1B[36m}\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mNumber\x1B[39m\x1B[36m}\x1B[39m\x1B[36m\\p{\x1B[39m\x1B[35mMark\x1B[39m\x1B[36m}\x1B[39m\x1B[36m.\x1B[39m\x1B[36m-\x1B[39m\x1B[33m]\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\.\x1B[39m\x1B[31m[\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mLetter\x1B[39m\x1B[33m}\x1B[39m\x1B[31m]\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[33m,\x1B[39m\x1B[31m}\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [createRegExp('^[\\p{Alphabetic}&&\\p{ASCII}]+$', 'v'), '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[31m[\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mAlphabetic\x1B[39m\x1B[33m}\x1B[39m\x1B[33m&\x1B[39m\x1B[33m&\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[36mASCII\x1B[39m\x1B[33m}\x1B[39m\x1B[31m]\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m\x1B[31mv\x1B[39m'],
+  [/(a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?:a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?=a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?=\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?!a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?!\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?<=a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<=\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?a)/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mname\x1B[39m\x1B[31m>\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?a)\k/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mname\x1B[39m\x1B[31m>\x1B[39m\x1B[36ma\x1B[39m\x1B[31m)\x1B[39m\x1B[32m\\k<\x1B[39m\x1B[31mname\x1B[39m\x1B[32m>\x1B[39m\x1B[32m/\x1B[39m'],
+  [createRegExp('\\p{Letter}+', 'u'), '\x1B[32m/\x1B[39m\x1B[31m\\p{\x1B[39m\x1B[33mLetter\x1B[39m\x1B[31m}\x1B[39m\x1B[35m+\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [/[\u{1F600}-\u{1F601}]/u, '\x1B[32m/\x1B[39m\x1B[31m[\x1B[39m\x1B[33m\\u{\x1B[39m\x1B[36m1F600\x1B[39m\x1B[33m}\x1B[39m\x1B[36m-\x1B[39m\x1B[33m\\u{\x1B[39m\x1B[36m1F601\x1B[39m\x1B[33m}\x1B[39m\x1B[31m]\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [/\x61/, '\x1B[32m/\x1B[39m\x1B[33m\\x61\x1B[39m\x1B[32m/\x1B[39m'],
+  [/\u{1F600}/u, '\x1B[32m/\x1B[39m\x1B[31m\\u{\x1B[39m\x1B[33m1F600\x1B[39m\x1B[31m}\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [/[a-z-]/, '\x1B[32m/\x1B[39m\x1B[31m[\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m-\x1B[39m\x1B[31m]\x1B[39m\x1B[32m/\x1B[39m'],
+  [/[a-z-]/, '\x1B[32m/\x1B[39m\x1B[31m[\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m-\x1B[39m\x1B[31m]\x1B[39m\x1B[32m/\x1B[39m'],
+  [/.{2,3}?abc?/, '\x1B[32m/\x1B[39m\x1B[36m.\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[33m,\x1B[39m\x1B[36m3\x1B[39m\x1B[31m}\x1B[39m\x1B[35m?\x1B[39m\x1B[33ma\x1B[39m\x1B[33mb\x1B[39m\x1B[33mc\x1B[39m\x1B[35m?\x1B[39m\x1B[32m/\x1B[39m'],
+  [/a{2}/, '\x1B[32m/\x1B[39m\x1B[33ma\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[31m}\x1B[39m\x1B[32m/\x1B[39m'],
+  [/\d/, '\x1B[32m/\x1B[39m\x1B[33m\\d\x1B[39m\x1B[32m/\x1B[39m'],
+  [/[^a-z\d\u{1F600}-\u{1F601}]/u, '\x1B[32m/\x1B[39m\x1B[31m[\x1B[39m\x1B[33m^\x1B[39m\x1B[33ma\x1B[39m\x1B[36m-\x1B[39m\x1B[33mz\x1B[39m\x1B[33m\\d\x1B[39m\x1B[33m\\u{\x1B[39m\x1B[36m1F600\x1B[39m\x1B[33m}\x1B[39m\x1B[36m-\x1B[39m\x1B[33m\\u{\x1B[39m\x1B[36m1F601\x1B[39m\x1B[33m}\x1B[39m\x1B[31m]\x1B[39m\x1B[32m/\x1B[39m\x1B[31mu\x1B[39m'],
+  [/(?\d{4})-\d{2}|\d{2}-(?\d{4})/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33myear\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[33m-\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[31m}\x1B[39m\x1B[35m|\x1B[39m\x1B[33m\\d\x1B[39m\x1B[31m{\x1B[39m\x1B[36m2\x1B[39m\x1B[31m}\x1B[39m\x1B[33m-\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33myear\x1B[39m\x1B[31m>\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[35m4\x1B[39m\x1B[33m}\x1B[39m\x1B[31m)\x1B[39m\x1B[32m/\x1B[39m'],
+  [/(?<=Mr\.|Mrs.)\s[A-Z]\w+/, '\x1B[32m/\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<=\x1B[39m\x1B[36mM\x1B[39m\x1B[36mr\x1B[39m\x1B[36m\\.\x1B[39m\x1B[32m|\x1B[39m\x1B[36mM\x1B[39m\x1B[36mr\x1B[39m\x1B[36ms\x1B[39m\x1B[35m.\x1B[39m\x1B[31m)\x1B[39m\x1B[33m\\s\x1B[39m\x1B[31m[\x1B[39m\x1B[33mA\x1B[39m\x1B[36m-\x1B[39m\x1B[33mZ\x1B[39m\x1B[31m]\x1B[39m\x1B[33m\\w\x1B[39m\x1B[35m+\x1B[39m\x1B[32m/\x1B[39m'],
+  [/a/giu, '\x1B[32m/\x1B[39m\x1B[33ma\x1B[39m\x1B[32m/\x1B[39m\x1B[31mgiu\x1B[39m'],
+  [/\p{Let(?["'])(?:\.|(?!\k)[\s\S])*\k$/, `\x1B[32m/\x1B[39m\x1B[33m\\p{\x1B[39m\x1B[33mL\x1B[39m\x1B[33me\x1B[39m\x1B[33mt\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?<\x1B[39m\x1B[33mquote\x1B[39m\x1B[31m>\x1B[39m\x1B[33m[\x1B[39m\x1B[36m"\x1B[39m\x1B[36m'\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36m\\.\x1B[39m\x1B[32m|\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?!\x1B[39m\x1B[33m\\k<\x1B[39m\x1B[36mquote\x1B[39m\x1B[33m>\x1B[39m\x1B[33m)\x1B[39m\x1B[33m[\x1B[39m\x1B[36m\\s\x1B[39m\x1B[36m\\S\x1B[39m\x1B[33m]\x1B[39m\x1B[31m)\x1B[39m\x1B[35m*\x1B[39m\x1B[32m\\k<\x1B[39m\x1B[31mquote\x1B[39m\x1B[32m>\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m`],
+  [/^p{Lu}p{Ll}+(?:sp{Lu}p{Ll}+)+$/, '\x1B[32m/\x1B[39m\x1B[35m^\x1B[39m\x1B[33mp\x1B[39m\x1B[33m{\x1B[39m\x1B[33mL\x1B[39m\x1B[33mu\x1B[39m\x1B[33m}\x1B[39m\x1B[33mp\x1B[39m\x1B[33m{\x1B[39m\x1B[33mL\x1B[39m\x1B[33ml\x1B[39m\x1B[33m}\x1B[39m\x1B[35m+\x1B[39m\x1B[31m(\x1B[39m\x1B[31m?:\x1B[39m\x1B[36ms\x1B[39m\x1B[36mp\x1B[39m\x1B[36m{\x1B[39m\x1B[36mL\x1B[39m\x1B[36mu\x1B[39m\x1B[36m}\x1B[39m\x1B[36mp\x1B[39m\x1B[36m{\x1B[39m\x1B[36mL\x1B[39m\x1B[36ml\x1B[39m\x1B[36m}\x1B[39m\x1B[32m+\x1B[39m\x1B[31m)\x1B[39m\x1B[35m+\x1B[39m\x1B[35m$\x1B[39m\x1B[32m/\x1B[39m'],
+];
+
+for (const test of tests) {
+  expectColored(test);
+}
+
+// These test cases do not highlight the regular expression correctly.
+const brokenTests = [
+  [/\p{Let(?["'])(?:\.|\p{quote}[\s\S])*\k$/, `\x1B[32m/\x1B[39m\x1B[31m\\p{\x1B[39m\x1B[33mLet(?["'])(?:\\.|\\p{quote\x1B[39m\x1B[31m}\x1B[39m\x1B[31m[\x1B[39m\x1B[33m\\s\x1B[39m\x1B[33m\\S\x1B[39m\x1B[31m]\x1B[39m\x1B[32m)\x1B[39m\x1B[36m*\x1B[39m\x1B[32m\\k<\x1B[39m\x1B[32mquote\x1B[39m\x1B[32m>\x1B[39m\x1B[36m$\x1B[39m\x1B[32m/\x1B[39m`],
+];
+
+for (const test of brokenTests) {
+  expectColored(test);
+}
+
+{
+  const regexp = /(?\d{4})-\d{2}|\d{2}-(?\d{4})/;
+  const regular = util.inspect(regexp, { colors: true });
+
+  util.inspect.styles.regexp.colors = [];
+  const emptyColorArray = util.inspect(regexp, { colors: true });
+
+  assert.strictEqual(emptyColorArray, regular);
+
+  util.inspect.styles.regexp.colors = undefined;
+  const undefinedColors = util.inspect(regexp, { colors: true });
+
+  assert.strictEqual(undefinedColors, regular);
+
+  util.inspect.styles.regexp.colors = ['red', 'yellow', 'cyan'];
+  const customColors = util.inspect(regexp, { colors: true });
+
+  assert.strictEqual(
+    customColors,
+    '\x1B[31m/\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?<\x1B[39m\x1B[36myear\x1B[39m\x1B[33m>\x1B[39m\x1B[31m\\d\x1B[39m\x1B[36m{\x1B[39m\x1B[33m4\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[36m-\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[31m2\x1B[39m\x1B[33m}\x1B[39m\x1B[33m|\x1B[39m\x1B[36m\\d\x1B[39m\x1B[33m{\x1B[39m\x1B[31m2\x1B[39m\x1B[33m}\x1B[39m\x1B[36m-\x1B[39m\x1B[33m(\x1B[39m\x1B[33m?<\x1B[39m\x1B[36myear\x1B[39m\x1B[33m>\x1B[39m\x1B[31m\\d\x1B[39m\x1B[36m{\x1B[39m\x1B[33m4\x1B[39m\x1B[36m}\x1B[39m\x1B[33m)\x1B[39m\x1B[31m/\x1B[39m'
+  );
+
+  util.inspect.styles.regexp = 'red';
+  const redStyle = util.inspect(regexp, { colors: true });
+
+  assert.strictEqual(redStyle, '\x1B[31m/(?\\d{4})-\\d{2}|\\d{2}-(?\\d{4})/\x1B[39m');
+}
diff --git a/test/js/node/test/parallel/test-util-styletext.js b/test/js/node/test/parallel/test-util-styletext.js
index 6baa6a60eac8..3db01bec1c3a 100644
--- a/test/js/node/test/parallel/test-util-styletext.js
+++ b/test/js/node/test/parallel/test-util-styletext.js
@@ -1,7 +1,12 @@
 'use strict';
-require('../common');
-const assert = require('assert');
-const util = require('util');
+
+const common = require('../common');
+const assert = require('node:assert');
+const util = require('node:util');
+const { WriteStream } = require('node:tty');
+
+const styled = '\u001b[31mtest\u001b[39m';
+const noChange = 'test';
 
 [
   undefined,
@@ -17,12 +22,12 @@ const util = require('util');
     util.styleText(invalidOption, 'test');
   }, {
     code: 'ERR_INVALID_ARG_VALUE',
-  });
+  }, invalidOption);
   assert.throws(() => {
     util.styleText('red', invalidOption);
   }, {
     code: 'ERR_INVALID_ARG_TYPE'
-  });
+  }, invalidOption);
 });
 
 assert.throws(() => {
@@ -31,13 +36,188 @@ assert.throws(() => {
   code: 'ERR_INVALID_ARG_VALUE',
 });
 
-assert.strictEqual(util.styleText('red', 'test'), '\u001b[31mtest\u001b[39m');
+assert.strictEqual(
+  util.styleText('red', 'test', { validateStream: false }),
+  '\u001b[31mtest\u001b[39m',
+);
+
+assert.strictEqual(
+  util.styleText('gray', 'test', { validateStream: false }),
+  '\u001b[90mtest\u001b[39m',
+);
+
+assert.strictEqual(
+  util.styleText('grey', 'test', { validateStream: false }),
+  '\u001b[90mtest\u001b[39m',
+);
+
+assert.strictEqual(
+  util.styleText(['bold', 'red'], 'test', { validateStream: false }),
+  '\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m',
+);
+
+assert.strictEqual(
+  util.styleText('red',
+                 'A' + util.styleText('blue', 'B', { validateStream: false }) + 'C',
+                 { validateStream: false }),
+  '\u001b[31mA\u001b[34mB\u001b[31mC\u001b[39m'
+);
+
+assert.strictEqual(
+  util.styleText('red',
+                 'red' +
+    util.styleText('blue', 'blue', { validateStream: false }) +
+    'red' +
+    util.styleText('blue', 'blue', { validateStream: false }) +
+    'red',
+                 { validateStream: false }
+  ),
+  '\x1B[31mred\x1B[34mblue\x1B[31mred\x1B[34mblue\x1B[31mred\x1B[39m'
+);
+
+assert.strictEqual(
+  util.styleText('red',
+                 'red' +
+    util.styleText('blue', 'blue', { validateStream: false }) +
+    'red' +
+    util.styleText('red', 'red', { validateStream: false }) +
+    'red' +
+    util.styleText('blue', 'blue', { validateStream: false }),
+                 { validateStream: false }
+  ),
+  '\x1b[31mred\x1b[34mblue\x1b[31mred\x1b[31mred\x1b[31mred\x1b[34mblue\x1b[39m\x1b[39m'
+);
+
+assert.strictEqual(
+  util.styleText('red',
+                 'A' + util.styleText(['bgRed', 'blue'], 'B', { validateStream: false }) +
+    'C', { validateStream: false }),
+  '\x1B[31mA\x1B[41m\x1B[34mB\x1B[31m\x1B[49mC\x1B[39m'
+);
+
+assert.strictEqual(
+  util.styleText('dim',
+                 'dim' +
+    util.styleText('bold', 'bold', { validateStream: false }) +
+  'dim', { validateStream: false }),
+  '\x1B[2mdim\x1B[1mbold\x1B[22m\x1B[2mdim\x1B[22m'
+);
+
+assert.strictEqual(
+  util.styleText('blue',
+                 'blue' +
+    util.styleText('red',
+                   'red' +
+      util.styleText('green', 'green', { validateStream: false }) +
+      'red', { validateStream: false }) +
+    'blue', { validateStream: false }),
+  '\x1B[34mblue\x1B[31mred\x1B[32mgreen\x1B[31mred\x1B[34mblue\x1B[39m'
+);
 
-assert.strictEqual(util.styleText(['bold', 'red'], 'test'), '\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m');
-assert.strictEqual(util.styleText(['bold', 'red'], 'test'), util.styleText('bold', util.styleText('red', 'test')));
+assert.strictEqual(
+  util.styleText(
+    'red',
+    'red' +
+    util.styleText(
+      'blue',
+      'blue' + util.styleText('red', 'red', {
+        validateStream: false,
+      }) + 'blue',
+      {
+        validateStream: false,
+      }
+    ) + 'red', {
+      validateStream: false,
+    }
+  ),
+  '\x1b[31mred\x1b[34mblue\x1b[31mred\x1b[34mblue\x1b[31mred\x1b[39m'
+);
+
+assert.strictEqual(
+  util.styleText(['bold', 'red'], 'test', { validateStream: false }),
+  util.styleText(
+    'bold',
+    util.styleText('red', 'test', { validateStream: false }),
+    { validateStream: false },
+  ),
+);
 
 assert.throws(() => {
   util.styleText(['invalid'], 'text');
 }, {
   code: 'ERR_INVALID_ARG_VALUE',
 });
+
+assert.throws(() => {
+  util.styleText('red', 'text', { stream: {} });
+}, {
+  code: 'ERR_INVALID_ARG_TYPE',
+});
+
+// Color aliases should be accepted (e.g. 'grey' is an alias for 'gray')
+// See https://github.com/nodejs/node/issues/62177
+assert.strictEqual(
+  util.styleText('grey', 'test', { validateStream: false }),
+  util.styleText('gray', 'test', { validateStream: false }),
+);
+assert.strictEqual(
+  util.styleText('bgGrey', 'test', { validateStream: false }),
+  util.styleText('bgGray', 'test', { validateStream: false }),
+);
+assert.strictEqual(
+  util.styleText('blackBright', 'test', { validateStream: false }),
+  util.styleText('gray', 'test', { validateStream: false }),
+);
+assert.strictEqual(
+  util.styleText('faint', 'test', { validateStream: false }),
+  util.styleText('dim', 'test', { validateStream: false }),
+);
+assert.strictEqual(
+  util.styleText(['grey', 'bold'], 'test', { validateStream: false }),
+  util.styleText(['gray', 'bold'], 'test', { validateStream: false }),
+);
+
+// does not throw
+util.styleText('red', 'text', { stream: {}, validateStream: false });
+
+assert.strictEqual(
+  util.styleText('red', 'test', { validateStream: false }),
+  styled,
+);
+
+assert.strictEqual(util.styleText('none', 'test'), 'test');
+
+const fd = common.getTTYfd();
+if (fd !== -1) {
+  const writeStream = new WriteStream(fd);
+
+  const originalEnv = process.env;
+  [
+    { isTTY: true, env: {}, expected: styled },
+    { isTTY: false, env: {}, expected: noChange },
+    { isTTY: true, env: { NODE_DISABLE_COLORS: '1' }, expected: noChange },
+    { isTTY: true, env: { NO_COLOR: '1' }, expected: noChange },
+    { isTTY: true, env: { FORCE_COLOR: '1' }, expected: styled },
+    { isTTY: true, env: { FORCE_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
+    { isTTY: false, env: { FORCE_COLOR: '1', NO_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
+    { isTTY: true, env: { FORCE_COLOR: '1', NO_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
+  ].forEach((testCase) => {
+    writeStream.isTTY = testCase.isTTY;
+    process.env = {
+      ...process.env,
+      ...testCase.env
+    };
+    {
+      const output = util.styleText('red', 'test', { stream: writeStream });
+      assert.strictEqual(output, testCase.expected);
+    }
+    {
+      // Check that when passing an array of styles, the output behaves the same
+      const output = util.styleText(['red'], 'test', { stream: writeStream });
+      assert.strictEqual(output, testCase.expected);
+    }
+    process.env = originalEnv;
+  });
+} else {
+  common.skip('Could not create TTY fd');
+}
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-format.test.js b/test/js/node/util/node-inspect-tests/parallel/util-format.test.js
index 60b624034f36..57bbac75fb0c 100644
--- a/test/js/node/util/node-inspect-tests/parallel/util-format.test.js
+++ b/test/js/node/util/node-inspect-tests/parallel/util-format.test.js
@@ -478,7 +478,7 @@ test("no assertion failures", () => {
 
   assert.strictEqual(
     util.format(new SharedArrayBuffer(4)),
-    "SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }",
+    "SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4 }",
   );
 
   assert.strictEqual(util.formatWithOptions({ colors: true, compact: 3 }, "%s", [1, { a: true }]), "[ 1, [Object] ]");
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js b/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
index e538109fff99..071828145b75 100644
--- a/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
+++ b/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
@@ -140,37 +140,37 @@ test("no assertion failures", () => {
     const showHidden = true;
     const ab = new Uint8Array([1, 2, 3, 4]).buffer;
     const dv = new DataView(ab, 1, 2);
-    assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }");
+    assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, [byteLength]: 4 }");
     assert.strictEqual(
       util.inspect(new DataView(ab, 1, 2), showHidden),
       "DataView {\n" +
-        "  byteLength: 2,\n" +
-        "  byteOffset: 1,\n" +
-        "  buffer: ArrayBuffer {" +
-        " [Uint8Contents]: <01 02 03 04>, byteLength: 4 }\n}",
+        "  [byteLength]: 2,\n" +
+        "  [byteOffset]: 1,\n" +
+        "  [buffer]: ArrayBuffer {" +
+        " [Uint8Contents]: <01 02 03 04>, [byteLength]: 4 }\n}",
     );
-    assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }");
+    assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, [byteLength]: 4 }");
     assert.strictEqual(
       util.inspect(dv, showHidden),
       "DataView {\n" +
-        "  byteLength: 2,\n" +
-        "  byteOffset: 1,\n" +
-        "  buffer: ArrayBuffer { [Uint8Contents]: " +
-        "<01 02 03 04>, byteLength: 4 }\n}",
+        "  [byteLength]: 2,\n" +
+        "  [byteOffset]: 1,\n" +
+        "  [buffer]: ArrayBuffer { [Uint8Contents]: " +
+        "<01 02 03 04>, [byteLength]: 4 }\n}",
     );
     ab.x = 42;
     dv.y = 1337;
     assert.strictEqual(
       util.inspect(ab, showHidden),
-      "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, " + "byteLength: 4, x: 42 }",
+      "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, " + "[byteLength]: 4, x: 42 }",
     );
     assert.strictEqual(
-      util.inspect(dv, showHidden),
+      util.inspect(dv, { showHidden, breakLength: 82 }),
       "DataView {\n" +
-        "  byteLength: 2,\n" +
-        "  byteOffset: 1,\n" +
-        "  buffer: ArrayBuffer { [Uint8Contents]: <01 02 03 04>," +
-        " byteLength: 4, x: 42 },\n" +
+        "  [byteLength]: 2,\n" +
+        "  [byteOffset]: 1,\n" +
+        "  [buffer]: ArrayBuffer { [Uint8Contents]: <01 02 03 04>," +
+        " [byteLength]: 4, x: 42 },\n" +
         "  y: 1337\n}",
     );
   }
@@ -180,19 +180,19 @@ test("no assertion failures", () => {
     assert.strictEqual(ab.byteLength, 42);
     new MessageChannel().port1.postMessage(ab, [ab]);
     assert.strictEqual(ab.byteLength, 0);
-    assert.strictEqual(util.inspect(ab), "ArrayBuffer { (detached), byteLength: 0 }");
+    assert.strictEqual(util.inspect(ab), "ArrayBuffer { (detached), [byteLength]: 0 }");
   }
 
   // Truncate output for ArrayBuffers using plural or singular bytes
   {
     const ab = new ArrayBuffer(3);
     assert.strictEqual(
-      util.inspect(ab, { showHidden: true, maxArrayLength: 2 }),
-      "ArrayBuffer { [Uint8Contents]" + ": <00 00 ... 1 more byte>, byteLength: 3 }",
+      util.inspect(ab, { showHidden: true, maxArrayLength: 2, breakLength: 82 }),
+      "ArrayBuffer { [Uint8Contents]" + ": <00 00 ... 1 more byte>, [byteLength]: 3 }",
     );
     assert.strictEqual(
-      util.inspect(ab, { showHidden: true, maxArrayLength: 1 }),
-      "ArrayBuffer { [Uint8Contents]" + ": <00 ... 2 more bytes>, byteLength: 3 }",
+      util.inspect(ab, { showHidden: true, maxArrayLength: 1, breakLength: 82 }),
+      "ArrayBuffer { [Uint8Contents]" + ": <00 ... 2 more bytes>, [byteLength]: 3 }",
     );
   }
 });
@@ -202,36 +202,36 @@ test("inspect from a different context", () => {
   const showHidden = false;
   const ab = vm.runInNewContext("new ArrayBuffer(4)");
   const dv = vm.runInNewContext("new DataView(ab, 1, 2)", { ab });
-  assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }");
+  assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4 }");
   assert.strictEqual(
     util.inspect(new DataView(ab, 1, 2), showHidden),
     "DataView {\n" +
-      "  byteLength: 2,\n" +
-      "  byteOffset: 1,\n" +
-      "  buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }\n}",
+      "  [byteLength]: 2,\n" +
+      "  [byteOffset]: 1,\n" +
+      "  [buffer]: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4 }\n}",
   );
-  assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }");
+  assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4 }");
   //! segfaults
   /*assert.strictEqual(
     util.inspect(dv, showHidden),
     'DataView {\n' +
-    '  byteLength: 2,\n' +
-    '  byteOffset: 1,\n' +
-    '  buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }\n}'
+    '  [byteLength]: 2,\n' +
+    '  [byteOffset]: 1,\n' +
+    '  [buffer]: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4 }\n}'
   );*/
   ab.x = 42;
   dv.y = 1337;
   assert.strictEqual(
     util.inspect(ab, showHidden),
-    "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4, x: 42 }",
+    "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4, x: 42 }",
   );
   //! segfaults
   /*assert.strictEqual(
     util.inspect(dv, showHidden),
     'DataView {\n' +
-    '  byteLength: 2,\n' +
-    '  byteOffset: 1,\n' +
-    '  buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4, x: 42 },\n' +
+    '  [byteLength]: 2,\n' +
+    '  [byteOffset]: 1,\n' +
+    '  [buffer]: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, [byteLength]: 4, x: 42 },\n' +
     '  y: 1337\n}'
   );*/
 });
@@ -263,7 +263,7 @@ test("no assertion failures 2", () => {
         `  [length]: ${length},\n` +
         `  [byteLength]: ${byteLength},\n` +
         "  [byteOffset]: 0,\n" +
-        `  [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`,
+        `  [buffer]: ArrayBuffer { [byteLength]: ${byteLength} }\n]`,
     );
     assert.strictEqual(util.inspect(array, false), `${constructor.name}(${length}) [ 65, 97 ]`);
   });
@@ -299,7 +299,7 @@ test("no assertion failures 2", () => {
         `  [length]: ${length},\n` +
         `  [byteLength]: ${byteLength},\n` +
         "  [byteOffset]: 0,\n" +
-        `  [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`,
+        `  [buffer]: ArrayBuffer { [byteLength]: ${byteLength} }\n]`,
     );
     assert.strictEqual(util.inspect(array, false), `${constructor.name}(${length}) [ 65, 97 ]`);
   });
@@ -828,7 +828,7 @@ test("no assertion failures 2", () => {
     testColorStyle("null", null);
     testColorStyle("string", "test string");
     testColorStyle("date", new Date());
-    testColorStyle("regexp", /regexp/);
+    // RegExp now uses token-level highlighting; verified in a dedicated test file.
   }
 
   // An object with "hasOwnProperty" overwritten should not throw.
@@ -1628,7 +1628,7 @@ test("no assertion failures 2", () => {
       "    [byteLength]: 0,",
       "    [byteOffset]: 0,",
       "    [buffer]: ArrayBuffer {",
-      "      byteLength: 0,",
+      "      [byteLength]: 0,",
       "      foo: true",
       "    }",
       "  ],",
@@ -1646,7 +1646,7 @@ test("no assertion failures 2", () => {
       "      [byteLength]: 0,",
       "      [byteOffset]: 0,",
       "      [buffer]: ArrayBuffer {",
-      "        byteLength: 0,",
+      "        [byteLength]: 0,",
       "        foo: true",
       "      }",
       "    ],",
@@ -1675,7 +1675,7 @@ test("no assertion failures 2", () => {
       "    [length]: 0,",
       "    [byteLength]: 0,",
       "    [byteOffset]: 0,",
-      "    [buffer]: ArrayBuffer { byteLength: 0, foo: true }",
+      "    [buffer]: ArrayBuffer { [byteLength]: 0, foo: true }",
       "  ],",
       "  [Set Iterator] {\n" + "    [ 1, 2, [length]: 2 ],",
       "    [Symbol(Symbol.toStringTag)]: 'Set Iterator'\n" +
@@ -1685,7 +1685,7 @@ test("no assertion failures 2", () => {
       "      [length]: 0,",
       "      [byteLength]: 0,",
       "      [byteOffset]: 0,",
-      "      [buffer]: ArrayBuffer { byteLength: 0, foo: true }",
+      "      [buffer]: ArrayBuffer { [byteLength]: 0, foo: true }",
       "    ],",
       "    [Circular *1],",
       "    [Symbol(Symbol.toStringTag)]: 'Map Iterator'\n" + "  }",
@@ -1716,7 +1716,7 @@ test("no assertion failures 2", () => {
       "    [byteLength]: 0,",
       "    [byteOffset]: 0,",
       "    [buffer]: ArrayBuffer {",
-      "      byteLength: 0,",
+      "      [byteLength]: 0,",
       "      foo: true } ],",
       "  [Set Iterator] {",
       "    [ 1,",
@@ -1730,7 +1730,7 @@ test("no assertion failures 2", () => {
       "      [byteLength]: 0,",
       "      [byteOffset]: 0,",
       "      [buffer]: ArrayBuffer {",
-      "        byteLength: 0,",
+      "        [byteLength]: 0,",
       "        foo: true } ],",
       "    [Circular *1],",
       "    [Symbol(Symbol.toStringTag)]:",
@@ -2060,15 +2060,15 @@ test("no assertion failures 3", () => {
     [new BigUint64Array(2), "[BigUint64Array(2): null prototype] [ 0n, 0n ]"],
     [
       new ArrayBuffer(4),
-      "[ArrayBuffer: null prototype] {\n  [Uint8Contents]: <00 00 00 00>,\n  byteLength: undefined\n}",
+      "[ArrayBuffer: null prototype] {\n  [Uint8Contents]: <00 00 00 00>,\n  [byteLength]: undefined\n}",
     ],
     [
       new DataView(new ArrayBuffer(4)),
-      "[DataView: null prototype] {\n  byteLength: undefined,\n  byteOffset: undefined,\n  buffer: undefined\n}",
+      "[DataView: null prototype] {\n  [byteLength]: undefined,\n  [byteOffset]: undefined,\n  [buffer]: undefined\n}",
     ],
     [
       new SharedArrayBuffer(2),
-      "[SharedArrayBuffer: null prototype] {\n  [Uint8Contents]: <00 00>,\n  byteLength: undefined\n}",
+      "[SharedArrayBuffer: null prototype] {\n  [Uint8Contents]: <00 00>,\n  [byteLength]: undefined\n}",
     ],
     [new Date("Sun, 14 Feb 2010 11:48:40 GMT"), "[Date: null prototype] 2010-02-14T11:48:40.000Z"],
   ].forEach(([value, expected]) => {
diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js
index 8427b75c74e1..7689c1230321 100644
--- a/test/js/node/util/util.test.js
+++ b/test/js/node/util/util.test.js
@@ -341,10 +341,63 @@ describe("util", () => {
     );
   });
 
+  // styleText hex support, added in Node v26. Expectations verified against the
+  // node v26.3.0 binary.
+  describe("styleText hex colors", () => {
+    const noValidate = { validateStream: false };
+
+    it("parses 6-digit hex", () => {
+      expect(util.styleText("#ffcc00", "test", noValidate)).toBe("\u001b[38;2;255;204;0mtest\u001b[39m");
+      expect(util.styleText("#000000", "test", noValidate)).toBe("\u001b[38;2;0;0;0mtest\u001b[39m");
+      expect(util.styleText("#ffffff", "test", noValidate)).toBe("\u001b[38;2;255;255;255mtest\u001b[39m");
+    });
+
+    it("is case-insensitive", () => {
+      expect(util.styleText("#AABBCC", "test", noValidate)).toBe("\u001b[38;2;170;187;204mtest\u001b[39m");
+      expect(util.styleText("#aAbBcC", "test", noValidate)).toBe("\u001b[38;2;170;187;204mtest\u001b[39m");
+    });
+
+    it("expands 3-digit shorthand", () => {
+      expect(util.styleText("#fc0", "test", noValidate)).toBe("\u001b[38;2;255;204;0mtest\u001b[39m");
+      expect(util.styleText("#000", "test", noValidate)).toBe("\u001b[38;2;0;0;0mtest\u001b[39m");
+      expect(util.styleText("#FFF", "test", noValidate)).toBe("\u001b[38;2;255;255;255mtest\u001b[39m");
+      expect(util.styleText("#abc", "test", noValidate)).toBe("\u001b[38;2;170;187;204mtest\u001b[39m");
+    });
+
+    it("combines hex with named formats", () => {
+      expect(util.styleText(["bold", "#fc0"], "x", noValidate)).toBe(
+        "\u001b[1m\u001b[38;2;255;204;0mx\u001b[39m\u001b[22m",
+      );
+      expect(util.styleText(["#fc0", "underline"], "x", noValidate)).toBe(
+        "\u001b[38;2;255;204;0m\u001b[4mx\u001b[24m\u001b[39m",
+      );
+    });
+
+    it("nests hex colors by reopening the outer color", () => {
+      const inner = util.styleText("#0000ff", "inner", noValidate);
+      expect(util.styleText("#ff0000", `before${inner}after`, noValidate)).toBe(
+        "\u001b[38;2;255;0;0mbefore\u001b[38;2;0;0;255minner\u001b[38;2;255;0;0mafter\u001b[39m",
+      );
+    });
+
+    it("treats `none` as a passthrough", () => {
+      expect(util.styleText("none", "test", noValidate)).toBe("test");
+      expect(util.styleText(["none", "#fc0"], "x", noValidate)).toBe("\u001b[38;2;255;204;0mx\u001b[39m");
+    });
+
+    for (const invalid of ["#gggggg", "#ff", "#ffff", "#fffff", "#fffffff", "#", "ffcc00"]) {
+      it(`rejects ${invalid}`, () => {
+        expect(() => util.styleText(invalid, "t", noValidate)).toThrowWithCode(TypeError, "ERR_INVALID_ARG_VALUE");
+        expect(() => util.styleText([invalid], "t", noValidate)).toThrowWithCode(TypeError, "ERR_INVALID_ARG_VALUE");
+      });
+    }
+  });
+
   it("multiplecolors", () => {
-    expect(util.styleText(["bold", "red"], "test")).toBe("\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m");
-    expect(util.styleText("bold", "test")).toBe("\u001b[1mtest\u001b[22m");
-    expect(util.styleText("red", "test")).toBe("\u001b[31mtest\u001b[39m");
+    const noValidate = { validateStream: false };
+    expect(util.styleText(["bold", "red"], "test", noValidate)).toBe("\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m");
+    expect(util.styleText("bold", "test", noValidate)).toBe("\u001b[1mtest\u001b[22m");
+    expect(util.styleText("red", "test", noValidate)).toBe("\u001b[31mtest\u001b[39m");
   });
 
   it("styleText", () => {
@@ -376,7 +429,37 @@ describe("util", () => {
       },
     );
 
-    assert.strictEqual(util.styleText("red", "test"), "\u001b[31mtest\u001b[39m");
+    assert.strictEqual(util.styleText("red", "test", { validateStream: false }), "\u001b[31mtest\u001b[39m");
+  });
+
+  describe("getCallSites", () => {
+    it("restores Error state when stackTraceLimit is non-writable", () => {
+      const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit");
+      const savedPrepare = Error.prepareStackTrace;
+      try {
+        Object.defineProperty(Error, "stackTraceLimit", { value: 10, writable: false, configurable: true });
+        const sites = util.getCallSites(5);
+        expect(Array.isArray(sites)).toBe(true);
+        // Critical invariant: a user-installed prepareStackTrace must not be leaked.
+        expect(Error.prepareStackTrace).toBe(savedPrepare);
+      } finally {
+        Object.defineProperty(Error, "stackTraceLimit", desc);
+        Error.prepareStackTrace = savedPrepare;
+      }
+    });
+
+    it("each frame has the node v26 shape", () => {
+      const sites = util.getCallSites(3);
+      expect(sites.length).toBeGreaterThan(0);
+      expect(sites[0]).toEqual({
+        functionName: expect.any(String),
+        scriptId: expect.any(String),
+        scriptName: expect.stringContaining("util.test.js"),
+        lineNumber: expect.any(Number),
+        columnNumber: expect.any(Number),
+        column: sites[0].columnNumber,
+      });
+    });
   });
 
   describe("getSystemErrorName", () => {