Skip to content

Instantly share code, notes, and snippets.

@dead-claudia
Last active July 26, 2018 23:23
Show Gist options
  • Save dead-claudia/829cfa16da8dc4688c18dad378e97df6 to your computer and use it in GitHub Desktop.
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`.
// 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