From 1e964d70f63790cfc0e09583de2739a53ac1bd43 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 13 Sep 2018 17:39:10 +0200 Subject: [PATCH] util: move inspect in separate file The inspect function became very big and it's better to handle this in a separate file. --- lib/internal/util/inspect.js | 1155 ++++++++++++++++++++++++++++++++++ lib/util.js | 1130 +-------------------------------- node.gyp | 1 + 3 files changed, 1157 insertions(+), 1129 deletions(-) create mode 100644 lib/internal/util/inspect.js diff --git a/lib/internal/util/inspect.js b/lib/internal/util/inspect.js new file mode 100644 index 00000000000000..dc66c5f9dfedd3 --- /dev/null +++ b/lib/internal/util/inspect.js @@ -0,0 +1,1155 @@ +'use strict'; + +const { internalBinding } = require('internal/bootstrap/loaders'); + +const { + getOwnNonIndexProperties, + getPromiseDetails, + getProxyDetails, + kPending, + kRejected, + previewEntries, + propertyFilter: { + ALL_PROPERTIES, + ONLY_ENUMERABLE + } +} = internalBinding('util'); + +const { + customInspectSymbol, + isError, + join, + removeColors +} = require('internal/util'); + +const { + codes: { + ERR_INVALID_ARG_TYPE + }, + isStackOverflowError +} = require('internal/errors'); + +const types = internalBinding('types'); +Object.assign(types, require('internal/util/types')); +const { + isAnyArrayBuffer, + isArrayBuffer, + isArgumentsObject, + isBoxedPrimitive, + isDataView, + isExternal, + isMap, + isMapIterator, + isPromise, + isSet, + isSetIterator, + isWeakMap, + isWeakSet, + isRegExp, + isDate, + isTypedArray, + isStringObject, + isNumberObject, + isBooleanObject, + isBigIntObject, + isUint8Array, + isUint8ClampedArray, + isUint16Array, + isUint32Array, + isInt8Array, + isInt16Array, + isInt32Array, + isFloat32Array, + isFloat64Array, + isBigInt64Array, + isBigUint64Array +} = types; + +const ReflectApply = Reflect.apply; + +// This function is borrowed from the function with the same name on V8 Extras' +// `utils` object. V8 implements Reflect.apply very efficiently in conjunction +// with the spread syntax, such that no additional special case is needed for +// function calls w/o arguments. +// Refs: https://github.com/v8/v8/blob/d6ead37d265d7215cf9c5f768f279e21bd170212/src/js/prologue.js#L152-L156 +function uncurryThis(func) { + return (thisArg, ...args) => ReflectApply(func, thisArg, args); +} + +const propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable); +const regExpToString = uncurryThis(RegExp.prototype.toString); +const dateToISOString = uncurryThis(Date.prototype.toISOString); +const errorToString = uncurryThis(Error.prototype.toString); + +const bigIntValueOf = uncurryThis(BigInt.prototype.valueOf); +const booleanValueOf = uncurryThis(Boolean.prototype.valueOf); +const numberValueOf = uncurryThis(Number.prototype.valueOf); +const symbolValueOf = uncurryThis(Symbol.prototype.valueOf); +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); + +const inspectDefaultOptions = Object.seal({ + showHidden: false, + depth: 2, + colors: false, + customInspect: true, + showProxy: false, + maxArrayLength: 100, + breakLength: 60, + compact: true, + sorted: false +}); + +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + +/* eslint-disable no-control-regex */ +const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/; +const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g; +const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c]/; +const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c]/g; +/* eslint-enable no-control-regex */ + +const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; +const numberRegExp = /^(0|[1-9][0-9]*)$/; + +const readableRegExps = {}; + +const kMinLineLength = 16; + +// Constants to map the iterator state. +const kWeak = 0; +const kIterator = 1; +const kMapEntries = 2; + +// Escaped special characters. Use empty strings to fill up unused entries. +const meta = [ + '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', + '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t', + '\\n', '\\u000b', '\\f', '\\r', '\\u000e', + '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013', + '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', + '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d', + '\\u001e', '\\u001f', '', '', '', + '', '', '', '', "\\'", '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '\\\\' +]; + +/** + * Echos the value of any input. Tries to print the value out + * in the best way possible given the different types. + * + * @param {any} value The value to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* Legacy: value, showHidden, depth, colors */ +function inspect(value, opts) { + // Default options + const ctx = { + budget: {}, + indentationLvl: 0, + seen: [], + stylize: stylizeNoColor, + showHidden: inspectDefaultOptions.showHidden, + depth: inspectDefaultOptions.depth, + colors: inspectDefaultOptions.colors, + customInspect: inspectDefaultOptions.customInspect, + showProxy: inspectDefaultOptions.showProxy, + // TODO(BridgeAR): Deprecate `maxArrayLength` and replace it with + // `maxEntries`. + maxArrayLength: inspectDefaultOptions.maxArrayLength, + breakLength: inspectDefaultOptions.breakLength, + compact: inspectDefaultOptions.compact, + sorted: inspectDefaultOptions.sorted + }; + if (arguments.length > 1) { + // Legacy... + if (arguments.length > 2) { + if (arguments[2] !== undefined) { + ctx.depth = arguments[2]; + } + if (arguments.length > 3 && arguments[3] !== undefined) { + ctx.colors = arguments[3]; + } + } + // Set user-specified options + if (typeof opts === 'boolean') { + ctx.showHidden = opts; + } else if (opts) { + const optKeys = Object.keys(opts); + for (var i = 0; i < optKeys.length; i++) { + ctx[optKeys[i]] = opts[optKeys[i]]; + } + } + } + if (ctx.colors) ctx.stylize = stylizeWithColor; + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + return formatValue(ctx, value, ctx.depth); +} +inspect.custom = customInspectSymbol; + +Object.defineProperty(inspect, 'defaultOptions', { + get() { + return inspectDefaultOptions; + }, + set(options) { + if (options === null || typeof options !== 'object') { + throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); + } + return Object.assign(inspectDefaultOptions, options); + } +}); + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = Object.assign(Object.create(null), { + 'bold': [1, 22], + 'italic': [3, 23], + 'underline': [4, 24], + 'inverse': [7, 27], + 'white': [37, 39], + 'grey': [90, 39], + 'black': [30, 39], + 'blue': [34, 39], + 'cyan': [36, 39], + 'green': [32, 39], + 'magenta': [35, 39], + 'red': [31, 39], + 'yellow': [33, 39] +}); + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = Object.assign(Object.create(null), { + 'special': 'cyan', + 'number': 'yellow', + 'bigint': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'symbol': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}); + +function addQuotes(str, quotes) { + if (quotes === -1) { + return `"${str}"`; + } + if (quotes === -2) { + return `\`${str}\``; + } + return `'${str}'`; +} + +const escapeFn = (str) => meta[str.charCodeAt(0)]; + +// Escape control characters, single quotes and the backslash. +// This is similar to JSON stringify escaping. +function strEscape(str) { + let escapeTest = strEscapeSequencesRegExp; + let escapeReplace = strEscapeSequencesReplacer; + let singleQuote = 39; + + // Check for double quotes. If not present, do not escape single quotes and + // instead wrap the text in double quotes. If double quotes exist, check for + // backticks. If they do not exist, use those as fallback instead of the + // double quotes. + if (str.indexOf("'") !== -1) { + // This invalidates the charCode and therefore can not be matched for + // anymore. + if (str.indexOf('"') === -1) { + singleQuote = -1; + } else if (str.indexOf('`') === -1 && str.indexOf('${') === -1) { + singleQuote = -2; + } + if (singleQuote !== 39) { + escapeTest = strEscapeSequencesRegExpSingle; + escapeReplace = strEscapeSequencesReplacerSingle; + } + } + + // Some magic numbers that worked out fine while benchmarking with v8 6.0 + if (str.length < 5000 && !escapeTest.test(str)) + return addQuotes(str, singleQuote); + if (str.length > 100) { + str = str.replace(escapeReplace, escapeFn); + return addQuotes(str, singleQuote); + } + + let result = ''; + let last = 0; + for (var i = 0; i < str.length; i++) { + const point = str.charCodeAt(i); + if (point === singleQuote || point === 92 || point < 32) { + if (last === i) { + result += meta[point]; + } else { + result += `${str.slice(last, i)}${meta[point]}`; + } + last = i + 1; + } + } + if (last === 0) { + result = str; + } else if (last !== i) { + result += str.slice(last); + } + return addQuotes(result, singleQuote); +} + +function stylizeWithColor(str, styleType) { + const style = inspect.styles[styleType]; + if (style !== undefined) { + const color = inspect.colors[style]; + return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; + } + return str; +} + +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'); + if (descriptor !== undefined && + typeof descriptor.value === 'function' && + descriptor.value.name !== '') { + return descriptor.value.name; + } + + obj = Object.getPrototypeOf(obj); + } + + return ''; +} + +function getPrefix(constructor, tag, fallback) { + if (constructor !== '') { + if (tag !== '' && constructor !== tag) { + return `${constructor} [${tag}] `; + } + return `${constructor} `; + } + + if (tag !== '') + return `[${tag}] `; + + if (fallback !== undefined) + return `${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], + [isUint8ClampedArray, Uint8ClampedArray], + [isUint16Array, Uint16Array], + [isUint32Array, Uint32Array], + [isInt8Array, Int8Array], + [isInt16Array, Int16Array], + [isInt32Array, Int32Array], + [isFloat32Array, Float32Array], + [isFloat64Array, Float64Array], + [isBigInt64Array, BigInt64Array], + [isBigUint64Array, BigUint64Array] + ]) { + if (check(value)) { + return clazz; + } + } +} + +function noPrototypeIterator(ctx, value, recurseTimes) { + let newVal; + // TODO: Create a Subclass in case there's no prototype and show + // `null-prototype`. + if (isSet(value)) { + const clazz = Object.getPrototypeOf(value) || Set; + newVal = new clazz(setValues(value)); + } else if (isMap(value)) { + const clazz = Object.getPrototypeOf(value) || Map; + newVal = new clazz(mapEntries(value)); + } else if (Array.isArray(value)) { + const clazz = Object.getPrototypeOf(value) || Array; + newVal = new clazz(value.length || 0); + } else if (isTypedArray(value)) { + const clazz = findTypedConstructor(value) || Uint8Array; + newVal = new clazz(value); + } + if (newVal) { + Object.defineProperties(newVal, Object.getOwnPropertyDescriptors(value)); + return formatValue(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. +function formatValue(ctx, value, recurseTimes) { + // Primitive types cannot have properties. + if (typeof value !== 'object' && typeof value !== 'function') { + return formatPrimitive(ctx.stylize, value, ctx); + } + if (value === null) { + return ctx.stylize('null', 'null'); + } + + if (ctx.stop !== undefined) { + const name = getConstructorName(value) || value[Symbol.toStringTag]; + return ctx.stylize(`[${name || 'Object'}]`, 'special'); + } + + if (ctx.showProxy) { + const proxy = getProxyDetails(value); + if (proxy !== undefined) { + return formatProxy(ctx, proxy, recurseTimes); + } + } + + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it. + if (ctx.customInspect) { + const maybeCustom = value[customInspectSymbol]; + if (typeof maybeCustom === 'function' && + // Filter out the util module, its inspect function is special. + maybeCustom !== inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + const ret = maybeCustom.call(value, recurseTimes, ctx); + + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== value) { + if (typeof ret !== 'string') { + return formatValue(ctx, ret, recurseTimes); + } + return ret; + } + } + } + + // Using an array here is actually better for the average case than using + // a Set. `seen` will only check for the depth and will never grow too large. + if (ctx.seen.indexOf(value) !== -1) + return ctx.stylize('[Circular]', 'special'); + + return formatRaw(ctx, value, recurseTimes); +} + +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 = getEmptyFormatArray; + let braces; + let noIterator = true; + 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 && 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 && 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 && 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 && 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 { + noIterator = true; + } + } + if (noIterator) { + keys = getKeys(value, ctx.showHidden); + braces = ['{', '}']; + if (constructor === 'Object') { + if (isArgumentsObject(value)) { + if (keys.length === 0) + return '[Arguments] {}'; + braces[0] = '[Arguments] {'; + } else if (tag !== '') { + braces[0] = `${getPrefix(constructor, tag)}{`; + if (keys.length === 0) { + return `${braces[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 (keys.length === 0) + return ctx.stylize(`[${name}]`, 'special'); + base = `[${name}]`; + } else if (isRegExp(value)) { + // Make RegExps say that they are RegExps + 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 (keys.length === 0) { + if (Number.isNaN(dateGetTime(value))) + return ctx.stylize(String(value), 'date'); + return ctx.stylize(dateToISOString(value), 'date'); + } + base = dateToISOString(value); + } else if (isError(value)) { + // Make error with message first say the error. + base = formatError(value); + // Wrap the error in brackets in case it has no stack trace. + const stackStart = base.indexOf('\n at'); + if (stackStart === -1) { + base = `[${base}]`; + } + // The message and the stack have to be indented as well! + if (ctx.indentationLvl !== 0) { + const indentation = ' '.repeat(ctx.indentationLvl); + base = formatError(value).replace(/\n/g, `\n${indentation}`); + } + if (keys.length === 0) + return base; + + if (ctx.compact === false && stackStart !== -1) { + braces[0] += `${base.slice(stackStart)}`; + base = `[${base.slice(0, stackStart)}]`; + } + } else if (isAnyArrayBuffer(value)) { + let prefix = getPrefix(constructor, tag); + if (prefix === '') { + prefix = isArrayBuffer(value) ? 'ArrayBuffer ' : 'SharedArrayBuffer '; + } + // 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}{`; + keys.unshift('byteLength'); + } else if (isDataView(value)) { + braces[0] = `${getPrefix(constructor, tag, 'DataView')}{`; + // .buffer goes last, it's not a primitive like the others. + keys.unshift('byteLength', 'byteOffset', 'buffer'); + } else if (isPromise(value)) { + braces[0] = `${getPrefix(constructor, tag, 'Promise')}{`; + formatter = formatPromise; + } else if (isWeakSet(value)) { + braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`; + formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection; + } else if (isWeakMap(value)) { + braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`; + formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection; + } else if (types.isModuleNamespaceObject(value)) { + braces[0] = `[${tag}] {`; + formatter = formatNamespaceObject; + 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; + } + if (isMapIterator(value)) { + braces = [`[${tag || 'Map Iterator'}] {`, '}']; + formatter = formatMapIterator; + } else if (isSetIterator(value)) { + braces = [`[${tag || 'Set Iterator'}] {`, '}']; + formatter = formatSetIterator; + // Handle other regular objects again. + } else if (keys.length === 0) { + if (isExternal(value)) + return ctx.stylize('[External]', 'special'); + return `${getPrefix(constructor, tag)}{}`; + } else { + braces[0] = `${getPrefix(constructor, tag)}{`; + } + } + } + + if (recurseTimes != null) { + if (recurseTimes < 0) + return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special'); + recurseTimes -= 1; + } + + ctx.seen.push(value); + let output; + const indentationLvl = ctx.indentationLvl; + try { + output = formatter(ctx, value, recurseTimes, keys); + if (skip === false) { + for (i = 0; i < keys.length; i++) { + output.push( + formatProperty(ctx, value, recurseTimes, keys[i], extrasType)); + } + } + } catch (err) { + return handleMaxCallStackSize(ctx, err, constructor, tag, indentationLvl); + } + ctx.seen.pop(); + + if (ctx.sorted) { + const comparator = ctx.sorted === true ? undefined : ctx.sorted; + if (extrasType === kObjectType) { + output = output.sort(comparator); + } else if (keys.length > 1) { + const sorted = output.slice(output.length - keys.length).sort(comparator); + output.splice(output.length - keys.length, keys.length, ...sorted); + } + } + + const res = reduceToSingleString(ctx, output, base, braces); + const budget = ctx.budget[ctx.indentationLvl] || 0; + const newLength = budget + res.length; + ctx.budget[ctx.indentationLvl] = newLength; + // If any indentationLvl exceeds this limit, limit further inspecting to the + // minimum. Otherwise the recursive algorithm might continue inspecting the + // object even though the maximum string size (~2 ** 28 on 32 bit systems and + // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at + // exactly 2 ** 27 but a bit higher. This depends on the object shape. + // This limit also makes sure that huge objects don't block the event loop + // significantly. + if (newLength > 2 ** 27) { + ctx.stop = true; + } + return res; +} + +function handleMaxCallStackSize(ctx, err, constructor, tag, indentationLvl) { + if (isStackOverflowError(err)) { + ctx.seen.pop(); + ctx.indentationLvl = indentationLvl; + 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)) + return fn('-0', 'number'); + return fn(`${value}`, 'number'); +} + +function formatBigInt(fn, value) { + return fn(`${value}n`, 'bigint'); +} + +function formatPrimitive(fn, value, ctx) { + if (typeof value === 'string') { + if (ctx.compact === false && + ctx.indentationLvl + value.length > ctx.breakLength && + value.length > kMinLineLength) { + // eslint-disable-next-line max-len + const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, kMinLineLength); + // eslint-disable-next-line max-len + const averageLineLength = Math.ceil(value.length / Math.ceil(value.length / minLineLength)); + const divisor = Math.max(averageLineLength, kMinLineLength); + let res = ''; + if (readableRegExps[divisor] === undefined) { + // Build a new RegExp that naturally breaks text into multiple lines. + // + // Rules + // 1. Greedy match all text up the max line length that ends with a + // whitespace or the end of the string. + // 2. If none matches, non-greedy match any text up to a whitespace or + // the end of the string. + // + // eslint-disable-next-line max-len, node-core/no-unescaped-regexp-dot + readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm'); + } + const matches = value.match(readableRegExps[divisor]); + if (matches.length > 1) { + const indent = ' '.repeat(ctx.indentationLvl); + res += `${fn(strEscape(matches[0]), 'string')} +\n`; + for (var i = 1; i < matches.length - 1; i++) { + res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`; + } + res += `${indent} ${fn(strEscape(matches[i]), 'string')}`; + return res; + } + } + return fn(strEscape(value), 'string'); + } + if (typeof value === 'number') + return formatNumber(fn, value); + // eslint-disable-next-line valid-typeof + if (typeof value === 'bigint') + return formatBigInt(fn, value); + if (typeof value === 'boolean') + return fn(`${value}`, 'boolean'); + if (typeof value === 'undefined') + return fn('undefined', 'undefined'); + // es6 symbol primitive + return fn(value.toString(), 'symbol'); +} + +function formatError(value) { + return value.stack || errorToString(value); +} + +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], + kObjectType); + } catch (err) { + if (!(types.isNativeError(err) && err.name === 'ReferenceError')) { + throw err; + } + // Use the existing functionality. This makes sure the indentation and + // 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], 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. + output[i] = output[i].slice(0, pos + 1) + + ctx.stylize('', 'special'); + } + } + return output; +} + +// The array is sparse and/or has extra keys +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 (tmp > 2 ** 32 - 2) { + break; + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { + break; + } + const emptyItems = tmp - index; + const ending = emptyItems > 1 ? 's' : ''; + const message = `<${emptyItems} empty item${ending}>`; + output.push(ctx.stylize(message, 'undefined')); + index = tmp; + if (output.length === maxLength) { + break; + } + } + 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')); + } + } else if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); + } + return output; +} + +function formatArray(ctx, value, recurseTimes) { + const valLen = value.length; + const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); + + const remaining = valLen - len; + 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.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); + return output; +} + +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); + const elementFormatter = value.length > 0 && typeof value[0] === 'number' ? + formatNumber : + formatBigInt; + for (var i = 0; i < maxLength; ++i) + output[i] = elementFormatter(ctx.stylize, value[i]); + if (remaining > 0) { + output[i] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; + } + if (ctx.showHidden) { + // .buffer goes last, it's not a primitive like the others. + ctx.indentationLvl += 2; + for (const key of [ + 'BYTES_PER_ELEMENT', + 'length', + 'byteLength', + 'byteOffset', + 'buffer' + ]) { + const str = formatValue(ctx, value[key], recurseTimes); + output.push(`[${key}]: ${str}`); + } + ctx.indentationLvl -= 2; + } + return output; +} + +function formatSet(ctx, value, 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 Object.getOwnPropertyNames(). + if (ctx.showHidden) + output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`); + return output; +} + +function formatMap(ctx, value, 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; +} + +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); + ctx.indentationLvl += 2; + for (var i = 0; i < maxLength; i++) { + output[i] = formatValue(ctx, entries[i], recurseTimes); + } + ctx.indentationLvl -= 2; + if (state === kWeak) { + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output). + output = output.sort(); + } + const remaining = entries.length - maxLength; + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); + } + return output; +} + +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; + const remaining = len - maxArrayLength; + const maxLength = Math.min(maxArrayLength, len); + let output = new Array(maxLength); + let start = ''; + let end = ''; + let middle = ' => '; + let i = 0; + if (state === kMapEntries) { + start = '[ '; + end = ' ]'; + middle = ', '; + } + ctx.indentationLvl += 2; + for (; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${start}${formatValue(ctx, entries[pos], recurseTimes)}` + + `${middle}${formatValue(ctx, entries[pos + 1], recurseTimes)}${end}`; + } + ctx.indentationLvl -= 2; + if (state === kWeak) { + // Sort all entries to have a halfway reliable output (if more entries + // than retrieved ones exist, we can not reliably return the same output). + output = output.sort(); + } + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); + } + return output; +} + +function formatWeakCollection(ctx) { + return [ctx.stylize('', 'special')]; +} + +function formatWeakSet(ctx, value, recurseTimes) { + const entries = previewEntries(value); + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatWeakMap(ctx, value, recurseTimes) { + const entries = previewEntries(value); + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatSetIterator(ctx, value, recurseTimes) { + const entries = previewEntries(value); + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function formatMapIterator(ctx, value, recurseTimes) { + const [entries, isKeyValue] = previewEntries(value, true); + if (isKeyValue) { + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); + } + + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function formatPromise(ctx, value, recurseTimes) { + let output; + const [state, result] = getPromiseDetails(value); + if (state === kPending) { + output = [ctx.stylize('', 'special')]; + } else { + // Using `formatValue` is correct here without the need to fix the + // indentation level. + ctx.indentationLvl += 2; + const str = formatValue(ctx, result, recurseTimes); + ctx.indentationLvl -= 2; + output = [ + state === kRejected ? + `${ctx.stylize('', 'special')} ${str}` : + str + ]; + } + return output; +} + +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 = (type !== kObjectType || ctx.compact === false) ? 2 : 3; + ctx.indentationLvl += diff; + str = formatValue(ctx, desc.value, recurseTimes); + if (diff === 3) { + const len = ctx.colors ? removeColors(str).length : str.length; + if (ctx.breakLength < len) { + extra = `\n${' '.repeat(ctx.indentationLvl)}`; + } + } + ctx.indentationLvl -= diff; + } else if (desc.get !== undefined) { + if (desc.set !== undefined) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else if (desc.set !== undefined) { + str = ctx.stylize('[Setter]', 'special'); + } else { + str = ctx.stylize('undefined', 'undefined'); + } + if (type === kArrayType) { + return str; + } + if (typeof key === 'symbol') { + const tmp = key.toString().replace(strEscapeSequencesReplacer, escapeFn); + name = `[${ctx.stylize(tmp, 'symbol')}]`; + } else if (desc.enumerable === false) { + name = `[${key.replace(strEscapeSequencesReplacer, escapeFn)}]`; + } else if (keyStrRegExp.test(key)) { + name = ctx.stylize(key, 'name'); + } else { + name = ctx.stylize(strEscape(key), 'string'); + } + return `${name}:${extra}${str}`; +} + +function reduceToSingleString(ctx, output, base, braces) { + const breakLength = ctx.breakLength; + let i = 0; + if (ctx.compact === false) { + const indentation = ' '.repeat(ctx.indentationLvl); + let res = `${base ? `${base} ` : ''}${braces[0]}\n${indentation} `; + for (; i < output.length - 1; i++) { + res += `${output[i]},\n${indentation} `; + } + res += `${output[i]}\n${indentation}${braces[1]}`; + return res; + } + if (output.length * 2 <= breakLength) { + let length = 0; + for (; i < output.length && length <= breakLength; i++) { + if (ctx.colors) { + length += removeColors(output[i]).length + 1; + } else { + length += output[i].length + 1; + } + } + if (length <= breakLength) + return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` + + braces[1]; + } + // If the opening "brace" is too large, like in the case of "Set {", + // we need to force the first item to be on the next line or the + // items will not line up correctly. + const indentation = ' '.repeat(ctx.indentationLvl); + const ln = base === '' && braces[0].length === 1 ? + ' ' : `${base ? ` ${base}` : ''}\n${indentation} `; + const str = join(output, `,\n${indentation} `); + return `${braces[0]}${ln}${str} ${braces[1]}`; +} + +module.exports = { + inspect +}; diff --git a/lib/util.js b/lib/util.js index d1a483cf17bff7..ae2295533df3ef 100644 --- a/lib/util.js +++ b/lib/util.js @@ -22,6 +22,7 @@ 'use strict'; const errors = require('internal/errors'); +const { inspect } = require('internal/util/inspect'); const { ERR_FALSY_VALUE_REJECTION, ERR_INVALID_ARG_TYPE, @@ -32,213 +33,24 @@ const { TextDecoder, TextEncoder } = require('internal/encoding'); const { isBuffer } = require('buffer').Buffer; const { internalBinding } = require('internal/bootstrap/loaders'); -const { - getOwnNonIndexProperties, - getPromiseDetails, - getProxyDetails, - kPending, - kRejected, - previewEntries, - propertyFilter: { - ALL_PROPERTIES, - ONLY_ENUMERABLE - } -} = internalBinding('util'); const types = internalBinding('types'); Object.assign(types, require('internal/util/types')); const { - isAnyArrayBuffer, - isArrayBuffer, - isArgumentsObject, - isBoxedPrimitive, - isDataView, - isExternal, - isMap, - isMapIterator, - isPromise, - isSet, - isSetIterator, - isWeakMap, - isWeakSet, isRegExp, isDate, - isTypedArray, - isStringObject, - isNumberObject, - isBooleanObject, - isBigIntObject, - isUint8Array, - isUint8ClampedArray, - isUint16Array, - isUint32Array, - isInt8Array, - isInt16Array, - isInt32Array, - isFloat32Array, - isFloat64Array, - isBigInt64Array, - isBigUint64Array } = types; const { - customInspectSymbol, deprecate, getSystemErrorName: internalErrorName, isError, promisify, - join, - removeColors } = require('internal/util'); -const inspectDefaultOptions = Object.seal({ - showHidden: false, - depth: 2, - colors: false, - customInspect: true, - showProxy: false, - maxArrayLength: 100, - breakLength: 60, - compact: true, - sorted: false -}); - -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' -// `utils` object. V8 implements Reflect.apply very efficiently in conjunction -// with the spread syntax, such that no additional special case is needed for -// function calls w/o arguments. -// Refs: https://github.com/v8/v8/blob/d6ead37d265d7215cf9c5f768f279e21bd170212/src/js/prologue.js#L152-L156 -function uncurryThis(func) { - return (thisArg, ...args) => ReflectApply(func, thisArg, args); -} - -const propertyIsEnumerable = uncurryThis(Object.prototype.propertyIsEnumerable); -const regExpToString = uncurryThis(RegExp.prototype.toString); -const dateToISOString = uncurryThis(Date.prototype.toISOString); -const errorToString = uncurryThis(Error.prototype.toString); - -const bigIntValueOf = uncurryThis(BigInt.prototype.valueOf); -const booleanValueOf = uncurryThis(Boolean.prototype.valueOf); -const numberValueOf = uncurryThis(Number.prototype.valueOf); -const symbolValueOf = uncurryThis(Symbol.prototype.valueOf); -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; -/* eslint-disable no-control-regex */ -const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/; -const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g; -const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c]/; -const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c]/g; - -/* eslint-enable no-control-regex */ - -const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; -const numberRegExp = /^(0|[1-9][0-9]*)$/; - -const readableRegExps = {}; - -const MIN_LINE_LENGTH = 16; - -// Escaped special characters. Use empty strings to fill up unused entries. -const meta = [ - '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', - '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t', - '\\n', '\\u000b', '\\f', '\\r', '\\u000e', - '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013', - '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', - '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d', - '\\u001e', '\\u001f', '', '', '', - '', '', '', '', "\\'", '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '\\\\' -]; -// Constants to map the iterator state. -const kWeak = 0; -const kIterator = 1; -const kMapEntries = 2; - -function addQuotes(str, quotes) { - if (quotes === -1) { - return `"${str}"`; - } - if (quotes === -2) { - return `\`${str}\``; - } - return `'${str}'`; -} - -const escapeFn = (str) => meta[str.charCodeAt(0)]; - -// Escape control characters, single quotes and the backslash. -// This is similar to JSON stringify escaping. -function strEscape(str) { - let escapeTest = strEscapeSequencesRegExp; - let escapeReplace = strEscapeSequencesReplacer; - let singleQuote = 39; - - // Check for double quotes. If not present, do not escape single quotes and - // instead wrap the text in double quotes. If double quotes exist, check for - // backticks. If they do not exist, use those as fallback instead of the - // double quotes. - if (str.indexOf("'") !== -1) { - // This invalidates the charCode and therefore can not be matched for - // anymore. - if (str.indexOf('"') === -1) { - singleQuote = -1; - } else if (str.indexOf('`') === -1 && str.indexOf('${') === -1) { - singleQuote = -2; - } - if (singleQuote !== 39) { - escapeTest = strEscapeSequencesRegExpSingle; - escapeReplace = strEscapeSequencesReplacerSingle; - } - } - - // Some magic numbers that worked out fine while benchmarking with v8 6.0 - if (str.length < 5000 && !escapeTest.test(str)) - return addQuotes(str, singleQuote); - if (str.length > 100) { - str = str.replace(escapeReplace, escapeFn); - return addQuotes(str, singleQuote); - } - - let result = ''; - let last = 0; - for (var i = 0; i < str.length; i++) { - const point = str.charCodeAt(i); - if (point === singleQuote || point === 92 || point < 32) { - if (last === i) { - result += meta[point]; - } else { - result += `${str.slice(last, i)}${meta[point]}`; - } - last = i + 1; - } - } - if (last === 0) { - result = str; - } else if (last !== i) { - result += str.slice(last); - } - return addQuotes(result, singleQuote); -} - function tryStringify(arg) { try { return JSON.stringify(arg); @@ -384,946 +196,6 @@ function debuglog(set) { return debugs[set]; } -/** - * Echos the value of any input. Tries to print the value out - * in the best way possible given the different types. - * - * @param {any} value The value to print out. - * @param {Object} opts Optional options object that alters the output. - */ -/* Legacy: value, showHidden, depth, colors */ -function inspect(value, opts) { - // Default options - const ctx = { - budget: {}, - indentationLvl: 0, - seen: [], - stylize: stylizeNoColor, - showHidden: inspectDefaultOptions.showHidden, - depth: inspectDefaultOptions.depth, - colors: inspectDefaultOptions.colors, - customInspect: inspectDefaultOptions.customInspect, - showProxy: inspectDefaultOptions.showProxy, - // TODO(BridgeAR): Deprecate `maxArrayLength` and replace it with - // `maxEntries`. - maxArrayLength: inspectDefaultOptions.maxArrayLength, - breakLength: inspectDefaultOptions.breakLength, - compact: inspectDefaultOptions.compact, - sorted: inspectDefaultOptions.sorted - }; - if (arguments.length > 1) { - // Legacy... - if (arguments.length > 2) { - if (arguments[2] !== undefined) { - ctx.depth = arguments[2]; - } - if (arguments.length > 3 && arguments[3] !== undefined) { - ctx.colors = arguments[3]; - } - } - // Set user-specified options - if (typeof opts === 'boolean') { - ctx.showHidden = opts; - } else if (opts) { - const optKeys = Object.keys(opts); - for (var i = 0; i < optKeys.length; i++) { - ctx[optKeys[i]] = opts[optKeys[i]]; - } - } - } - if (ctx.colors) ctx.stylize = stylizeWithColor; - if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; - return formatValue(ctx, value, ctx.depth); -} -inspect.custom = customInspectSymbol; - -Object.defineProperty(inspect, 'defaultOptions', { - get() { - return inspectDefaultOptions; - }, - set(options) { - if (options === null || typeof options !== 'object') { - throw new ERR_INVALID_ARG_TYPE('options', 'Object', options); - } - return _extend(inspectDefaultOptions, options); - } -}); - -// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics -inspect.colors = Object.assign(Object.create(null), { - 'bold': [1, 22], - 'italic': [3, 23], - 'underline': [4, 24], - 'inverse': [7, 27], - 'white': [37, 39], - 'grey': [90, 39], - 'black': [30, 39], - 'blue': [34, 39], - 'cyan': [36, 39], - 'green': [32, 39], - 'magenta': [35, 39], - 'red': [31, 39], - 'yellow': [33, 39] -}); - -// Don't use 'blue' not visible on cmd.exe -inspect.styles = Object.assign(Object.create(null), { - 'special': 'cyan', - 'number': 'yellow', - 'bigint': 'yellow', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'symbol': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' -}); - -function stylizeWithColor(str, styleType) { - const style = inspect.styles[styleType]; - if (style !== undefined) { - const color = inspect.colors[style]; - return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; - } - return str; -} - -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'); - if (descriptor !== undefined && - typeof descriptor.value === 'function' && - descriptor.value.name !== '') { - return descriptor.value.name; - } - - obj = Object.getPrototypeOf(obj); - } - - return ''; -} - -function getPrefix(constructor, tag, fallback) { - if (constructor !== '') { - if (tag !== '' && constructor !== tag) { - return `${constructor} [${tag}] `; - } - return `${constructor} `; - } - - if (tag !== '') - return `[${tag}] `; - - if (fallback !== undefined) - return `${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], - [isUint8ClampedArray, Uint8ClampedArray], - [isUint16Array, Uint16Array], - [isUint32Array, Uint32Array], - [isInt8Array, Int8Array], - [isInt16Array, Int16Array], - [isInt32Array, Int32Array], - [isFloat32Array, Float32Array], - [isFloat64Array, Float64Array], - [isBigInt64Array, BigInt64Array], - [isBigUint64Array, BigUint64Array] - ]) { - if (check(value)) { - return clazz; - } - } -} - -function noPrototypeIterator(ctx, value, recurseTimes) { - let newVal; - // TODO: Create a Subclass in case there's no prototype and show - // `null-prototype`. - if (isSet(value)) { - const clazz = Object.getPrototypeOf(value) || Set; - newVal = new clazz(setValues(value)); - } else if (isMap(value)) { - const clazz = Object.getPrototypeOf(value) || Map; - newVal = new clazz(mapEntries(value)); - } else if (Array.isArray(value)) { - const clazz = Object.getPrototypeOf(value) || Array; - newVal = new clazz(value.length || 0); - } else if (isTypedArray(value)) { - const clazz = findTypedConstructor(value) || Uint8Array; - newVal = new clazz(value); - } - if (newVal) { - Object.defineProperties(newVal, Object.getOwnPropertyDescriptors(value)); - return formatValue(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. -function formatValue(ctx, value, recurseTimes) { - // Primitive types cannot have properties. - if (typeof value !== 'object' && typeof value !== 'function') { - return formatPrimitive(ctx.stylize, value, ctx); - } - if (value === null) { - return ctx.stylize('null', 'null'); - } - - if (ctx.stop !== undefined) { - const name = getConstructorName(value) || value[Symbol.toStringTag]; - return ctx.stylize(`[${name || 'Object'}]`, 'special'); - } - - if (ctx.showProxy) { - const proxy = getProxyDetails(value); - if (proxy !== undefined) { - return formatProxy(ctx, proxy, recurseTimes); - } - } - - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it. - if (ctx.customInspect) { - const maybeCustom = value[customInspectSymbol]; - if (typeof maybeCustom === 'function' && - // Filter out the util module, its inspect function is special. - maybeCustom !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - const ret = maybeCustom.call(value, recurseTimes, ctx); - - // If the custom inspection method returned `this`, don't go into - // infinite recursion. - if (ret !== value) { - if (typeof ret !== 'string') { - return formatValue(ctx, ret, recurseTimes); - } - return ret; - } - } - } - - // Using an array here is actually better for the average case than using - // a Set. `seen` will only check for the depth and will never grow too large. - if (ctx.seen.indexOf(value) !== -1) - return ctx.stylize('[Circular]', 'special'); - - return formatRaw(ctx, value, recurseTimes); -} - -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 = getEmptyFormatArray; - let braces; - let noIterator = true; - 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 && 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 && 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 && 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 && 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 { - noIterator = true; - } - } - if (noIterator) { - keys = getKeys(value, ctx.showHidden); - braces = ['{', '}']; - if (constructor === 'Object') { - if (isArgumentsObject(value)) { - if (keys.length === 0) - return '[Arguments] {}'; - braces[0] = '[Arguments] {'; - } else if (tag !== '') { - braces[0] = `${getPrefix(constructor, tag)}{`; - if (keys.length === 0) { - return `${braces[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 (keys.length === 0) - return ctx.stylize(`[${name}]`, 'special'); - base = `[${name}]`; - } else if (isRegExp(value)) { - // Make RegExps say that they are RegExps - 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 (keys.length === 0) { - if (Number.isNaN(dateGetTime(value))) - return ctx.stylize(String(value), 'date'); - return ctx.stylize(dateToISOString(value), 'date'); - } - base = dateToISOString(value); - } else if (isError(value)) { - // Make error with message first say the error. - base = formatError(value); - // Wrap the error in brackets in case it has no stack trace. - const stackStart = base.indexOf('\n at'); - if (stackStart === -1) { - base = `[${base}]`; - } - // The message and the stack have to be indented as well! - if (ctx.indentationLvl !== 0) { - const indentation = ' '.repeat(ctx.indentationLvl); - base = formatError(value).replace(/\n/g, `\n${indentation}`); - } - if (keys.length === 0) - return base; - - if (ctx.compact === false && stackStart !== -1) { - braces[0] += `${base.slice(stackStart)}`; - base = `[${base.slice(0, stackStart)}]`; - } - } else if (isAnyArrayBuffer(value)) { - let prefix = getPrefix(constructor, tag); - if (prefix === '') { - prefix = isArrayBuffer(value) ? 'ArrayBuffer ' : 'SharedArrayBuffer '; - } - // 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}{`; - keys.unshift('byteLength'); - } else if (isDataView(value)) { - braces[0] = `${getPrefix(constructor, tag, 'DataView')}{`; - // .buffer goes last, it's not a primitive like the others. - keys.unshift('byteLength', 'byteOffset', 'buffer'); - } else if (isPromise(value)) { - braces[0] = `${getPrefix(constructor, tag, 'Promise')}{`; - formatter = formatPromise; - } else if (isWeakSet(value)) { - braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`; - formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection; - } else if (isWeakMap(value)) { - braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`; - formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection; - } else if (types.isModuleNamespaceObject(value)) { - braces[0] = `[${tag}] {`; - formatter = formatNamespaceObject; - 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; - } - if (isMapIterator(value)) { - braces = [`[${tag || 'Map Iterator'}] {`, '}']; - formatter = formatMapIterator; - } else if (isSetIterator(value)) { - braces = [`[${tag || 'Set Iterator'}] {`, '}']; - formatter = formatSetIterator; - // Handle other regular objects again. - } else if (keys.length === 0) { - if (isExternal(value)) - return ctx.stylize('[External]', 'special'); - return `${getPrefix(constructor, tag)}{}`; - } else { - braces[0] = `${getPrefix(constructor, tag)}{`; - } - } - } - - if (recurseTimes != null) { - if (recurseTimes < 0) - return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special'); - recurseTimes -= 1; - } - - ctx.seen.push(value); - let output; - const indentationLvl = ctx.indentationLvl; - try { - output = formatter(ctx, value, recurseTimes, keys); - if (skip === false) { - for (i = 0; i < keys.length; i++) { - output.push( - formatProperty(ctx, value, recurseTimes, keys[i], extrasType)); - } - } - } catch (err) { - return handleMaxCallStackSize(ctx, err, constructor, tag, indentationLvl); - } - ctx.seen.pop(); - - if (ctx.sorted) { - const comparator = ctx.sorted === true ? undefined : ctx.sorted; - if (extrasType === kObjectType) { - output = output.sort(comparator); - } else if (keys.length > 1) { - const sorted = output.slice(output.length - keys.length).sort(comparator); - output.splice(output.length - keys.length, keys.length, ...sorted); - } - } - - const res = reduceToSingleString(ctx, output, base, braces); - const budget = ctx.budget[ctx.indentationLvl] || 0; - const newLength = budget + res.length; - ctx.budget[ctx.indentationLvl] = newLength; - // If any indentationLvl exceeds this limit, limit further inspecting to the - // minimum. Otherwise the recursive algorithm might continue inspecting the - // object even though the maximum string size (~2 ** 28 on 32 bit systems and - // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at - // exactly 2 ** 27 but a bit higher. This depends on the object shape. - // This limit also makes sure that huge objects don't block the event loop - // significantly. - if (newLength > 2 ** 27) { - ctx.stop = true; - } - return res; -} - -function handleMaxCallStackSize(ctx, err, constructor, tag, indentationLvl) { - if (errors.isStackOverflowError(err)) { - ctx.seen.pop(); - ctx.indentationLvl = indentationLvl; - 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)) - return fn('-0', 'number'); - return fn(`${value}`, 'number'); -} - -function formatBigInt(fn, value) { - return fn(`${value}n`, 'bigint'); -} - -function formatPrimitive(fn, value, ctx) { - if (typeof value === 'string') { - if (ctx.compact === false && - ctx.indentationLvl + value.length > ctx.breakLength && - value.length > MIN_LINE_LENGTH) { - // eslint-disable-next-line max-len - const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH); - // eslint-disable-next-line max-len - const averageLineLength = Math.ceil(value.length / Math.ceil(value.length / minLineLength)); - const divisor = Math.max(averageLineLength, MIN_LINE_LENGTH); - let res = ''; - if (readableRegExps[divisor] === undefined) { - // Build a new RegExp that naturally breaks text into multiple lines. - // - // Rules - // 1. Greedy match all text up the max line length that ends with a - // whitespace or the end of the string. - // 2. If none matches, non-greedy match any text up to a whitespace or - // the end of the string. - // - // eslint-disable-next-line max-len, node-core/no-unescaped-regexp-dot - readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm'); - } - const matches = value.match(readableRegExps[divisor]); - if (matches.length > 1) { - const indent = ' '.repeat(ctx.indentationLvl); - res += `${fn(strEscape(matches[0]), 'string')} +\n`; - for (var i = 1; i < matches.length - 1; i++) { - res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`; - } - res += `${indent} ${fn(strEscape(matches[i]), 'string')}`; - return res; - } - } - return fn(strEscape(value), 'string'); - } - if (typeof value === 'number') - return formatNumber(fn, value); - // eslint-disable-next-line valid-typeof - if (typeof value === 'bigint') - return formatBigInt(fn, value); - if (typeof value === 'boolean') - return fn(`${value}`, 'boolean'); - if (typeof value === 'undefined') - return fn('undefined', 'undefined'); - // es6 symbol primitive - return fn(value.toString(), 'symbol'); -} - -function formatError(value) { - return value.stack || errorToString(value); -} - -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], - kObjectType); - } catch (err) { - if (!(types.isNativeError(err) && err.name === 'ReferenceError')) { - throw err; - } - // Use the existing functionality. This makes sure the indentation and - // 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], 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. - output[i] = output[i].slice(0, pos + 1) + - ctx.stylize('', 'special'); - } - } - return output; -} - -// The array is sparse and/or has extra keys -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 (tmp > 2 ** 32 - 2) { - break; - } - if (`${index}` !== key) { - if (!numberRegExp.test(key)) { - break; - } - const emptyItems = tmp - index; - const ending = emptyItems > 1 ? 's' : ''; - const message = `<${emptyItems} empty item${ending}>`; - output.push(ctx.stylize(message, 'undefined')); - index = tmp; - if (output.length === maxLength) { - break; - } - } - 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')); - } - } else if (remaining > 0) { - output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); - } - return output; -} - -function formatArray(ctx, value, recurseTimes) { - const valLen = value.length; - const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); - - const remaining = valLen - len; - 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.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); - return output; -} - -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); - const elementFormatter = value.length > 0 && typeof value[0] === 'number' ? - formatNumber : - formatBigInt; - for (var i = 0; i < maxLength; ++i) - output[i] = elementFormatter(ctx.stylize, value[i]); - if (remaining > 0) { - output[i] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`; - } - if (ctx.showHidden) { - // .buffer goes last, it's not a primitive like the others. - ctx.indentationLvl += 2; - for (const key of [ - 'BYTES_PER_ELEMENT', - 'length', - 'byteLength', - 'byteOffset', - 'buffer' - ]) { - const str = formatValue(ctx, value[key], recurseTimes); - output.push(`[${key}]: ${str}`); - } - ctx.indentationLvl -= 2; - } - return output; -} - -function formatSet(ctx, value, 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 Object.getOwnPropertyNames(). - if (ctx.showHidden) - output.push(`[size]: ${ctx.stylize(`${value.size}`, 'number')}`); - return output; -} - -function formatMap(ctx, value, 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; -} - -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); - ctx.indentationLvl += 2; - for (var i = 0; i < maxLength; i++) { - output[i] = formatValue(ctx, entries[i], recurseTimes); - } - ctx.indentationLvl -= 2; - if (state === kWeak) { - // Sort all entries to have a halfway reliable output (if more entries than - // retrieved ones exist, we can not reliably return the same output). - output = output.sort(); - } - const remaining = entries.length - maxLength; - if (remaining > 0) { - output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); - } - return output; -} - -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; - const remaining = len - maxArrayLength; - const maxLength = Math.min(maxArrayLength, len); - let output = new Array(maxLength); - let start = ''; - let end = ''; - let middle = ' => '; - let i = 0; - if (state === kMapEntries) { - start = '[ '; - end = ' ]'; - middle = ', '; - } - ctx.indentationLvl += 2; - for (; i < maxLength; i++) { - const pos = i * 2; - output[i] = `${start}${formatValue(ctx, entries[pos], recurseTimes)}` + - `${middle}${formatValue(ctx, entries[pos + 1], recurseTimes)}${end}`; - } - ctx.indentationLvl -= 2; - if (state === kWeak) { - // Sort all entries to have a halfway reliable output (if more entries - // than retrieved ones exist, we can not reliably return the same output). - output = output.sort(); - } - if (remaining > 0) { - output.push(`... ${remaining} more item${remaining > 1 ? 's' : ''}`); - } - return output; -} - -function formatWeakCollection(ctx) { - return [ctx.stylize('', 'special')]; -} - -function formatWeakSet(ctx, value, recurseTimes) { - const entries = previewEntries(value); - return formatSetIterInner(ctx, recurseTimes, entries, kWeak); -} - -function formatWeakMap(ctx, value, recurseTimes) { - const entries = previewEntries(value); - return formatMapIterInner(ctx, recurseTimes, entries, kWeak); -} - -function formatSetIterator(ctx, value, recurseTimes) { - const entries = previewEntries(value); - return formatSetIterInner(ctx, recurseTimes, entries, kIterator); -} - -function formatMapIterator(ctx, value, recurseTimes) { - const [entries, isKeyValue] = previewEntries(value, true); - if (isKeyValue) { - return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); - } - - return formatSetIterInner(ctx, recurseTimes, entries, kIterator); -} - -function formatPromise(ctx, value, recurseTimes) { - let output; - const [state, result] = getPromiseDetails(value); - if (state === kPending) { - output = [ctx.stylize('', 'special')]; - } else { - // Using `formatValue` is correct here without the need to fix the - // indentation level. - ctx.indentationLvl += 2; - const str = formatValue(ctx, result, recurseTimes); - ctx.indentationLvl -= 2; - output = [ - state === kRejected ? - `${ctx.stylize('', 'special')} ${str}` : - str - ]; - } - return output; -} - -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 = (type !== kObjectType || ctx.compact === false) ? 2 : 3; - ctx.indentationLvl += diff; - str = formatValue(ctx, desc.value, recurseTimes); - if (diff === 3) { - const len = ctx.colors ? removeColors(str).length : str.length; - if (ctx.breakLength < len) { - extra = `\n${' '.repeat(ctx.indentationLvl)}`; - } - } - ctx.indentationLvl -= diff; - } else if (desc.get !== undefined) { - if (desc.set !== undefined) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else if (desc.set !== undefined) { - str = ctx.stylize('[Setter]', 'special'); - } else { - str = ctx.stylize('undefined', 'undefined'); - } - if (type === kArrayType) { - return str; - } - if (typeof key === 'symbol') { - const tmp = key.toString().replace(strEscapeSequencesReplacer, escapeFn); - name = `[${ctx.stylize(tmp, 'symbol')}]`; - } else if (desc.enumerable === false) { - name = `[${key.replace(strEscapeSequencesReplacer, escapeFn)}]`; - } else if (keyStrRegExp.test(key)) { - name = ctx.stylize(key, 'name'); - } else { - name = ctx.stylize(strEscape(key), 'string'); - } - return `${name}:${extra}${str}`; -} - -function reduceToSingleString(ctx, output, base, braces) { - const breakLength = ctx.breakLength; - let i = 0; - if (ctx.compact === false) { - const indentation = ' '.repeat(ctx.indentationLvl); - let res = `${base ? `${base} ` : ''}${braces[0]}\n${indentation} `; - for (; i < output.length - 1; i++) { - res += `${output[i]},\n${indentation} `; - } - res += `${output[i]}\n${indentation}${braces[1]}`; - return res; - } - if (output.length * 2 <= breakLength) { - let length = 0; - for (; i < output.length && length <= breakLength; i++) { - if (ctx.colors) { - length += removeColors(output[i]).length + 1; - } else { - length += output[i].length + 1; - } - } - if (length <= breakLength) - return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` + - braces[1]; - } - // If the opening "brace" is too large, like in the case of "Set {", - // we need to force the first item to be on the next line or the - // items will not line up correctly. - const indentation = ' '.repeat(ctx.indentationLvl); - const ln = base === '' && braces[0].length === 1 ? - ' ' : `${base ? ` ${base}` : ''}\n${indentation} `; - const str = join(output, `,\n${indentation} `); - return `${braces[0]}${ln}${str} ${braces[1]}`; -} - function isBoolean(arg) { return typeof arg === 'boolean'; } diff --git a/node.gyp b/node.gyp index 77d24d142e331d..7c1d811ac28514 100644 --- a/node.gyp +++ b/node.gyp @@ -159,6 +159,7 @@ 'lib/internal/url.js', 'lib/internal/util.js', 'lib/internal/util/comparisons.js', + 'lib/internal/util/inspect.js', 'lib/internal/util/inspector.js', 'lib/internal/util/types.js', 'lib/internal/http2/core.js',