diff --git a/benchmark/util/format.js b/benchmark/util/format.js index 042b8a93ccfcf2..2a4a20097c72d3 100644 --- a/benchmark/util/format.js +++ b/benchmark/util/format.js @@ -16,7 +16,7 @@ const inputs = { }; const bench = common.createBenchmark(main, { - n: [4e6], + n: [1e5], type: Object.keys(inputs) }); diff --git a/benchmark/util/inspect-array.js b/benchmark/util/inspect-array.js index 8b3c54aeb942fe..4fd73785f789d1 100644 --- a/benchmark/util/inspect-array.js +++ b/benchmark/util/inspect-array.js @@ -4,8 +4,8 @@ const common = require('../common'); const util = require('util'); const bench = common.createBenchmark(main, { - n: [1e3], - len: [1e5], + n: [5e2], + len: [1e2, 1e5], type: [ 'denseArray', 'sparseArray', diff --git a/benchmark/util/inspect-proxy.js b/benchmark/util/inspect-proxy.js index 5427df9952c250..3c82d50ac3c0ce 100644 --- a/benchmark/util/inspect-proxy.js +++ b/benchmark/util/inspect-proxy.js @@ -3,7 +3,7 @@ const util = require('util'); const common = require('../common.js'); -const bench = common.createBenchmark(main, { n: [1e6] }); +const bench = common.createBenchmark(main, { n: [2e4] }); function main({ n }) { const proxyA = new Proxy({}, { get: () => {} }); diff --git a/benchmark/util/inspect.js b/benchmark/util/inspect.js index 35253ac96682eb..9ba3020fd0e55b 100644 --- a/benchmark/util/inspect.js +++ b/benchmark/util/inspect.js @@ -9,7 +9,7 @@ const opts = { none: undefined }; const bench = common.createBenchmark(main, { - n: [2e6], + n: [2e4], method: [ 'Object', 'Object_empty', @@ -81,7 +81,7 @@ function main({ method, n, option }) { benchmark(n, new Error('error'), options); break; case 'Array': - benchmark(n, Array(20).fill().map((_, i) => i), options); + benchmark(n, Array(50).fill().map((_, i) => i), options); break; case 'TypedArray': obj = new Uint8Array(Array(50).fill().map((_, i) => i)); diff --git a/benchmark/util/normalize-encoding.js b/benchmark/util/normalize-encoding.js index 73cbadff72b8ca..47c8bce277cf3d 100644 --- a/benchmark/util/normalize-encoding.js +++ b/benchmark/util/normalize-encoding.js @@ -5,26 +5,23 @@ const assert = require('assert'); const groupedInputs = { group_common: ['undefined', 'utf8', 'utf-8', 'base64', - 'binary', 'latin1', 'ucs-2'], - group_upper: ['UTF-8', 'UTF8', 'UCS2', 'UTF-16LE', - 'UTF16LE', 'BASE64', 'UCS-2'], - group_uncommon: ['foo', '1', 'false', 'undefined', '[]', '{}'], + 'binary', 'latin1', 'ucs2'], + group_upper: ['UTF-8', 'UTF8', 'UCS2', + 'UTF16LE', 'BASE64', 'UCS2'], + group_uncommon: ['foo'], group_misc: ['', 'utf16le', 'hex', 'HEX', 'BINARY'] }; const inputs = [ - '', - 'utf8', 'utf-8', 'UTF-8', - 'UTF8', 'Utf8', 'uTf-8', 'utF-8', - 'ucs2', 'UCS2', 'UcS2', - 'ucs-2', 'UCS-2', 'UcS-2', - 'utf16le', 'utf-16le', 'UTF-16LE', 'UTF16LE', + '', 'utf8', 'utf-8', 'UTF-8', 'UTF8', 'Utf8', + 'ucs2', 'UCS2', 'utf16le', 'UTF16LE', 'binary', 'BINARY', 'latin1', 'base64', 'BASE64', - 'hex', 'HEX', 'foo', '1', 'false', 'undefined', '[]', '{}']; + 'hex', 'HEX', 'foo', 'undefined' +]; const bench = common.createBenchmark(main, { input: inputs.concat(Object.keys(groupedInputs)), - n: [1e7] + n: [1e5] }, { flags: '--expose-internals' }); @@ -39,16 +36,8 @@ function getInput(input) { return groupedInputs.group_uncommon; case 'group_misc': return groupedInputs.group_misc; - case '1': - return [1]; - case 'false': - return [false]; case 'undefined': return [undefined]; - case '[]': - return [[]]; - case '{}': - return [{}]; default: return [input]; } diff --git a/benchmark/util/priority-queue.js b/benchmark/util/priority-queue.js index 51a696439a2864..9cff7cbbacba84 100644 --- a/benchmark/util/priority-queue.js +++ b/benchmark/util/priority-queue.js @@ -3,7 +3,7 @@ const common = require('../common'); const bench = common.createBenchmark(main, { - n: [1e6] + n: [1e5] }, { flags: ['--expose-internals'] }); function main({ n, type }) { diff --git a/benchmark/util/splice-one.js b/benchmark/util/splice-one.js index 5c2a39f6d72a11..4ca7c8564d3fc4 100644 --- a/benchmark/util/splice-one.js +++ b/benchmark/util/splice-one.js @@ -3,7 +3,7 @@ const common = require('../common'); const bench = common.createBenchmark(main, { - n: [1e7], + n: [1e5], pos: ['start', 'middle', 'end'], size: [10, 100, 500], }, { flags: ['--expose-internals'] }); diff --git a/benchmark/util/type-check.js b/benchmark/util/type-check.js index d9ba7bc209de07..d9acdaedf5a57f 100644 --- a/benchmark/util/type-check.js +++ b/benchmark/util/type-check.js @@ -29,7 +29,7 @@ const bench = common.createBenchmark(main, { type: Object.keys(args), version: ['native', 'js'], argument: ['true', 'false-primitive', 'false-object'], - n: [5e6] + n: [1e5] }, { flags: ['--expose-internals'] }); diff --git a/lib/util.js b/lib/util.js index f0b7285fe01035..35bd9ec46f5b6f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -33,11 +33,16 @@ const { isBuffer } = require('buffer').Buffer; const { internalBinding } = require('internal/bootstrap/loaders'); const { + getOwnNonIndexProperties, getPromiseDetails, getProxyDetails, kPending, kRejected, - previewEntries + previewEntries, + propertyFilter: { + ALL_PROPERTIES, + ONLY_ENUMERABLE + } } = internalBinding('util'); const types = internalBinding('types'); @@ -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; @@ -430,8 +440,6 @@ Object.defineProperty(inspect, 'defaultOptions', { if (options === null || typeof options !== 'object') { throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); } - // TODO(BridgeAR): Add input validation and make sure `defaultOptions` are - // not configurable. return _extend(inspectDefaultOptions, options); } }); @@ -477,10 +485,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'); @@ -513,6 +526,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], @@ -533,8 +596,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 @@ -573,19 +634,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); } } @@ -616,78 +665,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 { @@ -695,34 +731,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'); @@ -741,7 +778,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) { @@ -749,14 +786,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}{`; @@ -770,50 +807,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('', '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('', '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; @@ -825,7 +854,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)}{}`; @@ -843,36 +872,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)) @@ -937,20 +964,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; @@ -959,7 +979,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. @@ -971,91 +991,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' : ''}`); - } - 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)); + output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType)); + index++; + } + 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); const elementFormatter = value.length > 0 && typeof value[0] === 'number' ? formatNumber : formatBigInt; @@ -1078,52 +1074,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); @@ -1141,12 +1124,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; @@ -1177,37 +1158,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('', '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) { @@ -1224,19 +1206,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) { @@ -1257,7 +1236,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-benchmark-util.js b/test/parallel/test-benchmark-util.js index 838e51daac26b4..97b02bbdeed5cd 100644 --- a/test/parallel/test-benchmark-util.js +++ b/test/parallel/test-benchmark-util.js @@ -13,5 +13,6 @@ runBenchmark('util', 'pos=start', 'size=1', 'type=', + 'len=1', 'version=native'], { NODEJS_BENCHMARK_ZERO_ALLOWED: 1 }); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index b5ccdf019a5226..0994f262996cf5 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -872,7 +872,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 }" ); }