Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -2574,7 +2574,9 @@ changes:
* `format` {string | Array} A text format or an Array
of text formats defined in `util.inspect.colors`, or a hex color in `#RGB`
or `#RRGGBB` form.
* `text` {string} The text to be formatted.
* `text` {string|number|boolean|bigint} The text to be formatted.
Non-string scalar values (`number`, `boolean`, `bigint`) are coerced
to a string internally via `String(text)`.
* `options` {Object}
* `validateStream` {boolean} When true, `stream` is checked to see if it can handle colors. **Default:** `true`.
* `stream` {Stream} A stream that will be validated if it can be colored. **Default:** `process.stdout`.
Expand Down
15 changes: 13 additions & 2 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const {
ReflectApply,
RegExpPrototypeExec,
SafeMap,
String,
StringPrototypeSlice,
StringPrototypeToWellFormed,
} = primordials;
Expand Down Expand Up @@ -237,7 +238,7 @@ function rgbToAnsi24Bit(r, g, b) {

/**
* @param {string | string[]} format
* @param {string} text
* @param {string | number | boolean | bigint} text
* @param {object} [options]
* @param {boolean} [options.validateStream] - Whether to validate the stream.
* @param {Stream} [options.stream] - The stream used for validation.
Expand All @@ -246,6 +247,14 @@ function rgbToAnsi24Bit(r, g, b) {
function styleText(format, text, options) {
const validateStream = options?.validateStream ?? true;

// Coerce scalar values (number, boolean, bigint) to string.
// This avoids requiring callers to manually cast values like
// `String(count)` before passing them to styleText.
const textType = typeof text;
if (textType === 'number' || textType === 'boolean' || textType === 'bigint') {
text = String(text);
}

// Fast path: single format string with validateStream=false
if (!validateStream && typeof format === 'string' && typeof text === 'string') {
const cache = getStyleCache();
Expand All @@ -268,7 +277,9 @@ function styleText(format, text, options) {
}
}

validateString(text, 'text');
if (textType !== 'number' && textType !== 'boolean' && textType !== 'bigint') {
validateString(text, 'text');
}
if (options !== undefined) {
validateObject(options, 'options');
}
Expand Down
30 changes: 28 additions & 2 deletions test/parallel/test-util-styletext.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,39 @@ const noChange = 'test';
}, {
code: 'ERR_INVALID_ARG_VALUE',
}, invalidOption);
});

// Non-string scalars for `text` should NOT throw — they are coerced internally.
[
null,
undefined,
Symbol(),
() => {},
{},
].forEach((invalidText) => {
assert.throws(() => {
util.styleText('red', invalidOption);
util.styleText('red', invalidText);
}, {
code: 'ERR_INVALID_ARG_TYPE'
}, invalidOption);
}, invalidText);
});

// Scalar values are coerced to string (number, boolean, bigint).
assert.strictEqual(
util.styleText('red', 42, { validateStream: false }),
'\u001b[31m42\u001b[39m',
);

assert.strictEqual(
util.styleText('red', true, { validateStream: false }),
'\u001b[31mtrue\u001b[39m',
);

assert.strictEqual(
util.styleText('red', 3n, { validateStream: false }),
'\u001b[31m3\u001b[39m',
);

assert.throws(() => {
util.styleText('invalid', 'text');
}, {
Expand Down