@@ -25,7 +25,6 @@ const {
2525 ArrayIsArray,
2626 ArrayPrototypePop,
2727 ArrayPrototypePush,
28- ArrayPrototypeReduce,
2928 Error,
3029 ErrorCaptureStackTrace,
3130 FunctionPrototypeBind,
@@ -37,8 +36,6 @@ const {
3736 ObjectSetPrototypeOf,
3837 ObjectValues,
3938 ReflectApply,
40- RegExp,
41- RegExpPrototypeSymbolReplace,
4239 StringPrototypeToWellFormed,
4340} = primordials ;
4441
@@ -104,13 +101,58 @@ function lazyAbortController() {
104101
105102let internalDeepEqual ;
106103
107- /**
108- * @param {string } [code]
109- * @returns {string }
110- */
111- function escapeStyleCode ( code ) {
112- if ( code === undefined ) return '' ;
113- return `\u001b[${ code } m` ;
104+ // Pre-computed ANSI escape code constants
105+ const kEscape = '\u001b[' ;
106+ const kEscapeEnd = 'm' ;
107+
108+ // Codes for dim (2) and bold (1) - these share close code 22
109+ const kDimCode = 2 ;
110+ const kBoldCode = 1 ;
111+
112+ let styleCache ;
113+
114+ function getStyleCache ( ) {
115+ if ( styleCache === undefined ) {
116+ styleCache = { __proto__ : null } ;
117+ const colors = inspect . colors ;
118+ for ( const key of ObjectKeys ( colors ) ) {
119+ const codes = colors [ key ] ;
120+ if ( codes ) {
121+ const openNum = codes [ 0 ] ;
122+ const closeNum = codes [ 1 ] ;
123+ styleCache [ key ] = {
124+ __proto__ : null ,
125+ openSeq : kEscape + openNum + kEscapeEnd ,
126+ closeSeq : kEscape + closeNum + kEscapeEnd ,
127+ keepClose : openNum === kDimCode || openNum === kBoldCode ,
128+ } ;
129+ }
130+ }
131+ }
132+ return styleCache ;
133+ }
134+
135+ function replaceCloseCode ( str , closeSeq , openSeq , keepClose ) {
136+ const closeLen = closeSeq . length ;
137+ let index = str . indexOf ( closeSeq ) ;
138+ if ( index === - 1 ) return str ;
139+
140+ let result = '' ;
141+ let lastIndex = 0 ;
142+ const replacement = keepClose ? closeSeq + openSeq : openSeq ;
143+
144+ do {
145+ const afterClose = index + closeLen ;
146+ if ( afterClose < str . length ) {
147+ result += str . slice ( lastIndex , index ) + replacement ;
148+ lastIndex = afterClose ;
149+ } else {
150+ break ;
151+ }
152+ index = str . indexOf ( closeSeq , lastIndex ) ;
153+ } while ( index !== - 1 ) ;
154+
155+ return result + str . slice ( lastIndex ) ;
114156}
115157
116158/**
@@ -121,84 +163,57 @@ function escapeStyleCode(code) {
121163 * @param {Stream } [options.stream] - The stream used for validation.
122164 * @returns {string }
123165 */
124- function styleText ( format , text , { validateStream = true , stream = process . stdout } = { } ) {
166+ function styleText ( format , text , options ) {
167+ const validateStream = options ?. validateStream ?? true ;
168+ const cache = getStyleCache ( ) ;
169+
170+ // Fast path: single format string with validateStream=false
171+ if ( ! validateStream && typeof format === 'string' && typeof text === 'string' ) {
172+ if ( format === 'none' ) return text ;
173+ const style = cache [ format ] ;
174+ if ( style !== undefined ) {
175+ const processed = replaceCloseCode ( text , style . closeSeq , style . openSeq , style . keepClose ) ;
176+ return style . openSeq + processed + style . closeSeq ;
177+ }
178+ }
179+
125180 validateString ( text , 'text' ) ;
181+ if ( options !== undefined ) {
182+ validateObject ( options , 'options' ) ;
183+ }
126184 validateBoolean ( validateStream , 'options.validateStream' ) ;
127185
128186 let skipColorize ;
129187 if ( validateStream ) {
188+ const stream = options ?. stream ?? process . stdout ;
130189 if (
131190 ! isReadableStream ( stream ) &&
132191 ! isWritableStream ( stream ) &&
133192 ! isNodeStream ( stream )
134193 ) {
135194 throw new ERR_INVALID_ARG_TYPE ( 'stream' , [ 'ReadableStream' , 'WritableStream' , 'Stream' ] , stream ) ;
136195 }
137-
138- // If the stream is falsy or should not be colorized, set skipColorize to true
139196 skipColorize = ! lazyUtilColors ( ) . shouldColorize ( stream ) ;
140197 }
141198
142- // If the format is not an array, convert it to an array
143199 const formatArray = ArrayIsArray ( format ) ? format : [ format ] ;
144200
145- const codes = [ ] ;
201+ let openCodes = '' ;
202+ let closeCodes = '' ;
203+ let processedText = text ;
204+
146205 for ( const key of formatArray ) {
147206 if ( key === 'none' ) continue ;
148- const formatCodes = inspect . colors [ key ] ;
149- // If the format is not a valid style, throw an error
150- if ( formatCodes == null ) {
207+ const style = cache [ key ] ;
208+ if ( style === undefined ) {
151209 validateOneOf ( key , 'format' , ObjectKeys ( inspect . colors ) ) ;
152210 }
153- if ( skipColorize ) continue ;
154- ArrayPrototypePush ( codes , formatCodes ) ;
155- }
156-
157- if ( skipColorize ) {
158- return text ;
211+ openCodes += style . openSeq ;
212+ closeCodes = style . closeSeq + closeCodes ;
213+ processedText = replaceCloseCode ( processedText , style . closeSeq , style . openSeq , style . keepClose ) ;
159214 }
160215
161- // Build opening codes
162- let openCodes = '' ;
163- for ( let i = 0 ; i < codes . length ; i ++ ) {
164- openCodes += escapeStyleCode ( codes [ i ] [ 0 ] ) ;
165- }
166-
167- // Process the text to handle nested styles
168- let processedText ;
169- if ( codes . length > 0 ) {
170- processedText = ArrayPrototypeReduce (
171- codes ,
172- ( text , code ) => RegExpPrototypeSymbolReplace (
173- // Find the reset code
174- new RegExp ( `\\u001b\\[${ code [ 1 ] } m` , 'g' ) ,
175- text ,
176- ( match , offset ) => {
177- // Check if there's more content after this reset
178- if ( offset + match . length < text . length ) {
179- if (
180- code [ 0 ] === inspect . colors . dim [ 0 ] ||
181- code [ 0 ] === inspect . colors . bold [ 0 ]
182- ) {
183- // Dim and bold are not mutually exclusive, so we need to reapply
184- return `${ match } ${ escapeStyleCode ( code [ 0 ] ) } ` ;
185- }
186- return escapeStyleCode ( code [ 0 ] ) ;
187- }
188- return match ;
189- } ,
190- ) ,
191- text ,
192- ) ;
193- } else {
194- processedText = text ;
195- }
196-
197- // Build closing codes in reverse order
198- let closeCodes = '' ;
199- for ( let i = codes . length - 1 ; i >= 0 ; i -- ) {
200- closeCodes += escapeStyleCode ( codes [ i ] [ 1 ] ) ;
201- }
216+ if ( skipColorize ) return text ;
202217
203218 return `${ openCodes } ${ processedText } ${ closeCodes } ` ;
204219}
0 commit comments