Skip to content

Commit eeae598

Browse files
committed
util: refactor inspect code for constistency
This removes the special handling to inspect iterable objects with a null prototype. It is now handled together with the regular prototype. PR-URL: #30225 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent b2bfacb commit eeae598

File tree

2 files changed

+80
-92
lines changed

2 files changed

+80
-92
lines changed

lib/internal/util/inspect.js

+67-92
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const {
1010
DatePrototypeToString,
1111
ErrorPrototypeToString,
1212
JSONStringify,
13+
MapPrototype,
1314
MapPrototypeEntries,
1415
MathFloor,
1516
MathMax,
@@ -21,10 +22,8 @@ const {
2122
NumberPrototypeValueOf,
2223
ObjectAssign,
2324
ObjectCreate,
24-
ObjectDefineProperties,
2525
ObjectDefineProperty,
2626
ObjectGetOwnPropertyDescriptor,
27-
ObjectGetOwnPropertyDescriptors,
2827
ObjectGetOwnPropertyNames,
2928
ObjectGetOwnPropertySymbols,
3029
ObjectGetPrototypeOf,
@@ -34,6 +33,7 @@ const {
3433
ObjectPrototypePropertyIsEnumerable,
3534
ObjectSeal,
3635
RegExpPrototypeToString,
36+
SetPrototype,
3737
SetPrototypeValues,
3838
StringPrototypeValueOf,
3939
SymbolPrototypeToString,
@@ -113,6 +113,11 @@ const assert = require('internal/assert');
113113

114114
const { NativeModule } = require('internal/bootstrap/loaders');
115115

116+
const setSizeGetter = uncurryThis(
117+
ObjectGetOwnPropertyDescriptor(SetPrototype, 'size').get);
118+
const mapSizeGetter = uncurryThis(
119+
ObjectGetOwnPropertyDescriptor(MapPrototype, 'size').get);
120+
116121
let hexSlice;
117122

118123
const builtInObjects = new Set(
@@ -651,51 +656,6 @@ function findTypedConstructor(value) {
651656
}
652657
}
653658

654-
let lazyNullPrototypeCache;
655-
// Creates a subclass and name
656-
// the constructor as `${clazz} : null prototype`
657-
function clazzWithNullPrototype(clazz, name) {
658-
if (lazyNullPrototypeCache === undefined) {
659-
lazyNullPrototypeCache = new Map();
660-
} else {
661-
const cachedClass = lazyNullPrototypeCache.get(clazz);
662-
if (cachedClass !== undefined) {
663-
return cachedClass;
664-
}
665-
}
666-
class NullPrototype extends clazz {
667-
get [SymbolToStringTag]() {
668-
return '';
669-
}
670-
}
671-
ObjectDefineProperty(NullPrototype.prototype.constructor, 'name',
672-
{ value: `[${name}: null prototype]` });
673-
lazyNullPrototypeCache.set(clazz, NullPrototype);
674-
return NullPrototype;
675-
}
676-
677-
function noPrototypeIterator(ctx, value, recurseTimes) {
678-
let newVal;
679-
if (isSet(value)) {
680-
const clazz = clazzWithNullPrototype(Set, 'Set');
681-
newVal = new clazz(SetPrototypeValues(value));
682-
} else if (isMap(value)) {
683-
const clazz = clazzWithNullPrototype(Map, 'Map');
684-
newVal = new clazz(MapPrototypeEntries(value));
685-
} else if (ArrayIsArray(value)) {
686-
const clazz = clazzWithNullPrototype(Array, 'Array');
687-
newVal = new clazz(value.length);
688-
} else if (isTypedArray(value)) {
689-
const constructor = findTypedConstructor(value);
690-
const clazz = clazzWithNullPrototype(constructor, constructor.name);
691-
newVal = new clazz(value);
692-
}
693-
if (newVal !== undefined) {
694-
ObjectDefineProperties(newVal, ObjectGetOwnPropertyDescriptors(value));
695-
return formatRaw(ctx, newVal, recurseTimes);
696-
}
697-
}
698-
699659
// Note: using `formatValue` directly requires the indentation level to be
700660
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
701661
// value afterwards again.
@@ -798,7 +758,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
798758
let extrasType = kObjectType;
799759

800760
// Iterators and the rest are split to reduce checks.
801-
if (value[SymbolIterator]) {
761+
// We have to check all values in case the constructor is set to null.
762+
// Otherwise it would not possible to identify all types properly.
763+
if (value[SymbolIterator] || constructor === null) {
802764
noIterator = false;
803765
if (ArrayIsArray(value)) {
804766
keys = getOwnNonIndexProperties(value, filter);
@@ -810,37 +772,66 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
810772
extrasType = kArrayExtrasType;
811773
formatter = formatArray;
812774
} else if (isSet(value)) {
775+
const size = setSizeGetter(value);
813776
keys = getKeys(value, ctx.showHidden);
814-
const prefix = getPrefix(constructor, tag, 'Set');
815-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
777+
let prefix = '';
778+
if (constructor !== null) {
779+
if (constructor === tag)
780+
tag = '';
781+
prefix = getPrefix(`${constructor}`, tag, '');
782+
formatter = formatSet.bind(null, value, size);
783+
} else {
784+
prefix = getPrefix(constructor, tag, 'Set');
785+
formatter = formatSet.bind(null, SetPrototypeValues(value), size);
786+
}
787+
if (size === 0 && keys.length === 0 && protoProps === undefined)
816788
return `${prefix}{}`;
817789
braces = [`${prefix}{`, '}'];
818-
formatter = formatSet;
819790
} else if (isMap(value)) {
791+
const size = mapSizeGetter(value);
820792
keys = getKeys(value, ctx.showHidden);
821-
const prefix = getPrefix(constructor, tag, 'Map');
822-
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
793+
let prefix = '';
794+
if (constructor !== null) {
795+
if (constructor === tag)
796+
tag = '';
797+
prefix = getPrefix(`${constructor}`, tag, '');
798+
formatter = formatMap.bind(null, value, size);
799+
} else {
800+
prefix = getPrefix(constructor, tag, 'Map');
801+
formatter = formatMap.bind(null, MapPrototypeEntries(value), size);
802+
}
803+
if (size === 0 && keys.length === 0 && protoProps === undefined)
823804
return `${prefix}{}`;
824805
braces = [`${prefix}{`, '}'];
825-
formatter = formatMap;
826806
} else if (isTypedArray(value)) {
827807
keys = getOwnNonIndexProperties(value, filter);
828-
const prefix = constructor !== null ?
829-
getPrefix(constructor, tag) :
830-
getPrefix(constructor, tag, findTypedConstructor(value).name);
808+
let bound = value;
809+
let prefix = '';
810+
if (constructor === null) {
811+
const constr = findTypedConstructor(value);
812+
prefix = getPrefix(constructor, tag, constr.name);
813+
// Reconstruct the array information.
814+
bound = new constr(value);
815+
} else {
816+
prefix = getPrefix(constructor, tag);
817+
}
831818
braces = [`${prefix}[`, ']'];
832819
if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
833820
return `${braces[0]}]`;
834-
formatter = formatTypedArray;
821+
// Special handle the value. The original value is required below. The
822+
// bound function is required to reconstruct missing information.
823+
formatter = formatTypedArray.bind(null, bound);
835824
extrasType = kArrayExtrasType;
836825
} else if (isMapIterator(value)) {
837826
keys = getKeys(value, ctx.showHidden);
838827
braces = getIteratorBraces('Map', tag);
839-
formatter = formatIterator;
828+
// Add braces to the formatter parameters.
829+
formatter = formatIterator.bind(null, braces);
840830
} else if (isSetIterator(value)) {
841831
keys = getKeys(value, ctx.showHidden);
842832
braces = getIteratorBraces('Set', tag);
843-
formatter = formatIterator;
833+
// Add braces to the formatter parameters.
834+
formatter = formatIterator.bind(null, braces);
844835
} else {
845836
noIterator = true;
846837
}
@@ -918,36 +909,20 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
918909
formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
919910
} else if (isModuleNamespaceObject(value)) {
920911
braces[0] = `[${tag}] {`;
921-
formatter = formatNamespaceObject;
912+
// Special handle keys for namespace objects.
913+
formatter = formatNamespaceObject.bind(null, keys);
922914
} else if (isBoxedPrimitive(value)) {
923915
base = getBoxedBase(value, ctx, keys, constructor, tag);
924916
if (keys.length === 0 && protoProps === undefined) {
925917
return base;
926918
}
927919
} else {
928-
// The input prototype got manipulated. Special handle these. We have to
929-
// rebuild the information so we are able to display everything.
930-
if (constructor === null) {
931-
const specialIterator = noPrototypeIterator(ctx, value, recurseTimes);
932-
if (specialIterator) {
933-
return specialIterator;
934-
}
935-
}
936-
if (isMapIterator(value)) {
937-
braces = getIteratorBraces('Map', tag);
938-
formatter = formatIterator;
939-
} else if (isSetIterator(value)) {
940-
braces = getIteratorBraces('Set', tag);
941-
formatter = formatIterator;
942-
// Handle other regular objects again.
943-
} else {
944-
if (keys.length === 0 && protoProps === undefined) {
945-
if (isExternal(value))
946-
return ctx.stylize('[External]', 'special');
947-
return `${getCtxStyle(value, constructor, tag)}{}`;
948-
}
949-
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
920+
if (keys.length === 0 && protoProps === undefined) {
921+
if (isExternal(value))
922+
return ctx.stylize('[External]', 'special');
923+
return `${getCtxStyle(value, constructor, tag)}{}`;
950924
}
925+
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
951926
}
952927
}
953928

@@ -964,7 +939,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
964939
let output;
965940
const indentationLvl = ctx.indentationLvl;
966941
try {
967-
output = formatter(ctx, value, recurseTimes, keys, braces);
942+
output = formatter(ctx, value, recurseTimes);
968943
for (i = 0; i < keys.length; i++) {
969944
output.push(
970945
formatProperty(ctx, value, recurseTimes, keys[i], extrasType));
@@ -1322,7 +1297,7 @@ function formatPrimitive(fn, value, ctx) {
13221297
return fn(SymbolPrototypeToString(value), 'symbol');
13231298
}
13241299

1325-
function formatNamespaceObject(ctx, value, recurseTimes, keys) {
1300+
function formatNamespaceObject(keys, ctx, value, recurseTimes) {
13261301
const output = new Array(keys.length);
13271302
for (let i = 0; i < keys.length; i++) {
13281303
try {
@@ -1424,7 +1399,7 @@ function formatArray(ctx, value, recurseTimes) {
14241399
return output;
14251400
}
14261401

1427-
function formatTypedArray(ctx, value, recurseTimes) {
1402+
function formatTypedArray(value, ctx, ignored, recurseTimes) {
14281403
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), value.length);
14291404
const remaining = value.length - maxLength;
14301405
const output = new Array(maxLength);
@@ -1455,7 +1430,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
14551430
return output;
14561431
}
14571432

1458-
function formatSet(ctx, value, recurseTimes) {
1433+
function formatSet(value, size, ctx, ignored, recurseTimes) {
14591434
const output = [];
14601435
ctx.indentationLvl += 2;
14611436
for (const v of value) {
@@ -1466,11 +1441,11 @@ function formatSet(ctx, value, recurseTimes) {
14661441
// arrays. For consistency's sake, do the same for `size`, even though this
14671442
// property isn't selected by ObjectGetOwnPropertyNames().
14681443
if (ctx.showHidden)
1469-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1444+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14701445
return output;
14711446
}
14721447

1473-
function formatMap(ctx, value, recurseTimes) {
1448+
function formatMap(value, size, ctx, ignored, recurseTimes) {
14741449
const output = [];
14751450
ctx.indentationLvl += 2;
14761451
for (const [k, v] of value) {
@@ -1480,7 +1455,7 @@ function formatMap(ctx, value, recurseTimes) {
14801455
ctx.indentationLvl -= 2;
14811456
// See comment in formatSet
14821457
if (ctx.showHidden)
1483-
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
1458+
output.push(`[size]: ${ctx.stylize(`${size}`, 'number')}`);
14841459
return output;
14851460
}
14861461

@@ -1558,7 +1533,7 @@ function formatWeakMap(ctx, value, recurseTimes) {
15581533
return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
15591534
}
15601535

1561-
function formatIterator(ctx, value, recurseTimes, keys, braces) {
1536+
function formatIterator(braces, ctx, value, recurseTimes) {
15621537
const [entries, isKeyValue] = previewEntries(value, true);
15631538
if (isKeyValue) {
15641539
// Mark entry iterators as such.
@@ -1593,7 +1568,7 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc) {
15931568
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
15941569
{ value: value[key], enumerable: true };
15951570
if (desc.value !== undefined) {
1596-
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
1571+
const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
15971572
ctx.indentationLvl += diff;
15981573
str = formatValue(ctx, desc.value, recurseTimes);
15991574
if (diff === 3) {

test/parallel/test-util-inspect.js

+13
Original file line numberDiff line numberDiff line change
@@ -2220,6 +2220,19 @@ assert.strictEqual(
22202220
configurable: true
22212221
});
22222222
assert.strictEqual(util.inspect(obj), '[Set: null prototype] { 1, 2 }');
2223+
Object.defineProperty(obj, Symbol.iterator, {
2224+
value: true,
2225+
configurable: true
2226+
});
2227+
Object.defineProperty(obj, 'size', {
2228+
value: NaN,
2229+
configurable: true,
2230+
enumerable: true
2231+
});
2232+
assert.strictEqual(
2233+
util.inspect(obj),
2234+
'[Set: null prototype] { 1, 2, size: NaN }'
2235+
);
22232236
}
22242237

22252238
// Check the getter option.

0 commit comments

Comments
 (0)