Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b1c1ddf

Browse filesBrowse files
RafaelGSSbrunocroh
authored andcommitted
lib: optimize styleText when validateStream is false
This commit optimizes the util.styleText when validateStream is false Co-Authored-By: Bruno Rodrigues <swe@brunocroh.com> Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: #61792 Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 2c94967 commit b1c1ddf
Copy full SHA for b1c1ddf

2 files changed

+82-67Lines changed: 82 additions & 67 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/util.js‎

Copy file name to clipboardExpand all lines: lib/util.js
+80-65Lines changed: 80 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -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

105102
let 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
}
Collapse file

‎test/parallel/test-util-styletext.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-util-styletext.js
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ const noChange = 'test';
2222
util.styleText(invalidOption, 'test');
2323
}, {
2424
code: 'ERR_INVALID_ARG_VALUE',
25-
});
25+
}, invalidOption);
2626
assert.throws(() => {
2727
util.styleText('red', invalidOption);
2828
}, {
2929
code: 'ERR_INVALID_ARG_TYPE'
30-
});
30+
}, invalidOption);
3131
});
3232

3333
assert.throws(() => {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.