Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: add Set and Map size to the inspect output #30225

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/api/util.md
Original file line number Diff line number Diff line change
@@ -576,7 +576,7 @@ console.log(util.inspect(o, { compact: true, depth: 5, breakLength: 80 }));
// 'test',
// 'foo' ] ],
// 4 ],
// b: Map { 'za' => 1, 'zb' => 'test' } }
// b: Map(2) { 'za' => 1, 'zb' => 'test' } }

// Setting `compact` to false changes the output to be more reader friendly.
console.log(util.inspect(o, { compact: false, depth: 5, breakLength: 80 }));
@@ -597,7 +597,7 @@ console.log(util.inspect(o, { compact: false, depth: 5, breakLength: 80 }));
// ],
// 4
// ],
// b: Map {
// b: Map(2) {
// 'za' => 1,
// 'zb' => 'test'
// }
@@ -639,9 +639,9 @@ const o1 = {
c: new Set([2, 3, 1])
};
console.log(inspect(o1, { sorted: true }));
// { a: '`a` comes before `b`', b: [ 2, 3, 1 ], c: Set { 1, 2, 3 } }
// { a: '`a` comes before `b`', b: [ 2, 3, 1 ], c: Set(3) { 1, 2, 3 } }
console.log(inspect(o1, { sorted: (a, b) => b.localeCompare(a) }));
// { c: Set { 3, 2, 1 }, b: [ 2, 3, 1 ], a: '`a` comes before `b`' }
// { c: Set(3) { 3, 2, 1 }, b: [ 2, 3, 1 ], a: '`a` comes before `b`' }

