diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js index 73da4446f824fe..01c1548ea398b5 100644 --- a/lib/internal/util/inspect.js +++ b/lib/internal/util/inspect.js @@ -87,6 +87,7 @@ const { SymbolPrototypeToString, SymbolPrototypeValueOf, SymbolIterator, + SymbolToPrimitive, SymbolToStringTag, TypedArrayPrototypeGetLength, TypedArrayPrototypeGetSymbolToStringTag, @@ -2103,6 +2104,11 @@ function hasBuiltInToString(value) { value = proxyTarget; } + // Check if value has a custom Symbol.toPrimitive transformation. + if (typeof value[SymbolToPrimitive] === 'function') { + return false; + } + // Count objects that have no `toString` function as built-in. if (typeof value.toString !== 'function') { return true; diff --git a/test/parallel/test-util-format.js b/test/parallel/test-util-format.js index 12e7833bb24457..8d2cab5a9c7a1c 100644 --- a/test/parallel/test-util-format.js +++ b/test/parallel/test-util-format.js @@ -269,6 +269,27 @@ assert.strictEqual(util.format('%s', -Infinity), '-Infinity'); ); } +// Symbol.toPrimitive handling for string format specifier +{ + const objectWithToPrimitive = { + [Symbol.toPrimitive](hint) { + switch (hint) { + case 'number': + return 42; + case 'string': + return 'string representation'; + case 'default': + default: + return 'default context'; + } + } + }; + + assert.strictEqual(util.format('%s', +objectWithToPrimitive), '42'); + assert.strictEqual(util.format('%s', objectWithToPrimitive), 'string representation'); + assert.strictEqual(util.format('%s', objectWithToPrimitive + ''), 'default context'); +} + // JSON format specifier assert.strictEqual(util.format('%j'), '%j'); assert.strictEqual(util.format('%j', 42), '42');