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 416052a

Browse filesBrowse files
islandryuaduh95
authored andcommitted
util: fix formatting of objects with built-in Symbol.toPrimitive
Fixes: #57818 PR-URL: #57832 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent dda6ca9 commit 416052a
Copy full SHA for 416052a

File tree

3 files changed

+85
-14
lines changed
Filter options

3 files changed

+85
-14
lines changed

‎doc/api/util.md

Copy file name to clipboardExpand all lines: doc/api/util.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ corresponding argument. Supported specifiers are:
436436

437437
* `%s`: `String` will be used to convert all values except `BigInt`, `Object`
438438
and `-0`. `BigInt` values will be represented with an `n` and Objects that
439-
have no user defined `toString` function are inspected using `util.inspect()`
439+
have neither a user defined `toString` function nor `Symbol.toPrimitive` function are inspected using `util.inspect()`
440440
with options `{ depth: 0, colors: false, compact: 3 }`.
441441
* `%d`: `Number` will be used to convert all values except `BigInt` and
442442
`Symbol`.

‎lib/internal/util/inspect.js

Copy file name to clipboardExpand all lines: lib/internal/util/inspect.js
+22-13Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,27 +2166,32 @@ function hasBuiltInToString(value) {
21662166
value = proxyTarget;
21672167
}
21682168

2169-
// Check if value has a custom Symbol.toPrimitive transformation.
2170-
if (typeof value[SymbolToPrimitive] === 'function') {
2171-
return false;
2172-
}
2169+
let hasOwnToString = ObjectPrototypeHasOwnProperty;
2170+
let hasOwnToPrimitive = ObjectPrototypeHasOwnProperty;
21732171

2174-
// Count objects that have no `toString` function as built-in.
2172+
// Count objects without `toString` and `Symbol.toPrimitive` function as built-in.
21752173
if (typeof value.toString !== 'function') {
2176-
return true;
2177-
}
2178-
2179-
// The object has a own `toString` property. Thus it's not not a built-in one.
2180-
if (ObjectPrototypeHasOwnProperty(value, 'toString')) {
2174+
if (typeof value[SymbolToPrimitive] !== 'function') {
2175+
return true;
2176+
} else if (ObjectPrototypeHasOwnProperty(value, SymbolToPrimitive)) {
2177+
return false;
2178+
}
2179+
hasOwnToString = returnFalse;
2180+
} else if (ObjectPrototypeHasOwnProperty(value, 'toString')) {
2181+
return false;
2182+
} else if (typeof value[SymbolToPrimitive] !== 'function') {
2183+
hasOwnToPrimitive = returnFalse;
2184+
} else if (ObjectPrototypeHasOwnProperty(value, SymbolToPrimitive)) {
21812185
return false;
21822186
}
21832187

2184-
// Find the object that has the `toString` property as own property in the
2185-
// prototype chain.
2188+
// Find the object that has the `toString` property or `Symbol.toPrimitive` property
2189+
// as own property in the prototype chain.
21862190
let pointer = value;
21872191
do {
21882192
pointer = ObjectGetPrototypeOf(pointer);
2189-
} while (!ObjectPrototypeHasOwnProperty(pointer, 'toString'));
2193+
} while (!hasOwnToString(pointer, 'toString') &&
2194+
!hasOwnToPrimitive(pointer, SymbolToPrimitive));
21902195

21912196
// Check closer if the object is a built-in.
21922197
const descriptor = ObjectGetOwnPropertyDescriptor(pointer, 'constructor');
@@ -2195,6 +2200,10 @@ function hasBuiltInToString(value) {
21952200
builtInObjects.has(descriptor.value.name);
21962201
}
21972202

2203+
function returnFalse() {
2204+
return false;
2205+
}
2206+
21982207
const firstErrorLine = (error) => StringPrototypeSplit(error.message, '\n', 1)[0];
21992208
let CIRCULAR_ERROR_MESSAGE;
22002209
function tryStringify(arg) {

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

Copy file name to clipboardExpand all lines: test/parallel/test-util-format.js
+62Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,68 @@ assert.strictEqual(util.format('%s', -Infinity), '-Infinity');
290290
assert.strictEqual(util.format('%s', objectWithToPrimitive + ''), 'default context');
291291
}
292292

293+
// built-in toPrimitive is the same behavior as inspect
294+
{
295+
const date = new Date('2023-10-01T00:00:00Z');
296+
assert.strictEqual(util.format('%s', date), util.inspect(date));
297+
298+
const symbol = Symbol('foo');
299+
assert.strictEqual(util.format('%s', symbol), util.inspect(symbol));
300+
}
301+
302+
// Prototype chain handling for toString
303+
{
304+
function hasToStringButNoToPrimitive() {}
305+
306+
hasToStringButNoToPrimitive.prototype.toString = function() {
307+
return 'hasToStringButNoToPrimitive';
308+
};
309+
310+
let obj = new hasToStringButNoToPrimitive();
311+
assert.strictEqual(util.format('%s', obj.toString()), 'hasToStringButNoToPrimitive');
312+
313+
function inheritsFromHasToStringButNoToPrimitive() {}
314+
Object.setPrototypeOf(inheritsFromHasToStringButNoToPrimitive.prototype,
315+
hasToStringButNoToPrimitive.prototype);
316+
obj = new inheritsFromHasToStringButNoToPrimitive();
317+
assert.strictEqual(util.format('%s', obj.toString()), 'hasToStringButNoToPrimitive');
318+
}
319+
320+
// Prototype chain handling for Symbol.toPrimitive
321+
{
322+
function hasToPrimitiveButNoToString() {}
323+
324+
hasToPrimitiveButNoToString.prototype[Symbol.toPrimitive] = function() {
325+
return 'hasToPrimitiveButNoToString';
326+
};
327+
328+
let obj = new hasToPrimitiveButNoToString();
329+
assert.strictEqual(util.format('%s', obj[Symbol.toPrimitive]()), 'hasToPrimitiveButNoToString');
330+
function inheritsFromHasToPrimitiveButNoToString() {}
331+
Object.setPrototypeOf(inheritsFromHasToPrimitiveButNoToString.prototype,
332+
hasToPrimitiveButNoToString.prototype);
333+
obj = new inheritsFromHasToPrimitiveButNoToString();
334+
assert.strictEqual(util.format('%s', obj[Symbol.toPrimitive]()), 'hasToPrimitiveButNoToString');
335+
}
336+
337+
// Prototype chain handling for both toString and Symbol.toPrimitive
338+
{
339+
function hasBothToStringAndToPrimitive() {}
340+
hasBothToStringAndToPrimitive.prototype.toString = function() {
341+
return 'toString';
342+
};
343+
hasBothToStringAndToPrimitive.prototype[Symbol.toPrimitive] = function() {
344+
return 'toPrimitive';
345+
};
346+
let obj = new hasBothToStringAndToPrimitive();
347+
assert.strictEqual(util.format('%s', obj.toString()), 'toString');
348+
function inheritsFromHasBothToStringAndToPrimitive() {}
349+
Object.setPrototypeOf(inheritsFromHasBothToStringAndToPrimitive.prototype,
350+
hasBothToStringAndToPrimitive.prototype);
351+
obj = new inheritsFromHasBothToStringAndToPrimitive();
352+
assert.strictEqual(util.format('%s', obj.toString()), 'toString');
353+
}
354+
293355
// JSON format specifier
294356
assert.strictEqual(util.format('%j'), '%j');
295357
assert.strictEqual(util.format('%j', 42), '42');

0 commit comments

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