const o2 = {
c: new Set([2, 1, 3]),
163 changes: 65 additions & 98 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ const {
DatePrototypeToString,
ErrorPrototypeToString,
JSONStringify,
MapPrototype,
MapPrototypeEntries,
MathFloor,
MathMax,
@@ -21,10 +22,8 @@ const {
NumberPrototypeValueOf,
ObjectAssign,
ObjectCreate,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptors,
ObjectGetOwnPropertyNames,
ObjectGetOwnPropertySymbols,
ObjectGetPrototypeOf,
@@ -34,6 +33,7 @@ const {
ObjectPrototypePropertyIsEnumerable,
ObjectSeal,
RegExpPrototypeToString,
SetPrototype,
SetPrototypeValues,
StringPrototypeValueOf,
SymbolPrototypeToString,
@@ -113,6 +113,11 @@ const assert = require('internal/assert');

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

const setSizeGetter = uncurryThis(
ObjectGetOwnPropertyDescriptor(SetPrototype, 'size').get);
const mapSizeGetter = uncurryThis(
ObjectGetOwnPropertyDescriptor(MapPrototype, 'size').get);

let hexSlice;

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

let lazyNullPrototypeCache;
// Creates a subclass and name
// the constructor as `${clazz} : null prototype`
function clazzWithNullPrototype(clazz, name) {
if (lazyNullPrototypeCache === undefined) {
lazyNullPrototypeCache = new Map();
} else {
const cachedClass = lazyNullPrototypeCache.get(clazz);
if (cachedClass !== undefined) {
return cachedClass;
}
}
class NullPrototype extends clazz {
get [SymbolToStringTag]() {
return '';
}
}
ObjectDefineProperty(NullPrototype.prototype.constructor, 'name',
{ value: `[${name}: null prototype]` });
lazyNullPrototypeCache.set(clazz, NullPrototype);
return NullPrototype;
}

function noPrototypeIterator(ctx, value, recurseTimes) {
let newVal;
if (isSet(value)) {
const clazz = clazzWithNullPrototype(Set, 'Set');
newVal = new clazz(SetPrototypeValues(value));
} else if (isMap(value)) {
const clazz = clazzWithNullPrototype(Map, 'Map');
newVal = new clazz(MapPrototypeEntries(value));
} else if (ArrayIsArray(value)) {
const clazz = clazzWithNullPrototype(Array, 'Array');
newVal = new clazz(value.length);
} else if (isTypedArray(value)) {
const constructor = findTypedConstructor(value);
const clazz = clazzWithNullPrototype(constructor, constructor.name);
newVal = new clazz(value);
}
if (newVal !== undefined) {
ObjectDefineProperties(newVal, ObjectGetOwnPropertyDescriptors(value));
return formatRaw(ctx, newVal, recurseTimes);
}
}

// Note: using `formatValue` directly requires the indentation level to be
// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
// value afterwards again.
@@ -798,7 +758,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
let extrasType = kObjectType;

// Iterators and the rest are split to reduce checks.
if (value[SymbolIterator]) {
// We have to check all values in case the constructor is set to null.
// Otherwise it would not possible to identify all types properly.
if (value[SymbolIterator] || constructor === null) {
noIterator = false;
if (ArrayIsArray(value)) {
keys = getOwnNonIndexProperties(value, filter);
@@ -810,37 +772,66 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
extrasType = kArrayExtrasType;
formatter = formatArray;
} else if (isSet(value)) {
const size = setSizeGetter(value);
keys = getKeys(value, ctx.showHidden);
const prefix = getPrefix(constructor, tag, 'Set');
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
let prefix = '';
if (constructor !== null) {
if (constructor === tag)
tag = '';
prefix = getPrefix(`${constructor}(${size})`, tag, '');
formatter = formatSet.bind(null, value, size);
} else {
prefix = getPrefix(constructor, tag, `Set(${size})`);
formatter = formatSet.bind(null, SetPrototypeValues(value), size);
}
if (size === 0 && keys.length === 0 && protoProps === undefined)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatSet;
} else if (isMap(value)) {
const size = mapSizeGetter(value);
keys = getKeys(value, ctx.showHidden);
const prefix = getPrefix(constructor, tag, 'Map');
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
let prefix = '';
if (constructor !== null) {
if (constructor === tag)
tag = '';
prefix = getPrefix(`${constructor}(${size})`, tag, '');
formatter = formatMap.bind(null, value, size);
} else {
prefix = getPrefix(constructor, tag, `Map(${size})`);
formatter = formatMap.bind(null, MapPrototypeEntries(value), size);
}
if (size === 0 && keys.length === 0 && protoProps === undefined)
return `${prefix}{}`;
braces = [`${prefix}{`, '}'];
formatter = formatMap;
} else if (isTypedArray(value)) {
keys = getOwnNonIndexProperties(value, filter);
const prefix = constructor !== null ?
getPrefix(constructor, tag) :
getPrefix(constructor, tag, findTypedConstructor(value).name);
let bound = value;
let prefix = '';
if (constructor === null) {
const constr = findTypedConstructor(value);
prefix = getPrefix(constructor, tag, constr.name);
// Reconstruct the array information.
bound = new constr(value);
} else {
prefix = getPrefix(constructor, tag);
}
braces = [`${prefix}[`, ']'];
if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
return `${braces[0]}]`;
formatter = formatTypedArray;
// Special handle the value. The original value is required below. The
// bound function is required to reconstruct missing information.
formatter = formatTypedArray.bind(null, bound);
extrasType = kArrayExtrasType;
} else if (isMapIterator(value)) {
keys = getKeys(value, ctx.showHidden);
braces = getIteratorBraces('Map', tag);
formatter = formatIterator;
// Add braces to the formatter parameters.
formatter = formatIterator.bind(null, braces);
} else if (isSetIterator(value)) {
keys = getKeys(value, ctx.showHidden);
braces = getIteratorBraces('Set', tag);
formatter = formatIterator;
// Add braces to the formatter parameters.
formatter = formatIterator.bind(null, braces);
} else {
noIterator = true;
}
@@ -918,36 +909,20 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
} else if (isModuleNamespaceObject(value)) {
braces[0] = `[${tag}] {`;
formatter = formatNamespaceObject;
// Special handle keys for namespace objects.
formatter = formatNamespaceObject.bind(null, keys);
} else if (isBoxedPrimitive(value)) {
base = getBoxedBase(value, ctx, keys, constructor, tag);
if (keys.length === 0 && protoProps === undefined) {
return base;
}
} else {
// The input prototype got manipulated. Special handle these. We have to
// rebuild the information so we are able to display everything.
if (constructor === null) {
const specialIterator = noPrototypeIterator(ctx, value, recurseTimes);
if (specialIterator) {
return specialIterator;
}
}
if (isMapIterator(value)) {
braces = getIteratorBraces('Map', tag);
formatter = formatIterator;
} else if (isSetIterator(value)) {
braces = getIteratorBraces('Set', tag);
formatter = formatIterator;
// Handle other regular objects again.
} else {
if (keys.length === 0 && protoProps === undefined) {
if (isExternal(value))
return ctx.stylize('[External]', 'special');
return `${getCtxStyle(value, constructor, tag)}{}`;
}
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
if (keys.length === 0 && protoProps === undefined) {
if (isExternal(value))
return ctx.stylize('[External]', 'special');
return `${getCtxStyle(value, constructor, tag)}{}`;
}
braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
}
}

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

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

function formatTypedArray(ctx, value, recurseTimes) {
function formatTypedArray(value, ctx, ignored, recurseTimes) {
const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), value.length);
const remaining = value.length - maxLength;
const output = new Array(maxLength);
@@ -1455,32 +1430,24 @@ function formatTypedArray(ctx, value, recurseTimes) {
return output;
}

function formatSet(ctx, value, recurseTimes) {
function formatSet(value, size, ctx, ignored, recurseTimes) {
const output = [];
ctx.indentationLvl += 2;
for (const v of value) {
output.push(formatValue(ctx, v, recurseTimes));
}
ctx.indentationLvl -= 2;
// With `showHidden`, `length` will display as a hidden property for
// arrays. For consistency's sake, do the same for `size`, even though this
// property isn't selected by ObjectGetOwnPropertyNames().
if (ctx.showHidden)
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
return output;
}

function formatMap(ctx, value, recurseTimes) {
function formatMap(value, size, ctx, ignored, recurseTimes) {
const output = [];
ctx.indentationLvl += 2;
for (const [k, v] of value) {
output.push(`${formatValue(ctx, k, recurseTimes)} => ` +
formatValue(ctx, v, recurseTimes));
}
ctx.indentationLvl -= 2;
// See comment in formatSet
if (ctx.showHidden)
output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`);
return output;
}

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

function formatIterator(ctx, value, recurseTimes, keys, braces) {
function formatIterator(braces, ctx, value, recurseTimes) {
const [entries, isKeyValue] = previewEntries(value, true);
if (isKeyValue) {
// Mark entry iterators as such.
@@ -1593,7 +1560,7 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc) {
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
{ value: value[key], enumerable: true };
if (desc.value !== undefined) {
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
ctx.indentationLvl += diff;
str = formatValue(ctx, desc.value, recurseTimes);
if (diff === 3) {
2 changes: 1 addition & 1 deletion test/parallel/test-assert-deep.js
Original file line number Diff line number Diff line change
@@ -524,7 +524,7 @@ assertNotDeepOrStrict(
{
code: 'ERR_ASSERTION',
message: `${defaultMsgStartFull}\n\n` +
" Map {\n+ 1 => 1\n- 1 => '1'\n }"
" Map(1) {\n+ 1 => 1\n- 1 => '1'\n }"
}
);
}
Loading