Last active
July 26, 2018 23:23
-
-
Save dead-claudia/829cfa16da8dc4688c18dad378e97df6 to your computer and use it in GitHub Desktop.
WIP `util-inspect` replacement that features the most recent Node APIs. An attempt is made here to remain compatible with older IE/etc. with `es5-shim` and without `es5-sham`.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright Joyent, Inc. and other Node contributors. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a | |
// copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to permit | |
// persons to whom the Software is furnished to do so, subject to the | |
// following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included | |
// in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
// USE OR OTHER DEALINGS IN THE SOFTWARE. | |
"use strict" | |
var isCallable = require("is-callable") | |
var isArguments = require("is-arguments") | |
var isRegExp = require("is-regex") | |
// eslint-disable-next-line global-require | |
var objectKeys = Object.keys || require("object-keys") | |
var objectToString = Object.prototype.toString | |
var propertyIsEnumerable = Object.prototype.propertyIsEnumerable | |
var symbolToStringTag, customInspectSymbol | |
var readEntries = Array.from || function (value) { | |
var iter = value.entries() | |
var array = [] | |
var next | |
while (!(next = iter.next()).done) array.push(next.value) | |
return array | |
} | |
/* eslint-disable no-undef */ | |
if ( | |
typeof Symbol !== "undefined" && isCallable(Symbol) && | |
typeof Symbol() === "symbol" | |
) { | |
symbolToStringTag = Symbol.toStringTag | |
customInspectSymbol = Symbol("util.inspect.custom") | |
/* eslint-enable no-undef */ | |
} else { | |
customInspectSymbol = "inspect" | |
} | |
function isError(e) { | |
return objectToString.call(e) === "[object Error]" || | |
e instanceof Error | |
} | |
function isAnyArrayBuffer(value) { | |
var toString = objectToString.call(value) | |
return toString === "[object ArrayBuffer]" || | |
toString === "[object SharedArrayBuffer]" | |
} | |
function isDataView(value) { | |
return objectToString.call(value) === "[object DataView]" | |
} | |
function isMap(value) { | |
return objectToString.call(value) === "[object Map]" | |
} | |
function isPromise(value) { | |
return objectToString.call(value) === "[object Promise]" | |
} | |
function isSet(value) { | |
return objectToString.call(value) === "[object Set]" | |
} | |
function isWeakCollection(value) { | |
var toString = objectToString.call(value) | |
return toString === "[object WeakMap]" || | |
toString === "[object WeakSet]" | |
} | |
function isDate(value) { | |
return objectToString.call(value) === "[object Date]" | |
} | |
function isMapIterator(value) { | |
return objectToString.call(value) === "[object Map Iterator]" | |
} | |
function isSetIterator(value) { | |
return objectToString.call(value) === "[object Set Iterator]" | |
} | |
function isModuleNamespaceObject(value) { | |
return objectToString.call(value) === "[object Module]" | |
} | |
function isReferenceError(value) { | |
return objectToString.call(value) === "[object ReferenceError]" | |
} | |
var typedArrayToStringTag = | |
// eslint-disable-next-line no-undef | |
Object.getOwnPropertyDescriptor && typeof Uint8Array !== "undefined" | |
? Object.getOwnPropertyDescriptor( | |
// eslint-disable-next-line no-undef | |
Object.getPrototypeOf(Uint8Array.prototype), symbolToStringTag | |
).get | |
: undefined | |
function isTypedArray(value) { | |
if (typedArrayToStringTag != null) { | |
return typedArrayToStringTag.call(value) != null | |
} | |
var toString = objectToString.call(value) | |
return toString === "[object Uint8Array]" || | |
toString === "[object Uint8ClampedArray]" || | |
toString === "[object Uint16Array]" || | |
toString === "[object Uint32Array]" || | |
toString === "[object Int8Array]" || | |
toString === "[object Int16Array]" || | |
toString === "[object Int32Array]" || | |
toString === "[object Float32Array]" || | |
toString === "[object Float64Array]" || | |
toString === "[object BigInt64Array]" || | |
toString === "[object BigUint64Array]" | |
} | |
function removeColors(str) { | |
return str.replace(/\u001b\[\d\d?m/g, "") | |
} | |
var maxStackName | |
var maxStackMessage | |
/** | |
* Returns true if `err.name` and `err.message` are equal to engine-specific | |
* values indicating max call stack size has been exceeded. | |
* "Maximum call stack size exceeded" in V8. | |
* | |
* @param {Error} err | |
* @returns {boolean} | |
*/ | |
function overflowStack() { | |
overflowStack() | |
} | |
function isStackOverflowError(err) { | |
if (maxStackMessage === undefined) { | |
try { | |
overflowStack() | |
} catch (err) { | |
maxStackMessage = err.message | |
maxStackName = err.name | |
} | |
} | |
return err.name === maxStackName && | |
err.message === maxStackMessage | |
} | |
var inspectDefaultOptions = { | |
showHidden: false, | |
depth: 2, | |
colors: false, | |
customInspect: true, | |
maxArrayLength: 100, | |
breakLength: 60, | |
compact: true, | |
} | |
function EmptyObject() {} | |
EmptyObject.prototype = null | |
inspect.defaultOptions = inspectDefaultOptions | |
inspect.custom = customInspectSymbol | |
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics | |
inspect.colors = new EmptyObject() | |
inspect.colors.bold = [1, 22] | |
inspect.colors.italic = [3, 23] | |
inspect.colors.underline = [4, 24] | |
inspect.colors.inverse = [7, 27] | |
inspect.colors.white = [37, 39] | |
inspect.colors.grey = [90, 39] | |
inspect.colors.black = [30, 39] | |
inspect.colors.blue = [34, 39] | |
inspect.colors.cyan = [36, 39] | |
inspect.colors.green = [32, 39] | |
inspect.colors.magenta = [35, 39] | |
inspect.colors.red = [31, 39] | |
inspect.colors.yellow = [33, 39] | |
// Don't use 'blue' not visible on cmd.exe | |
inspect.styles = new EmptyObject() | |
inspect.styles.special = "cyan" | |
inspect.styles.number = "yellow" | |
inspect.styles.bigint = "yellow" | |
inspect.styles.boolean = "yellow" | |
inspect.styles.undefined = "grey" | |
inspect.styles.null = "bold" | |
inspect.styles.string = "green" | |
inspect.styles.symbol = "green" | |
inspect.styles.date = "magenta" | |
// inspect.styles.name = intentionally not styling | |
inspect.styles.regexp = "red" | |
var regExpToString = RegExp.prototype.toString | |
var dateToISOString = Date.prototype.toISOString | |
var errorToString = Error.prototype.toString | |
/* eslint-disable no-control-regex */ | |
var strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/ | |
var strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g | |
var strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c]/ | |
var strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c]/g | |
/* eslint-enable no-control-regex */ | |
var keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/ | |
var numberRegExp = /^(0|[1-9][0-9]*)$/ | |
var readableRegExps = {} | |
var MIN_LINE_LENGTH = 16 | |
// Escaped special characters. Use empty strings to fill up unused entries. | |
var 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", "", "", "", | |
"", "", "", "", "\\'", "", "", "", "", "", | |
"", "", "", "", "", "", "", "", "", "", | |
"", "", "", "", "", "", "", "", "", "", | |
"", "", "", "", "", "", "", "", "", "", | |
"", "", "", "", "", "", "", "", "", "", | |
"", "", "", "", "", "", "", "\\\\", | |
] | |
function addQuotes(str, quotes) { | |
if (quotes === -1) { | |
return '"' + str + '"' | |
} else if (quotes === -2) { | |
return "`" + str + "`" | |
} else { | |
return "'" + str + "'" | |
} | |
} | |
function escapeFn(str) { | |
return meta[str.charCodeAt(0)] | |
} | |
// Escape control characters, single quotes and the backslash. | |
// This is similar to JSON stringify escaping. | |
// eslint-disable-next-line max-statements | |
function strEscape(str) { | |
var escapeTest = strEscapeSequencesRegExp | |
var escapeReplace = strEscapeSequencesReplacer | |
var 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) | |
} | |
var result = "" | |
var last = 0 | |
var i | |
for (i = 0; i < str.length; i++) { | |
var 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) | |
} | |
/** | |
* 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 | |
var ctx = { | |
seen: [], | |
stylize: stylizeNoColor, | |
showHidden: inspectDefaultOptions.showHidden, | |
depth: inspectDefaultOptions.depth, | |
colors: inspectDefaultOptions.colors, | |
customInspect: inspectDefaultOptions.customInspect, | |
maxArrayLength: inspectDefaultOptions.maxArrayLength, | |
breakLength: inspectDefaultOptions.breakLength, | |
indentationLvl: 0, | |
compact: inspectDefaultOptions.compact, | |
} | |
// 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) { | |
var optKeys = objectKeys(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) | |
} | |
function stylizeWithColor(str, styleType) { | |
var style = inspect.styles[styleType] | |
if (style !== undefined) { | |
var color = inspect.colors[style] | |
return "\u001b[" + color[0] + "m" + str + "\u001b[" + color[1] + "m" | |
} | |
return str | |
} | |
function stylizeNoColor( | |
// eslint-disable-next-line no-unused-vars | |
str, styleType | |
) { | |
return str | |
} | |
function getEmpty(ctor, tag) { | |
if (tag === ctor) return tag + " {}" | |
if (tag === "") return ctor + " {}" | |
if (ctor === "") return "[" + tag + "] {}" | |
return ctor + " [" + tag + "] {}" | |
} | |
function getPrefix(ctor, tag) { | |
if (tag === ctor) return tag + " " | |
if (tag === "") return ctor + " " | |
if (ctor === "") return "[" + tag + "] " | |
return ctor + " [" + tag + "] " | |
} | |
function createIndent(level) { | |
var result = "" | |
var str = " " | |
while (true) { | |
if (level & 1) result += str | |
level >>>= 1 | |
if (level <= 0) return result | |
str += str | |
} | |
} | |
var arrayIndexOf = Array.prototype.indexOf || /** @this */ function (item) { | |
for (var i = 0; i < this.length; i++) { | |
if (this[i] === item) return i | |
} | |
return -1 | |
} | |
// eslint-disable-next-line max-statements | |
function formatValue(ctx, value, recurseTimes) { | |
// Primitive types cannot have properties | |
if (typeof value !== "object" && !isCallable(value)) { | |
return formatPrimitive(ctx.stylize, value, ctx) | |
} | |
if (value === null) { | |
return ctx.stylize("null", "null") | |
} | |
// Provide a hook for user-specified inspect functions. | |
// Check that value is an object with an inspect function on it | |
if (ctx.customInspect) { | |
var maybeCustom = value[customInspectSymbol] | |
if ( | |
isCallable(maybeCustom) && | |
// 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) | |
) { | |
var 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 ret | |
return formatValue(ctx, ret, recurseTimes) | |
} | |
} | |
} | |
// 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 (arrayIndexOf.call(ctx.seen, value) !== -1) { | |
return ctx.stylize("[Circular]", "special") | |
} | |
var symbols = Object.getOwnPropertySymbols | |
? Object.getOwnPropertySymbols(value) | |
: [] | |
var keys, i | |
// Look up the keys of the object. | |
if (ctx.showHidden) { | |
keys = (Object.getOwnPropertyNames || objectKeys)(value) | |
} else { | |
// This might throw if `value` is a Module Namespace Object from an | |
// unevaluated module, but we don't want to perform the actual type | |
// check because it's expensive. | |
// eslint-disable-next-line no-warning-comments | |
// TODO: track https://github.com/tc39/ecma262/issues/1209 | |
// and modify this logic as needed. | |
try { | |
keys = objectKeys(value) | |
} catch (err) { | |
if ( | |
isReferenceError(err) && | |
isModuleNamespaceObject(value) | |
) { | |
keys = Object.getOwnPropertyNames(value) | |
} else { | |
throw err | |
} | |
} | |
if (symbols.length !== 0) { | |
var count = 0 | |
for (i = 0; i < symbols.length; i++) { | |
var key = symbols[i] | |
if (propertyIsEnumerable.call(value, key)) { | |
symbols[count++] = key | |
} | |
} | |
symbols.length = count | |
} | |
} | |
var keyLength = keys.length + symbols.length | |
var ctorObject = value.constructor | |
var ctor = isCallable(ctorObject) ? ctorObject.name : "" | |
var tag = value[symbolToStringTag] | |
if (typeof tag !== "string") tag = "" | |
var base = "" | |
var formatter = formatObject | |
var braceStart = "{" | |
var braceEnd = "}" | |
var raw, extra, formatted | |
if (Array.isArray(value)) { | |
// Only set the constructor for non ordinary ("Array [...]") | |
// arrays. | |
if (ctorObject === Array) { | |
if (value.length === 0 && keyLength === 0) return "[]" | |
braceStart = "[" | |
} else { | |
if (value.length === 0 && keyLength === 0) { | |
return getPrefix(ctor, tag) + "[]" | |
} | |
braceStart = getPrefix(ctor, tag) + "[" | |
} | |
braceEnd = "]" | |
formatter = formatArray | |
} else if (isSet(value)) { | |
if (value.size === 0 && keyLength === 0) return getEmpty(ctor, tag) | |
braceStart = getPrefix(ctor, tag) + "{" | |
formatter = formatSet | |
} else if (isMap(value)) { | |
if (value.size === 0 && keyLength === 0) return getEmpty(ctor, tag) | |
braceStart = getPrefix(ctor, tag) + "{" | |
formatter = formatMap | |
} else if (isTypedArray(value)) { | |
if (value.length === 0 && keyLength === 0) { | |
return getPrefix(ctor, tag) + "[]" | |
} | |
braceStart = getPrefix(ctor, tag) + "[" | |
braceEnd = "]" | |
formatter = formatTypedArray | |
} else if (isMapIterator(value)) { | |
braceStart = "[" + tag + "] {" | |
formatter = formatMapIterator | |
} else if (isSetIterator(value)) { | |
braceStart = "[" + tag + "] {" | |
formatter = formatSetIterator | |
} else { | |
// Check for boxed strings with valueOf() | |
// The .valueOf() call can fail for a multitude of reasons | |
try { | |
raw = value.valueOf() | |
} catch (e) { /* ignore */ } | |
if (typeof raw === "string") { | |
formatted = formatPrimitive(stylizeNoColor, raw, ctx) | |
base = "[String: " + formatted + "]" | |
if (keyLength === raw.length) return ctx.stylize(base, "string") | |
// For boxed Strings, we have to remove the 0-n indexed | |
// entries, since they just noisy up the output and are | |
// redundant | |
// Make boxed primitive Strings look like such | |
keys = keys.slice(value.length) | |
} else if (typeof raw === "number") { | |
// Make boxed primitive Numbers look like such | |
formatted = formatPrimitive(stylizeNoColor, raw) | |
base = "[Number: " + formatted + "]" | |
if (keyLength === 0) return ctx.stylize(base, "number") | |
} else if (typeof raw === "boolean") { | |
// Make boxed primitive Booleans look like such | |
formatted = formatPrimitive(stylizeNoColor, raw) | |
base = "[Boolean: " + formatted + "]" | |
if (keyLength === 0) return ctx.stylize(base, "boolean") | |
// eslint-disable-next-line valid-typeof | |
} else if (typeof raw === "bigint") { | |
// Make boxed primitive BigInts look like such | |
formatted = formatPrimitive(stylizeNoColor, raw) | |
base = "[BigInt: " + formatted + "]" | |
if (keyLength === 0) return ctx.stylize(base, "bigint") | |
} else if (typeof raw === "symbol") { | |
// Make boxed primitive BigInts look like such | |
formatted = formatPrimitive(stylizeNoColor, raw) | |
base = "[Symbol: " + formatted + "]" | |
if (keyLength === 0) return ctx.stylize(base, "symbol") | |
} else if (ctorObject === Object) { | |
if (isArguments(value)) { | |
if (keyLength === 0) return "[Arguments] {}" | |
braceStart = "[Arguments] {" | |
} else if (tag === "") { | |
if (keyLength === 0) return "{}" | |
} else { | |
if (keyLength === 0) return getEmpty(ctor, tag) | |
braceStart = getPrefix(ctor, tag) + "{" | |
} | |
} else if (isCallable(value)) { | |
var name = "" + (ctor || tag) + | |
(value.name ? ": " + value.name : "") | |
if (keyLength === 0) { | |
return ctx.stylize("[" + name + "]", "special") | |
} | |
base = "[" + name + "]" | |
} else if (isRegExp(value)) { | |
// Make RegExps say that they are RegExps | |
if (keyLength === 0 || recurseTimes < 0) { | |
return ctx.stylize(regExpToString.call(value), "regexp") | |
} | |
base = regExpToString.call(value) | |
} else if (isDate(value)) { | |
if (keyLength === 0) { | |
if (Number.isNaN(value.getTime())) { | |
return ctx.stylize(value.toString(), "date") | |
} | |
return ctx.stylize(dateToISOString.call(value), "date") | |
} | |
// Make dates with properties first say the date | |
base = dateToISOString.call(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. | |
var 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) { | |
var indentation = createIndent(ctx.indentationLvl) | |
base = formatError(value).replace(/\n/g, "\n" + indentation) | |
} | |
if (keyLength === 0) return base | |
if (ctx.compact === false && stackStart !== -1) { | |
base = "[" + base.slice(0, stackStart) + "]" | |
} else { | |
braceStart = "{" + base.slice(stackStart) | |
} | |
} else if (isAnyArrayBuffer(value)) { | |
// Fast path for ArrayBuffer and SharedArrayBuffer. | |
// Can't do the same for DataView because it has a non-primitive | |
// .buffer property that we need to recurse for. | |
if (keyLength === 0) { | |
return getPrefix(ctor, tag) + "{ byteLength: " + | |
formatNumber(ctx.stylize, value.byteLength) + | |
" }" | |
} | |
keys.unshift("byteLength") | |
braceStart = getPrefix(ctor, tag) + "{" | |
} else if (isDataView(value)) { | |
// .buffer goes last, it's not a primitive like the others. | |
keys.unshift("byteLength", "byteOffset", "buffer") | |
braceStart = getPrefix(ctor, tag) + "{" | |
} else if (isPromise(value)) { | |
braceStart = getPrefix(ctor, tag) + "{" | |
} else if (isWeakCollection(value)) { | |
extra = "<items unknown>" | |
braceStart = getPrefix(ctor, tag) + "{" | |
} else if (isModuleNamespaceObject(value)) { | |
braceStart = "[" + tag + "] {" | |
formatter = formatNamespaceObject | |
} else { | |
if (keyLength === 0) return getEmpty(ctor, tag) | |
braceStart = getPrefix(ctor, tag) + "{" | |
} | |
} | |
if (recurseTimes != null) { | |
if (recurseTimes < 0) { | |
return ctx.stylize("[" + (ctor || tag || "Object") + "]", "special") | |
} | |
recurseTimes -= 1 | |
} | |
ctx.seen.push(value) | |
var output | |
// This corresponds to a depth of at least 333 and likely 500. | |
if (ctx.indentationLvl < 1000) { | |
output = formatter(ctx, value, recurseTimes, keys) | |
} else { | |
try { | |
output = formatter(ctx, value, recurseTimes, keys) | |
} catch (err) { | |
if (isStackOverflowError(err)) { | |
ctx.seen.pop() | |
return ctx.stylize( | |
"[" + (ctor || tag || "Object") + | |
": Inspection interrupted " + | |
"prematurely. Maximum call stack size exceeded.]", | |
"special" | |
) | |
} | |
throw err | |
} | |
} | |
if (extra !== undefined) output.unshift(extra) | |
for (i = 0; i < symbols.length; i++) { | |
output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0)) | |
} | |
ctx.seen.pop() | |
return reduceToSingleString(ctx, output, base, braceStart, braceEnd) | |
} | |
function formatNumber(fn, value) { | |
// Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. | |
if (1 / value === -Infinity) return fn("-0", "number") | |
return fn(value + "", "number") | |
} | |
function formatBigInt(fn, value) { | |
return fn(value + "n", "bigint") | |
} | |
// eslint-disable-next-line max-statements | |
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 | |
var minLineLength = Math.max( | |
ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH | |
) | |
// eslint-disable-next-line max-len | |
var averageLineLength = Math.ceil( | |
value.length / Math.ceil(value.length / minLineLength) | |
) | |
var divisor = Math.max(averageLineLength, MIN_LINE_LENGTH) | |
var 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. | |
readableRegExps[divisor] = new RegExp( | |
"(.|\\n){1," + divisor + "}(\\s|$)|(\\n|.)+?(\\s|$)", "gm" | |
) | |
} | |
var matches = value.match(readableRegExps[divisor]) | |
if (matches.length > 1) { | |
var indent = createIndent(ctx.indentationLvl) | |
var i | |
res += fn(strEscape(matches[0]), "string") + " +\n" | |
for (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.call(value) | |
} | |
function formatObject(ctx, value, recurseTimes, keys) { | |
var len = keys.length | |
var output = new Array(len) | |
for (var i = 0; i < len; i++) { | |
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0) | |
} | |
return output | |
} | |
function formatNamespaceObject(ctx, value, recurseTimes, keys) { | |
var len = keys.length | |
var output = new Array(len) | |
for (var i = 0; i < len; i++) { | |
try { | |
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0) | |
} catch (err) { | |
if (!isReferenceError(err)) 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. | |
var tmp = {} | |
tmp[keys[i]] = "" | |
output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], 0) | |
var 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 | |
// eslint-disable-next-line max-statements, max-params | |
function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) { | |
var output = [] | |
var keyLen = keys.length | |
var visibleLength = 0 | |
var i = 0 | |
var key, ending, message | |
if (keyLen !== 0 && numberRegExp.test(keys[0])) { | |
for (var current = 0; current < keys.length; current++) { | |
if (visibleLength === maxLength) break | |
key = keys[current] | |
var index = +key | |
// Arrays can only have up to 2^32 - 1 entries | |
if (index > 0xFFFFFFFE) break | |
if (i !== index) { | |
if (!numberRegExp.test(key)) break | |
var emptyItems = index - i | |
ending = emptyItems > 1 ? "s" : "" | |
message = "<" + emptyItems + " empty item" + ending + ">" | |
output.push(ctx.stylize(message, "undefined")) | |
i = index | |
if (++visibleLength === maxLength) break | |
} | |
output.push(formatProperty(ctx, value, recurseTimes, key, 1)) | |
visibleLength++ | |
i++ | |
} | |
} | |
if (i < valLen && visibleLength !== maxLength) { | |
var len = valLen - i | |
ending = len > 1 ? "s" : "" | |
message = "<" + len + " empty item" + ending + ">" | |
output.push(ctx.stylize(message, "undefined")) | |
i = valLen | |
if (keyLen === 0) return output | |
} | |
var remaining = valLen - i | |
if (remaining > 0) { | |
output.push( | |
"... " + remaining + " more item" + (remaining > 1 ? "s" : "") | |
) | |
} | |
if (ctx.showHidden && keys[keyLen - 1] === "length") { | |
// No extra keys | |
output.push(formatProperty(ctx, value, recurseTimes, "length", 2)) | |
} else if ( | |
valLen === 0 || | |
keyLen > valLen && keys[valLen - 1] === valLen - 1 + "" | |
) { | |
// The array is not sparse | |
for (i = valLen; i < keyLen; i++) { | |
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)) | |
} | |
} else if (keys[keyLen - 1] !== valLen - 1 + "") { | |
var extra = [] | |
// Only handle special keys | |
for (i = keys.length - 1; i >= 0; i--) { | |
key = keys[i] | |
if (numberRegExp.test(key) && +key < 0xFFFFFFFF) break | |
extra.push(formatProperty(ctx, value, recurseTimes, key, 2)) | |
} | |
for (i = extra.length - 1; i >= 0; i--) output.push(extra[i]) | |
} | |
return output | |
} | |
function formatArray(ctx, value, recurseTimes, keys) { | |
var len = Math.min(Math.max(0, ctx.maxArrayLength), value.length) | |
var hidden = ctx.showHidden ? 1 : 0 | |
var valLen = value.length | |
var keyLen = keys.length - hidden | |
if (keyLen !== valLen || keys[keyLen - 1] !== valLen - 1 + "") { | |
return formatSpecialArray(ctx, value, recurseTimes, keys, len, valLen) | |
} | |
var remaining = valLen - len | |
var output = new Array(len + (remaining > 0 ? 1 : 0) + hidden) | |
var i | |
for (i = 0; i < len; i++) { | |
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 1) | |
} | |
if (remaining > 0) { | |
output[i++] = "... " + remaining + " more item" + | |
(remaining > 1 ? "s" : "") | |
} | |
if (ctx.showHidden === true) { | |
output[i] = formatProperty(ctx, value, recurseTimes, "length", 2) | |
} | |
return output | |
} | |
function formatTypedArray(ctx, value, recurseTimes, keys) { | |
var maxLength = Math.min(Math.max(0, ctx.maxArrayLength), value.length) | |
var remaining = value.length - maxLength | |
var output = new Array(maxLength + (remaining > 0 ? 1 : 0)) | |
var i | |
if (value.length > 0 && typeof value[0] === "number") { | |
for (i = 0; i < maxLength; ++i) { | |
output[i] = formatNumber(ctx.stylize, value[i]) | |
} | |
} else { | |
for (i = 0; i < maxLength; ++i) { | |
output[i] = formatBigInt(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. | |
var extraKeys = [ | |
"BYTES_PER_ELEMENT", | |
"length", | |
"byteLength", | |
"byteOffset", | |
"buffer", | |
] | |
for (i = 0; i < extraKeys.length; i++) { | |
var str = formatValue(ctx, value[extraKeys[i]], recurseTimes) | |
output.push("[" + extraKeys[i] + "]: " + str) | |
} | |
} | |
// TypedArrays cannot have holes. Therefore it is safe to assume that all | |
// extra keys are indexed after value.length. | |
for (i = value.length; i < keys.length; i++) { | |
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2)) | |
} | |
return output | |
} | |
function formatSet(ctx, value, recurseTimes, keys) { | |
var output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)) | |
var i = 0 | |
value.forEach(function (v) { | |
output[i++] = formatValue(ctx, v, recurseTimes) | |
}) | |
// With `showHidden`, `length` will display as a hidden property for | |
// arrays. For consistency's sake, do the same for `size`, even though this | |
// property isn't selected by Object.getOwnPropertyNames(). | |
if (ctx.showHidden) { | |
output[i++] = "[size]: " + ctx.stylize(value.size + "", "number") | |
} | |
for (var n = 0; n < keys.length; n++) { | |
output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0) | |
} | |
return output | |
} | |
function formatMap(ctx, value, recurseTimes, keys) { | |
var output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0)) | |
var i = 0 | |
value.forEach(function (k, v) { | |
output[i++] = formatValue(ctx, k, recurseTimes) + " => " + | |
formatValue(ctx, v, recurseTimes) | |
}) | |
// See comment in formatSet | |
if (ctx.showHidden) { | |
output[i++] = "[size]: " + ctx.stylize(value.size + "", "number") | |
} | |
for (var n = 0; n < keys.length; n++) { | |
output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0) | |
} | |
return output | |
} | |
// eslint-disable-next-line max-params | |
function formatSetIterInner(ctx, value, recurseTimes, keys, entries) { | |
var maxArrayLength = Math.max(ctx.maxArrayLength, 0) | |
var maxLength = Math.min(maxArrayLength, entries.length) | |
var output = new Array(maxLength) | |
var i | |
for (i = 0; i < maxLength; ++i) { | |
output[i] = formatValue(ctx, entries[i], recurseTimes) | |
} | |
var remaining = entries.length - maxLength | |
if (remaining > 0) { | |
output.push( | |
"... " + remaining + " more item" + (remaining > 1 ? "s" : "") | |
) | |
} | |
for (i = 0; i < keys.length; i++) { | |
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 0)) | |
} | |
return output | |
} | |
// eslint-disable-next-line max-params | |
function formatMapIterInner(ctx, value, recurseTimes, keys, entries) { | |
var maxArrayLength = Math.max(ctx.maxArrayLength, 0) | |
// Entries exist as [key1, val1, key2, val2, ...] | |
var len = entries.length / 2 | |
var remaining = len - maxArrayLength | |
var maxLength = Math.min(maxArrayLength, len) | |
var output = new Array(maxLength) | |
var i = 0 | |
for (; i < maxLength; i++) { | |
var pos = i * 2 | |
output[i] = "[ " + formatValue(ctx, entries[pos], recurseTimes) + ", " + | |
formatValue(ctx, entries[pos + 1], recurseTimes) + " ]" | |
} | |
if (remaining > 0) { | |
output.push( | |
"... " + remaining + " more item" + (remaining > 1 ? "s" : "") | |
) | |
} | |
for (i = 0; i < keys.length; i++) { | |
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 0)) | |
} | |
return output | |
} | |
function formatSetIterator(ctx, value, recurseTimes, keys) { | |
return formatSetIterInner( | |
ctx, value, recurseTimes, keys, readEntries(value) | |
) | |
} | |
function formatMapIterator(ctx, value, recurseTimes, keys) { | |
var entries = readEntries(value) | |
for (var i = 0; i < entries.length; i++) { | |
var item = entries[i] | |
if (!Array.isArray(item) || item.length !== 2) { | |
return formatSetIterInner(ctx, value, recurseTimes, keys, entries) | |
} | |
} | |
return formatMapIterInner(ctx, value, recurseTimes, keys, entries) | |
} | |
// eslint-disable-next-line max-params | |
function formatProperty(ctx, value, recurseTimes, key, array) { | |
var name, str | |
var extra = " " | |
var desc = | |
Object.getOwnPropertyDescriptor && | |
Object.getOwnPropertyDescriptor(value, key) || | |
{value: value[key], enumerable: true} | |
if (desc.value !== undefined) { | |
var diff = array !== 0 || ctx.compact === false ? 2 : 3 | |
ctx.indentationLvl += diff | |
str = formatValue(ctx, desc.value, recurseTimes) | |
if (diff === 3) { | |
var len = ctx.colors ? removeColors(str).length : str.length | |
if (ctx.breakLength < len) { | |
extra = "\n" + createIndent(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 (array === 1) return str | |
if (typeof key === "symbol") { | |
name = "[" + ctx.stylize(key.toString(), "symbol") + "]" | |
} else if (desc.enumerable === false) { | |
name = "[" + key + "]" | |
} else if (keyStrRegExp.test(key)) { | |
name = ctx.stylize(key, "name") | |
} else { | |
name = ctx.stylize(strEscape(key), "string") | |
} | |
return name + ":" + extra + str | |
} | |
// eslint-disable-next-line max-params | |
function reduceToSingleString(ctx, output, base, braceStart, braceEnd) { | |
var breakLength = ctx.breakLength | |
var i = 0 | |
var indentation | |
if (ctx.compact === false) { | |
indentation = createIndent(ctx.indentationLvl) | |
var res = (base ? base + " " : "") + | |
braceStart + "\n" + indentation + " " | |
for (; i < output.length - 1; i++) { | |
res += output[i] + ",\n" + indentation + " " | |
} | |
res += output[i] + "\n" + indentation + braceEnd | |
return res | |
} | |
if (output.length * 2 <= breakLength) { | |
var 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 braceStart + (base ? base + " " : "") + | |
" " + output.join(", ") + " " + braceEnd | |
} | |
} | |
// 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. | |
indentation = createIndent(ctx.indentationLvl) | |
var ln = base === "" && braceStart.length === 1 | |
? " " | |
: (base ? base + " " : "") + "\n" + indentation + " " | |
var str = output.join(",\n" + indentation + " ") | |
return braceStart + ln + str + " " + braceEnd | |
} | |
module.exports = inspect | |
if (Object.defineProperty) { | |
Object.defineProperty(inspect, "defaultOptions", { | |
configurable: false, writable: false, | |
}) | |
} | |
if (Object.seal) Object.seal(inspectDefaultOptions) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment