Skip to content

Commit ea8b932

Browse files
committed
util: restore all information in inspect
The former implementation lacked symbols on the iterator objects without prototype. This is now fixed. The special handling for overriding `Symbol.iterator` was removed as it's very difficult to deal with this properly. Manipulating the symbols is just not supported. PR-URL: #22437 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 4dc8467 commit ea8b932

File tree

2 files changed

+45
-59
lines changed

2 files changed

+45
-59
lines changed

lib/util.js

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,6 @@ function getPrefix(constructor, tag, fallback) {
513513
return '';
514514
}
515515

516-
function addExtraKeys(source, target, keys) {
517-
for (const key of keys) {
518-
target[key] = source[key];
519-
}
520-
return target;
521-
}
522-
523516
function findTypedConstructor(value) {
524517
for (const [check, clazz] of [
525518
[isUint8Array, Uint8Array],
@@ -535,14 +528,36 @@ function findTypedConstructor(value) {
535528
[isBigUint64Array, BigUint64Array]
536529
]) {
537530
if (check(value)) {
538-
return new clazz(value);
531+
return clazz;
539532
}
540533
}
541-
return value;
542534
}
543535

544536
const getBoxedValue = formatPrimitive.bind(null, stylizeNoColor);
545537

538+
function noPrototypeIterator(ctx, value, recurseTimes) {
539+
let newVal;
540+
// TODO: Create a Subclass in case there's no prototype and show
541+
// `null-prototype`.
542+
if (isSet(value)) {
543+
const clazz = Object.getPrototypeOf(value) || Set;
544+
newVal = new clazz(setValues(value));
545+
} else if (isMap(value)) {
546+
const clazz = Object.getPrototypeOf(value) || Map;
547+
newVal = new clazz(mapEntries(value));
548+
} else if (Array.isArray(value)) {
549+
const clazz = Object.getPrototypeOf(value) || Array;
550+
newVal = new clazz(value.length || 0);
551+
} else if (isTypedArray(value)) {
552+
const clazz = findTypedConstructor(value) || Uint8Array;
553+
newVal = new clazz(value);
554+
}
555+
if (newVal) {
556+
Object.defineProperties(newVal, Object.getOwnPropertyDescriptors(value));
557+
return formatValue(ctx, newVal, recurseTimes);
558+
}
559+
}
560+
546561
// Note: using `formatValue` directly requires the indentation level to be
547562
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
548563
// value afterwards again.
@@ -798,39 +813,25 @@ function formatValue(ctx, value, recurseTimes) {
798813
braces = ['{', '}'];
799814
// The input prototype got manipulated. Special handle these.
800815
// We have to rebuild the information so we are able to display everything.
801-
} else if (isSet(value)) {
802-
const newVal = addExtraKeys(value, new Set(setValues(value)), keys);
803-
return formatValue(ctx, newVal, recurseTimes);
804-
} else if (isMap(value)) {
805-
const newVal = addExtraKeys(value, new Map(mapEntries(value)), keys);
806-
return formatValue(ctx, newVal, recurseTimes);
807-
} else if (Array.isArray(value)) {
808-
// The prefix is not always possible to fully reconstruct.
809-
const prefix = getPrefix(constructor, tag);
810-
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
811-
formatter = formatArray;
812-
const newValue = [];
813-
newValue.length = value.length;
814-
value = addExtraKeys(value, newValue, keys);
815-
} else if (isTypedArray(value)) {
816-
const newValue = findTypedConstructor(value);
817-
value = addExtraKeys(value, newValue, keys.slice(newValue.length));
818-
// The prefix is not always possible to fully reconstruct.
819-
braces = [`${getPrefix(getConstructorName(value), tag)}[`, ']'];
820-
formatter = formatTypedArray;
821-
} else if (isMapIterator(value)) {
822-
braces = [`[${tag || 'Map Iterator'}] {`, '}'];
823-
formatter = formatMapIterator;
824-
} else if (isSetIterator(value)) {
825-
braces = [`[${tag || 'Set Iterator'}] {`, '}'];
826-
formatter = formatSetIterator;
827-
// Handle other regular objects again.
828-
} else if (keyLength === 0) {
829-
if (isExternal(value))
830-
return ctx.stylize('[External]', 'special');
831-
return `${getPrefix(constructor, tag)}{}`;
832816
} else {
833-
braces[0] = `${getPrefix(constructor, tag)}{`;
817+
const specialIterator = noPrototypeIterator(ctx, value, recurseTimes);
818+
if (specialIterator) {
819+
return specialIterator;
820+
}
821+
if (isMapIterator(value)) {
822+
braces = [`[${tag || 'Map Iterator'}] {`, '}'];
823+
formatter = formatMapIterator;
824+
} else if (isSetIterator(value)) {
825+
braces = [`[${tag || 'Set Iterator'}] {`, '}'];
826+
formatter = formatSetIterator;
827+
// Handle other regular objects again.
828+
} else if (keyLength === 0) {
829+
if (isExternal(value))
830+
return ctx.stylize('[External]', 'special');
831+
return `${getPrefix(constructor, tag)}{}`;
832+
} else {
833+
braces[0] = `${getPrefix(constructor, tag)}{`;
834+
}
834835
}
835836
}
836837

test/parallel/test-util-inspect.js

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,24 +1557,6 @@ assert.strictEqual(util.inspect('"\''), '`"\'`');
15571557
// eslint-disable-next-line no-template-curly-in-string
15581558
assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
15591559

1560-
// Manipulating the Symbol.iterator should still produce nice results.
1561-
[
1562-
[[1, 2], '[ 1, 2 ]'],
1563-
[[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'],
1564-
[new Set([1, 2]), 'Set { 1, 2 }'],
1565-
[new Map([[1, 2]]), 'Map { 1 => 2 }'],
1566-
[new Uint8Array(2), 'Uint8Array [ 0, 0 ]'],
1567-
// It seems like the following can not be fully restored :(
1568-
[new Set([1, 2]).entries(), 'Object [Set Iterator] {}'],
1569-
[new Map([[1, 2]]).keys(), 'Object [Map Iterator] {}'],
1570-
].forEach(([value, expected]) => {
1571-
// "Remove the Symbol.iterator"
1572-
Object.defineProperty(value, Symbol.iterator, {
1573-
value: false
1574-
});
1575-
assert.strictEqual(util.inspect(value), expected);
1576-
});
1577-
15781560
// Verify the output in case the value has no prototype.
15791561
// Sadly, these cases can not be fully inspected :(
15801562
[
@@ -1630,6 +1612,9 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
16301612
);
16311613
value.foo = 'bar';
16321614
assert.notStrictEqual(util.inspect(value), expected);
1615+
delete value.foo;
1616+
value[Symbol('foo')] = 'yeah';
1617+
assert.notStrictEqual(util.inspect(value), expected);
16331618
});
16341619

16351620
assert.strictEqual(inspect(1n), '1n');

0 commit comments

Comments
 (0)