From e899ec4363b0d526e7afc5d9fcbf413ad20650cf Mon Sep 17 00:00:00 2001 From: SparshGarg999 Date: Sun, 12 Jul 2026 20:37:10 +0530 Subject: [PATCH] util: allow styleText text param to accept non-string scalars Allow util.styleText() to accept umber, oolean, and igint`nvalues for the ext parameter, automatically coercing them to string via String(text). Previously, passing anything other than a string would throw ERR_INVALID_ARG_TYPE. This forced callers to wrap every non-string value manually: util.styleText('red', String(count)) // before util.styleText('red', count) // after The coercion is applied before the fast path so there is no extra branching cost for the common string case. Values that cannot be meaningfully stringified (null, undefined, Symbol, Function, Object) still throw ERR_INVALID_ARG_TYPE as before. Fixes: https://github.com/nodejs/node/issues/63841 Signed-off-by: SparshGarg999 --- doc/api/util.md | 4 +++- lib/util.js | 15 ++++++++++++-- test/parallel/test-util-styletext.js | 30 ++++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/doc/api/util.md b/doc/api/util.md index fd93b52722934f..761b14621066c1 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -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`. diff --git a/lib/util.js b/lib/util.js index e828229380d9e8..90104c0c6d04c6 100644 --- a/lib/util.js +++ b/lib/util.js @@ -39,6 +39,7 @@ const { ReflectApply, RegExpPrototypeExec, SafeMap, + String, StringPrototypeSlice, StringPrototypeToWellFormed, } = primordials; @@ -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. @@ -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(); @@ -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'); } diff --git a/test/parallel/test-util-styletext.js b/test/parallel/test-util-styletext.js index 3db01bec1c3acd..7586028d298d43 100644 --- a/test/parallel/test-util-styletext.js +++ b/test/parallel/test-util-styletext.js @@ -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'); }, {