From 5f9c6634b083990e5f3b9e2e59bcf5991bda693a Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Thu, 12 Feb 2026 15:49:35 -0300 Subject: [PATCH] lib: optimize styleText when validateStream is false This commit optimizes the util.styleText when validateStream is false Co-Authored-By: Bruno Rodrigues Signed-off-by: RafaelGSS --- lib/util.js | 155 +++++++++++++++------------ test/parallel/test-util-styletext.js | 4 +- 2 files changed, 87 insertions(+), 72 deletions(-) diff --git a/lib/util.js b/lib/util.js index ebd486addc7eb2..8da271bb80c877 100644 --- a/lib/util.js +++ b/lib/util.js @@ -25,7 +25,6 @@ const { ArrayIsArray, ArrayPrototypePop, ArrayPrototypePush, - ArrayPrototypeReduce, Error, ErrorCaptureStackTrace, FunctionPrototypeBind, @@ -37,8 +36,6 @@ const { ObjectSetPrototypeOf, ObjectValues, ReflectApply, - RegExp, - RegExpPrototypeSymbolReplace, StringPrototypeToWellFormed, } = primordials; @@ -104,13 +101,58 @@ function lazyAbortController() { let internalDeepEqual; -/** - * @param {string} [code] - * @returns {string} - */ -function escapeStyleCode(code) { - if (code === undefined) return ''; - return `\u001b[${code}m`; +// Pre-computed ANSI escape code constants +const kEscape = '\u001b['; +const kEscapeEnd = 'm'; + +// Codes for dim (2) and bold (1) - these share close code 22 +const kDimCode = 2; +const kBoldCode = 1; + +let styleCache; + +function getStyleCache() { + if (styleCache === undefined) { + styleCache = { __proto__: null }; + const colors = inspect.colors; + for (const key of ObjectKeys(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, + }; + } + } + } + return styleCache; +} + +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); } /** @@ -121,12 +163,28 @@ function escapeStyleCode(code) { * @param {Stream} [options.stream] - The stream used for validation. * @returns {string} */ -function styleText(format, text, { validateStream = true, stream = process.stdout } = {}) { +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; + } + } + validateString(text, 'text'); + if (options !== undefined) { + validateObject(options, 'options'); + } validateBoolean(validateStream, 'options.validateStream'); - let skipColorize; if (validateStream) { + const stream = options?.stream ?? process.stdout; if ( !isReadableStream(stream) && !isWritableStream(stream) && @@ -134,70 +192,27 @@ function styleText(format, text, { validateStream = true, stream = process.stdou ) { throw new ERR_INVALID_ARG_TYPE('stream', ['ReadableStream', 'WritableStream', 'Stream'], stream); } - - // If the stream is falsy or should not be colorized, set skipColorize to true - skipColorize = !lazyUtilColors().shouldColorize(stream); + const skipColorize = !lazyUtilColors().shouldColorize(stream); + if (skipColorize) return text; } - // If the format is not an array, convert it to an array - const formatArray = ArrayIsArray(format) ? format : [format]; + const isArray = ArrayIsArray(format); + const formatArray = isArray ? format : [format]; + + let openCodes = ''; + let closeCodes = ''; + let processedText = text; - const codes = []; - for (const key of formatArray) { + for (let i = 0; i < formatArray.length; i++) { + const key = formatArray[i]; if (key === 'none') continue; - const formatCodes = inspect.colors[key]; - // If the format is not a valid style, throw an error - if (formatCodes == null) { + const style = cache[key]; + if (style === undefined) { validateOneOf(key, 'format', ObjectKeys(inspect.colors)); } - if (skipColorize) continue; - ArrayPrototypePush(codes, formatCodes); - } - - if (skipColorize) { - return text; - } - - // Build opening codes - let openCodes = ''; - for (let i = 0; i < codes.length; i++) { - openCodes += escapeStyleCode(codes[i][0]); - } - - // Process the text to handle nested styles - let processedText; - if (codes.length > 0) { - processedText = ArrayPrototypeReduce( - codes, - (text, code) => RegExpPrototypeSymbolReplace( - // Find the reset code - new RegExp(`\\u001b\\[${code[1]}m`, 'g'), - text, - (match, offset) => { - // Check if there's more content after this reset - if (offset + match.length < text.length) { - if ( - code[0] === inspect.colors.dim[0] || - code[0] === inspect.colors.bold[0] - ) { - // Dim and bold are not mutually exclusive, so we need to reapply - return `${match}${escapeStyleCode(code[0])}`; - } - return escapeStyleCode(code[0]); - } - return match; - }, - ), - text, - ); - } else { - processedText = text; - } - - // Build closing codes in reverse order - let closeCodes = ''; - for (let i = codes.length - 1; i >= 0; i--) { - closeCodes += escapeStyleCode(codes[i][1]); + openCodes += style.openSeq; + closeCodes = style.closeSeq + closeCodes; + processedText = replaceCloseCode(processedText, style.closeSeq, style.openSeq, style.keepClose); } return `${openCodes}${processedText}${closeCodes}`; diff --git a/test/parallel/test-util-styletext.js b/test/parallel/test-util-styletext.js index b87c5d7e82c74c..4fdf419143453c 100644 --- a/test/parallel/test-util-styletext.js +++ b/test/parallel/test-util-styletext.js @@ -22,12 +22,12 @@ const noChange = 'test'; util.styleText(invalidOption, 'test'); }, { code: 'ERR_INVALID_ARG_VALUE', - }); + }, invalidOption); assert.throws(() => { util.styleText('red', invalidOption); }, { code: 'ERR_INVALID_ARG_TYPE' - }); + }, invalidOption); }); assert.throws(() => {