diff options
author | Ruben Bridgewater <ruben@bridgewater.de> | 2018-09-13 17:39:10 +0200 |
---|---|---|
committer | Ruben Bridgewater <ruben@bridgewater.de> | 2018-09-24 18:18:50 +0200 |
commit | c600a3ce1c7761761a34841730e133db02fadb4d (patch) | |
tree | f59b73b07f5af2935fa65e7dc7033ae8805d2b61 /lib/util.js | |
parent | 4da11f2dc5df6ccdcfefed2bd2cf26221e176185 (diff) | |
download | android-node-v8-c600a3ce1c7761761a34841730e133db02fadb4d.tar.gz android-node-v8-c600a3ce1c7761761a34841730e133db02fadb4d.tar.bz2 android-node-v8-c600a3ce1c7761761a34841730e133db02fadb4d.zip |
util: move inspect in separate file
The inspect function became very big and it's better to handle this
in a separate file.
PR-URL: https://github.com/nodejs/node/pull/22845
Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Diffstat (limited to 'lib/util.js')
-rw-r--r-- | lib/util.js | 1130 |
1 files changed, 1 insertions, 1129 deletions
diff --git a/lib/util.js b/lib/util.js index d1a483cf17..ae2295533d 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('<uninitialized>', '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('<items unknown>', '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('<pending>', '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('<rejected>', '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'; } |