diff --git a/lib/util.js b/lib/util.js index 09e60a76366b39..ec9651ec97c495 100644 --- a/lib/util.js +++ b/lib/util.js @@ -32,11 +32,16 @@ const { TextDecoder, TextEncoder } = require('internal/encoding'); const { isBuffer } = require('buffer').Buffer; const { + getOwnNonIndexProperties, getPromiseDetails, getProxyDetails, kPending, kRejected, - previewEntries + previewEntries, + propertyFilter: { + ALL_PROPERTIES, + ONLY_ENUMERABLE + } } = process.binding('util'); const { internalBinding } = require('internal/bootstrap/loaders'); @@ -46,6 +51,7 @@ const { isAnyArrayBuffer, isArrayBuffer, isArgumentsObject, + isBoxedPrimitive, isDataView, isExternal, isMap, @@ -61,7 +67,6 @@ const { isStringObject, isNumberObject, isBooleanObject, - isSymbolObject, isBigIntObject, isUint8Array, isUint8ClampedArray, @@ -97,6 +102,10 @@ const inspectDefaultOptions = Object.seal({ compact: true }); +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + const ReflectApply = Reflect.apply; // This function is borrowed from the function with the same name on V8 Extras' @@ -122,6 +131,7 @@ const stringValueOf = uncurryThis(String.prototype.valueOf); const setValues = uncurryThis(Set.prototype.values); const mapEntries = uncurryThis(Map.prototype.entries); const dateGetTime = uncurryThis(Date.prototype.getTime); +const hasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty); let CIRCULAR_ERROR_MESSAGE; let internalDeepEqual; @@ -425,10 +435,15 @@ function stylizeWithColor(str, styleType) { return str; } -function stylizeNoColor(str, styleType) { +function stylizeNoColor(str) { return str; } +// Return a new empty array to push in the results of the default formatter. +function getEmptyFormatArray() { + return []; +} + function getConstructorName(obj) { while (obj) { const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); @@ -461,6 +476,56 @@ function getPrefix(constructor, tag, fallback) { return ''; } +const getBoxedValue = formatPrimitive.bind(null, stylizeNoColor); + +// Look up the keys of the object. +function getKeys(value, showHidden) { + let keys; + const symbols = Object.getOwnPropertySymbols(value); + if (showHidden) { + keys = Object.getOwnPropertyNames(value); + if (symbols.length !== 0) + keys.push(...symbols); + } else { + // This might throw if `value` is a Module Namespace Object from an + // unevaluated module, but we don't want to perform the actual type + // check because it's expensive. + // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 + // and modify this logic as needed. + try { + keys = Object.keys(value); + } catch (err) { + if (types.isNativeError(err) && + err.name === 'ReferenceError' && + types.isModuleNamespaceObject(value)) { + keys = Object.getOwnPropertyNames(value); + } else { + throw err; + } + } + if (symbols.length !== 0) { + keys.push(...symbols.filter((key) => propertyIsEnumerable(value, key))); + } + } + return keys; +} + +function formatProxy(ctx, proxy, recurseTimes) { + if (recurseTimes != null) { + if (recurseTimes < 0) + return ctx.stylize('Proxy [Array]', 'special'); + recurseTimes -= 1; + } + ctx.indentationLvl += 2; + const res = [ + formatValue(ctx, proxy[0], recurseTimes), + formatValue(ctx, proxy[1], recurseTimes) + ]; + ctx.indentationLvl -= 2; + const str = reduceToSingleString(ctx, res, '', ['[', ']']); + return `Proxy ${str}`; +} + function findTypedConstructor(value) { for (const [check, clazz] of [ [isUint8Array, Uint8Array], @@ -481,8 +546,6 @@ function findTypedConstructor(value) { } } -const getBoxedValue = formatPrimitive.bind(null, stylizeNoColor); - function noPrototypeIterator(ctx, value, recurseTimes) { let newVal; // TODO: Create a Subclass in case there's no prototype and show @@ -521,19 +584,7 @@ function formatValue(ctx, value, recurseTimes) { if (ctx.showProxy) { const proxy = getProxyDetails(value); if (proxy !== undefined) { - if (recurseTimes != null) { - if (recurseTimes < 0) - return ctx.stylize('Proxy [Array]', 'special'); - recurseTimes -= 1; - } - ctx.indentationLvl += 2; - const res = [ - formatValue(ctx, proxy[0], recurseTimes), - formatValue(ctx, proxy[1], recurseTimes) - ]; - ctx.indentationLvl -= 2; - const str = reduceToSingleString(ctx, res, '', ['[', ']']); - return `Proxy ${str}`; + return formatProxy(ctx, proxy, recurseTimes); } } @@ -574,78 +625,65 @@ function formatValue(ctx, value, recurseTimes) { if (ctx.seen.indexOf(value) !== -1) return ctx.stylize('[Circular]', 'special'); - let keys; - let symbols = Object.getOwnPropertySymbols(value); - - // Look up the keys of the object. - if (ctx.showHidden) { - keys = Object.getOwnPropertyNames(value); - } else { - // This might throw if `value` is a Module Namespace Object from an - // unevaluated module, but we don't want to perform the actual type - // check because it's expensive. - // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 - // and modify this logic as needed. - try { - keys = Object.keys(value); - } catch (err) { - if (types.isNativeError(err) && - err.name === 'ReferenceError' && - types.isModuleNamespaceObject(value)) { - keys = Object.getOwnPropertyNames(value); - } else { - throw err; - } - } - - if (symbols.length !== 0) - symbols = symbols.filter((key) => propertyIsEnumerable(value, key)); - } + return formatRaw(ctx, value, recurseTimes); +} - const keyLength = keys.length + symbols.length; +function formatRaw(ctx, value, recurseTimes) { + let keys; const constructor = getConstructorName(value); let tag = value[Symbol.toStringTag]; if (typeof tag !== 'string') tag = ''; let base = ''; - let formatter = formatObject; + let formatter = getEmptyFormatArray; let braces; let noIterator = true; - let extra; let i = 0; + let skip = false; + const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE; + + let extrasType = kObjectType; // Iterators and the rest are split to reduce checks if (value[Symbol.iterator]) { noIterator = false; if (Array.isArray(value)) { + keys = getOwnNonIndexProperties(value, filter); // Only set the constructor for non ordinary ("Array [...]") arrays. const prefix = getPrefix(constructor, tag); braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']']; - if (value.length === 0 && keyLength === 0) + if (value.length === 0 && keys.length === 0) return `${braces[0]}]`; + extrasType = kArrayExtrasType; formatter = formatArray; } else if (isSet(value)) { + keys = getKeys(value, ctx.showHidden); const prefix = getPrefix(constructor, tag); - if (value.size === 0 && keyLength === 0) + if (value.size === 0 && keys.length === 0) return `${prefix}{}`; braces = [`${prefix}{`, '}']; formatter = formatSet; } else if (isMap(value)) { + keys = getKeys(value, ctx.showHidden); const prefix = getPrefix(constructor, tag); - if (value.size === 0 && keyLength === 0) + if (value.size === 0 && keys.length === 0) return `${prefix}{}`; braces = [`${prefix}{`, '}']; formatter = formatMap; } else if (isTypedArray(value)) { + keys = getOwnNonIndexProperties(value, filter); braces = [`${getPrefix(constructor, tag)}[`, ']']; - if (value.length === 0 && keyLength === 0 && !ctx.showHidden) + if (value.length === 0 && keys.length === 0 && !ctx.showHidden) return `${braces[0]}]`; formatter = formatTypedArray; + extrasType = kArrayExtrasType; } else if (isMapIterator(value)) { + keys = getKeys(value, ctx.showHidden); braces = [`[${tag}] {`, '}']; formatter = formatMapIterator; } else if (isSetIterator(value)) { + keys = getKeys(value, ctx.showHidden); braces = [`[${tag}] {`, '}']; formatter = formatSetIterator; } else { @@ -653,34 +691,35 @@ function formatValue(ctx, value, recurseTimes) { } } if (noIterator) { + keys = getKeys(value, ctx.showHidden); braces = ['{', '}']; if (constructor === 'Object') { if (isArgumentsObject(value)) { - if (keyLength === 0) + if (keys.length === 0) return '[Arguments] {}'; braces[0] = '[Arguments] {'; } else if (tag !== '') { braces[0] = `${getPrefix(constructor, tag)}{`; - if (keyLength === 0) { + if (keys.length === 0) { return `${braces[0]}}`; } - } else if (keyLength === 0) { + } else if (keys.length === 0) { return '{}'; } } else if (typeof value === 'function') { const type = constructor || tag || 'Function'; const name = `${type}${value.name ? `: ${value.name}` : ''}`; - if (keyLength === 0) + if (keys.length === 0) return ctx.stylize(`[${name}]`, 'special'); base = `[${name}]`; } else if (isRegExp(value)) { // Make RegExps say that they are RegExps - if (keyLength === 0 || recurseTimes < 0) + if (keys.length === 0 || recurseTimes < 0) return ctx.stylize(regExpToString(value), 'regexp'); base = `${regExpToString(value)}`; } else if (isDate(value)) { // Make dates with properties first say the date - if (keyLength === 0) { + if (keys.length === 0) { if (Number.isNaN(dateGetTime(value))) return ctx.stylize(String(value), 'date'); return ctx.stylize(dateToISOString(value), 'date'); @@ -699,7 +738,7 @@ function formatValue(ctx, value, recurseTimes) { const indentation = ' '.repeat(ctx.indentationLvl); base = formatError(value).replace(/\n/g, `\n${indentation}`); } - if (keyLength === 0) + if (keys.length === 0) return base; if (ctx.compact === false && stackStart !== -1) { @@ -707,14 +746,14 @@ function formatValue(ctx, value, recurseTimes) { base = `[${base.slice(0, stackStart)}]`; } } else if (isAnyArrayBuffer(value)) { - // Fast path for ArrayBuffer and SharedArrayBuffer. - // Can't do the same for DataView because it has a non-primitive - // .buffer property that we need to recurse for. let prefix = getPrefix(constructor, tag); if (prefix === '') { prefix = isArrayBuffer(value) ? 'ArrayBuffer ' : 'SharedArrayBuffer '; } - if (keyLength === 0) + // Fast path for ArrayBuffer and SharedArrayBuffer. + // Can't do the same for DataView because it has a non-primitive + // .buffer property that we need to recurse for. + if (keys.length === 0) return prefix + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; braces[0] = `${prefix}{`; @@ -728,50 +767,42 @@ function formatValue(ctx, value, recurseTimes) { formatter = formatPromise; } else if (isWeakSet(value)) { braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`; - if (ctx.showHidden) { - formatter = formatWeakSet; - } else { - extra = ctx.stylize('[items unknown]', 'special'); - } + formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection; } else if (isWeakMap(value)) { braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`; - if (ctx.showHidden) { - formatter = formatWeakMap; - } else { - extra = ctx.stylize('[items unknown]', 'special'); - } + formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection; } else if (types.isModuleNamespaceObject(value)) { braces[0] = `[${tag}] {`; formatter = formatNamespaceObject; - } else if (isNumberObject(value)) { - base = `[Number: ${getBoxedValue(numberValueOf(value))}]`; - if (keyLength === 0) - return ctx.stylize(base, 'number'); - } else if (isBooleanObject(value)) { - base = `[Boolean: ${getBoxedValue(booleanValueOf(value))}]`; - if (keyLength === 0) - return ctx.stylize(base, 'boolean'); - } else if (isBigIntObject(value)) { - base = `[BigInt: ${getBoxedValue(bigIntValueOf(value))}]`; - if (keyLength === 0) - return ctx.stylize(base, 'bigint'); - } else if (isSymbolObject(value)) { - base = `[Symbol: ${getBoxedValue(symbolValueOf(value))}]`; - if (keyLength === 0) - return ctx.stylize(base, 'symbol'); - } else if (isStringObject(value)) { - const raw = stringValueOf(value); - base = `[String: ${getBoxedValue(raw, ctx)}]`; - if (keyLength === raw.length) - return ctx.stylize(base, 'string'); - // For boxed Strings, we have to remove the 0-n indexed entries, - // since they just noisy up the output and are redundant - // Make boxed primitive Strings look like such - keys = keys.slice(value.length); - braces = ['{', '}']; - // The input prototype got manipulated. Special handle these. - // We have to rebuild the information so we are able to display everything. + skip = true; + } else if (isBoxedPrimitive(value)) { + let type; + if (isNumberObject(value)) { + base = `[Number: ${getBoxedValue(numberValueOf(value))}]`; + type = 'number'; + } else if (isStringObject(value)) { + base = `[String: ${getBoxedValue(stringValueOf(value), ctx)}]`; + type = 'string'; + // For boxed Strings, we have to remove the 0-n indexed entries, + // since they just noisy up the output and are redundant + // Make boxed primitive Strings look like such + keys = keys.slice(value.length); + } else if (isBooleanObject(value)) { + base = `[Boolean: ${getBoxedValue(booleanValueOf(value))}]`; + type = 'boolean'; + } else if (isBigIntObject(value)) { + base = `[BigInt: ${getBoxedValue(bigIntValueOf(value))}]`; + type = 'bigint'; + } else { + base = `[Symbol: ${getBoxedValue(symbolValueOf(value))}]`; + type = 'symbol'; + } + if (keys.length === 0) { + return ctx.stylize(base, type); + } } else { + // The input prototype got manipulated. Special handle these. We have to + // rebuild the information so we are able to display everything. const specialIterator = noPrototypeIterator(ctx, value, recurseTimes); if (specialIterator) { return specialIterator; @@ -783,7 +814,7 @@ function formatValue(ctx, value, recurseTimes) { braces = [`[${tag || 'Set Iterator'}] {`, '}']; formatter = formatSetIterator; // Handle other regular objects again. - } else if (keyLength === 0) { + } else if (keys.length === 0) { if (isExternal(value)) return ctx.stylize('[External]', 'special'); return `${getPrefix(constructor, tag)}{}`; @@ -801,36 +832,34 @@ function formatValue(ctx, value, recurseTimes) { ctx.seen.push(value); let output; - // This corresponds to a depth of at least 333 and likely 500. - if (ctx.indentationLvl < 1000) { + try { output = formatter(ctx, value, recurseTimes, keys); - } else { - try { - output = formatter(ctx, value, recurseTimes, keys); - } catch (err) { - if (errors.isStackOverflowError(err)) { - ctx.seen.pop(); - return ctx.stylize( - `[${constructor || tag || 'Object'}: Inspection interrupted ` + - 'prematurely. Maximum call stack size exceeded.]', - 'special' - ); + if (skip === false) { + for (i = 0; i < keys.length; i++) { + output.push( + formatProperty(ctx, value, recurseTimes, keys[i], extrasType)); } - throw err; } + } catch (err) { + return handleMaxCallStackSize(ctx, err, constructor, tag); } - if (extra !== undefined) - output.unshift(extra); - - for (i = 0; i < symbols.length; i++) { - output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0)); - } - ctx.seen.pop(); return reduceToSingleString(ctx, output, base, braces); } +function handleMaxCallStackSize(ctx, err, constructor, tag) { + if (errors.isStackOverflowError(err)) { + ctx.seen.pop(); + return ctx.stylize( + `[${constructor || tag || 'Object'}: Inspection interrupted ` + + 'prematurely. Maximum call stack size exceeded.]', + 'special' + ); + } + throw err; +} + function formatNumber(fn, value) { // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. if (Object.is(value, -0)) @@ -891,20 +920,13 @@ function formatError(value) { return value.stack || errorToString(value); } -function formatObject(ctx, value, recurseTimes, keys) { - const len = keys.length; - const output = new Array(len); - for (var i = 0; i < len; i++) - output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0); - return output; -} - function formatNamespaceObject(ctx, value, recurseTimes, keys) { const len = keys.length; const output = new Array(len); for (var i = 0; i < len; i++) { try { - output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0); + output[i] = formatProperty(ctx, value, recurseTimes, keys[i], + kObjectType); } catch (err) { if (!(types.isNativeError(err) && err.name === 'ReferenceError')) { throw err; @@ -913,7 +935,7 @@ function formatNamespaceObject(ctx, value, recurseTimes, keys) { // line breaks are always correct. Otherwise it is very difficult to keep // this aligned, even though this is a hacky way of dealing with this. const tmp = { [keys[i]]: '' }; - output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], 0); + output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType); const pos = output[i].lastIndexOf(' '); // We have to find the last whitespace and have to replace that value as // it will be visualized as a regular string. @@ -925,91 +947,67 @@ function formatNamespaceObject(ctx, value, recurseTimes, keys) { } // The array is sparse and/or has extra keys -function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { - const output = []; - const keyLen = keys.length; - let i = 0; - for (const key of keys) { - if (output.length === maxLength) - break; - const index = +key; +function formatSpecialArray(ctx, value, recurseTimes, maxLength, output, i) { + const keys = Object.keys(value); + let index = i; + for (; i < keys.length && output.length < maxLength; i++) { + const key = keys[i]; + const tmp = +key; // Arrays can only have up to 2^32 - 1 entries - if (index > 2 ** 32 - 2) + if (tmp > 2 ** 32 - 2) { break; - if (`${i}` !== key) { - if (!numberRegExp.test(key)) + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { break; - const emptyItems = index - i; + } + const emptyItems = tmp - index; const ending = emptyItems > 1 ? 's' : ''; const message = `<${emptyItems} empty item${ending}>`; output.push(ctx.stylize(message, 'undefined')); - i = index; - if (output.length === maxLength) + index = tmp; + if (output.length === maxLength) { break; + } } - output.push(formatProperty(ctx, value, recurseTimes, key, 1)); - i++; - } - if (i < valLen && output.length !== maxLength) { - const len = valLen - i; - const ending = len > 1 ? 's' : ''; - const message = `<${len} empty item${ending}>`; - output.push(ctx.stylize(message, 'undefined')); - i = valLen; - if (keyLen === 0) - return output; - } - const remaining = valLen - i; - if (remaining > 0) { - output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); + output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType)); + index++; } - if (ctx.showHidden && keys[keyLen - 1] === 'length') { - // No extra keys - output.push(formatProperty(ctx, value, recurseTimes, 'length', 2)); - } else if (valLen === 0 || - keyLen > valLen && keys[valLen - 1] === `${valLen - 1}`) { - // The array is not sparse - for (i = valLen; i < keyLen; i++) - output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)); - } else if (keys[keyLen - 1] !== `${valLen - 1}`) { - const extra = []; - // Only handle special keys - let key; - for (i = keys.length - 1; i >= 0; i--) { - key = keys[i]; - if (numberRegExp.test(key) && +key < 2 ** 32 - 1) - break; - extra.push(formatProperty(ctx, value, recurseTimes, key, 2)); + const remaining = value.length - index; + if (output.length !== maxLength) { + if (remaining > 0) { + const ending = remaining > 1 ? 's' : ''; + const message = `<${remaining} empty item${ending}>`; + output.push(ctx.stylize(message, 'undefined')); } - for (i = extra.length - 1; i >= 0; i--) - output.push(extra[i]); + } else if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); } return output; } -function formatArray(ctx, value, recurseTimes, keys) { - const len = Math.min(Math.max(0, ctx.maxArrayLength), value.length); - const hidden = ctx.showHidden ? 1 : 0; +function formatArray(ctx, value, recurseTimes) { const valLen = value.length; - const keyLen = keys.length - hidden; - if (keyLen !== valLen || keys[keyLen - 1] !== `${valLen - 1}`) - return formatSpecialArray(ctx, value, recurseTimes, keys, len, valLen); + const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); const remaining = valLen - len; - const output = new Array(len + (remaining > 0 ? 1 : 0) + hidden); - for (var i = 0; i < len; i++) - output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 1); + const output = []; + for (var i = 0; i < len; i++) { + // Special handle sparse arrays. + if (!hasOwnProperty(value, i)) { + return formatSpecialArray(ctx, value, recurseTimes, len, output, i); + } + output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType)); + } if (remaining > 0) - output[i++] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; - if (ctx.showHidden === true) - output[i] = formatProperty(ctx, value, recurseTimes, 'length', 2); + output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); return output; } -function formatTypedArray(ctx, value, recurseTimes, keys) { +function formatTypedArray(ctx, value, recurseTimes) { const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), value.length); const remaining = value.length - maxLength; - const output = new Array(maxLength + (remaining > 0 ? 1 : 0)); + const output = new Array(maxLength); for (var i = 0; i < maxLength; ++i) output[i] = formatNumber(ctx.stylize, value[i]); if (remaining > 0) @@ -1029,52 +1027,39 @@ function formatTypedArray(ctx, value, recurseTimes, keys) { } ctx.indentationLvl -= 2; } - // TypedArrays cannot have holes. Therefore it is safe to assume that all - // extra keys are indexed after value.length. - for (i = value.length; i < keys.length; i++) { - output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)); - } return output; } -function formatSet(ctx, value, recurseTimes, keys) { - const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)); - let i = 0; +function formatSet(ctx, value, recurseTimes) { + const output = []; ctx.indentationLvl += 2; for (const v of value) { - output[i++] = formatValue(ctx, v, recurseTimes); + 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 Object.getOwnPropertyNames(). if (ctx.showHidden) - output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`; - for (var n = 0; n < keys.length; n++) { - output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0); - } + output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`); return output; } -function formatMap(ctx, value, recurseTimes, keys) { - const output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)); - let i = 0; +function formatMap(ctx, value, recurseTimes) { + const output = []; ctx.indentationLvl += 2; for (const [k, v] of value) { - output[i++] = `${formatValue(ctx, k, recurseTimes)} => ` + - formatValue(ctx, v, recurseTimes); + output.push(`${formatValue(ctx, k, recurseTimes)} => ` + + formatValue(ctx, v, recurseTimes)); } ctx.indentationLvl -= 2; // See comment in formatSet if (ctx.showHidden) - output[i++] = `[size]: ${ctx.stylize(`${value.size}`, 'number')}`; - for (var n = 0; n < keys.length; n++) { - output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0); - } + output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`); return output; } -function formatSetIterInner(ctx, value, recurseTimes, keys, entries, state) { +function formatSetIterInner(ctx, recurseTimes, entries, state) { const maxArrayLength = Math.max(ctx.maxArrayLength, 0); const maxLength = Math.min(maxArrayLength, entries.length); let output = new Array(maxLength); @@ -1092,12 +1077,10 @@ function formatSetIterInner(ctx, value, recurseTimes, keys, entries, state) { if (remaining > 0) { output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); } - for (i = 0; i < keys.length; i++) - output.push(formatProperty(ctx, value, recurseTimes, keys[i], 0)); return output; } -function formatMapIterInner(ctx, value, recurseTimes, keys, entries, state) { +function formatMapIterInner(ctx, recurseTimes, entries, state) { const maxArrayLength = Math.max(ctx.maxArrayLength, 0); // Entries exist as [key1, val1, key2, val2, ...] const len = entries.length / 2; @@ -1128,37 +1111,38 @@ function formatMapIterInner(ctx, value, recurseTimes, keys, entries, state) { if (remaining > 0) { output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); } - for (i = 0; i < keys.length; i++) - output.push(formatProperty(ctx, value, recurseTimes, keys[i], 0)); return output; } -function formatWeakSet(ctx, value, recurseTimes, keys) { +function formatWeakCollection(ctx) { + return [ctx.stylize('[items unknown]', 'special')]; +} + +function formatWeakSet(ctx, value, recurseTimes) { const entries = previewEntries(value); - return formatSetIterInner(ctx, value, recurseTimes, keys, entries, kWeak); + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); } -function formatWeakMap(ctx, value, recurseTimes, keys) { +function formatWeakMap(ctx, value, recurseTimes) { const entries = previewEntries(value); - return formatMapIterInner(ctx, value, recurseTimes, keys, entries, kWeak); + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); } -function formatSetIterator(ctx, value, recurseTimes, keys) { +function formatSetIterator(ctx, value, recurseTimes) { const entries = previewEntries(value); - return formatSetIterInner(ctx, value, recurseTimes, keys, entries, kIterator); + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); } -function formatMapIterator(ctx, value, recurseTimes, keys) { +function formatMapIterator(ctx, value, recurseTimes) { const [entries, isKeyValue] = previewEntries(value, true); if (isKeyValue) { - return formatMapIterInner( - ctx, value, recurseTimes, keys, entries, kMapEntries); + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); } - return formatSetIterInner(ctx, value, recurseTimes, keys, entries, kIterator); + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); } -function formatPromise(ctx, value, recurseTimes, keys) { +function formatPromise(ctx, value, recurseTimes) { let output; const [state, result] = getPromiseDetails(value); if (state === kPending) { @@ -1175,19 +1159,16 @@ function formatPromise(ctx, value, recurseTimes, keys) { str ]; } - for (var n = 0; n < keys.length; n++) { - output.push(formatProperty(ctx, value, recurseTimes, keys[n], 0)); - } return output; } -function formatProperty(ctx, value, recurseTimes, key, array) { +function formatProperty(ctx, value, recurseTimes, key, type) { let name, str; let extra = ' '; const desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key], enumerable: true }; if (desc.value !== undefined) { - const diff = array !== 0 || ctx.compact === false ? 2 : 3; + const diff = (type !== kObjectType || ctx.compact === false) ? 2 : 3; ctx.indentationLvl += diff; str = formatValue(ctx, desc.value, recurseTimes); if (diff === 3) { @@ -1208,7 +1189,7 @@ function formatProperty(ctx, value, recurseTimes, key, array) { } else { str = ctx.stylize('undefined', 'undefined'); } - if (array === 1) { + if (type === kArrayType) { return str; } if (typeof key === 'symbol') { diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index df1348e95abf4d..e7585a86146b6b 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -922,7 +922,7 @@ if (typeof Symbol !== 'undefined') { const set = new Set(['foo']); set.bar = 42; assert.strictEqual( - util.inspect(set, true), + util.inspect(set, { showHidden: true }), "Set { 'foo', [size]: 1, bar: 42 }" ); }