UNPKG

@visulima/inspector

Version:

Inspect utility for Node.js and Browsers.

607 lines (579 loc) 20.9 kB
'use strict'; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); const TRUNCATOR = "…"; const inspectList = (list, from, options, inspect, inspectItem, separator = ", ") => { const size = list.length; if (size === 0) { return ""; } let inspect_ = inspect; if (inspectItem !== void 0) { inspect_ = inspectItem; } const originalLength = options.truncate; let output = ""; let peek = ""; let truncated = ""; for (let index = 0; index < size; index += 1) { const last = index + 1 === list.length; const secondToLast = index + 2 === list.length; truncated = `${TRUNCATOR}(${list.length - index})`; let value = list[index]; options.truncate = originalLength - output.length - (last ? 0 : separator.length); const string = peek || inspect_(value, from, options, inspect) + (last ? "" : separator); const nextLength = output.length + string.length; const truncatedLength = nextLength + truncated.length; if (last && nextLength > originalLength && output.length + truncated.length <= originalLength) { break; } if (!last && !secondToLast && truncatedLength > originalLength) { break; } value = list[index + 1]; peek = last ? "" : inspect_(value, from, options, inspect) + (secondToLast ? "" : separator); if (!last && secondToLast && truncatedLength > originalLength && nextLength + peek.length > originalLength) { break; } output += string; if (!last && !secondToLast && nextLength + peek.length >= originalLength) { truncated = `${TRUNCATOR}(${list.length - index - 1})`; break; } truncated = ""; } return `${output}${truncated}`; }; const inspectAttribute = ([key, value], _, options) => { options.truncate -= 3; if (!value) { return options.stylize(String(key), "yellow"); } return `${options.stylize(String(key), "yellow")}=${options.stylize(`"${value}"`, "string")}`; }; const inspectNode = (node, inspect, options) => { switch (node.nodeType) { case 1: { return inspectHTMLElement(node, node, options, inspect); } case 3: { return inspect(node.data, inspect, options); } default: { return inspect(node, inspect, options); } } }; const inspectNodeCollection = (collection, options, inspect, _) => inspectList(collection, collection, options, inspect, inspectNode, "\n"); const inspectHTMLElement = (element, object, options, inspect) => { const properties = element.getAttributeNames(); const name = element.tagName.toLowerCase(); const head = options.stylize(`<${name}`, "special"); const headClose = options.stylize(`>`, "special"); const tail = options.stylize(`</${name}>`, "special"); options.truncate -= name.length * 2 + 5; let propertyContents = ""; if (properties.length > 0) { propertyContents += " "; propertyContents += inspectList( properties.map((key) => [key, element.getAttribute(key)]), object, options, inspect, inspectAttribute, " " ); } options.truncate -= propertyContents.length; const { truncate } = options; let children = inspectNodeCollection(element.children, options, inspect); if (children && children.length > truncate) { children = `${TRUNCATOR}(${element.children.length})`; } return `${head}${propertyContents}${headClose}${children}${tail}`; }; const inspectArguments = (arguments_, options, inspect) => { if (arguments_.length === 0) { return "Arguments []"; } options.truncate -= 13; return `Arguments [ ${inspectList(arguments_, arguments_, options, inspect)} ]`; }; const getIndent = (indent, depth) => { let baseIndent; if (indent === " ") { baseIndent = " "; } else if (typeof indent === "number" && indent > 0) { baseIndent = Array.from({ length: indent + 1 }).join(" "); } else { return void 0; } return { base: baseIndent, prev: ` ${Array.from({ length: depth + 1 }).join(baseIndent)}` }; }; const indentedJoin = (values, indent) => { if (values.length === 0) { return ""; } const lineJoiner = indent.prev + indent.base; return lineJoiner + values.split(", ").join(`,${lineJoiner}`) + indent.prev; }; const quoteComplexKey = (key, options) => { if (/^[a-z_]\w*$/i.test(key)) { return key; } const stringifiedKey = JSON.stringify(key); if (options.quoteStyle === "double") { return stringifiedKey.replaceAll('"', String.raw`\"`); } return stringifiedKey.replaceAll("'", String.raw`\'`).replaceAll(String.raw`\"`, '"').replaceAll(/^"|"$/g, "'"); }; const inspectProperty = ([key, value], object, options, inspect) => { options.truncate -= 2; if (typeof key === "string") { key = quoteComplexKey(key, options); } else if (typeof key !== "number") { key = `[${inspect(key, object, options)}]`; } options.truncate -= key.length; value = inspect(value, object, options); return `${key}: ${value}`; }; const multiLineValues = (values) => { for (const value of values) { if (Array.isArray(value) || typeof value === "object" && value !== null) { return true; } } return false; }; const inspectArray = (array, options, inspect, indent) => { const nonIndexProperties = Object.keys(array).slice(array.length); if (array.length === 0 && nonIndexProperties.length === 0) { return "[]"; } options.truncate -= 4; let listContents = inspectList(array, array, options, inspect); options.truncate -= listContents.length; let propertyContents = ""; if (nonIndexProperties.length > 0) { propertyContents = inspectList( nonIndexProperties.map((key) => [key, array[key]]), array, options, inspect, inspectProperty ); } const hasIndent = indent && multiLineValues(array); if (hasIndent) { listContents = indentedJoin(listContents, indent); } return `[${hasIndent ? "" : " "}${listContents}${propertyContents ? `, ${propertyContents}` : ""}${hasIndent ? "" : " "}]`; }; const addNumericSeparator = (number_, string_) => { if (number_ === Number.POSITIVE_INFINITY || number_ === Number.NEGATIVE_INFINITY || string_.includes("e")) { return string_; } const separatorRegex = /\d(?=(?:\d{3})+(?!\d))/g; if (typeof number_ === "number") { const int = number_ < 0 ? -Math.floor(-number_) : Math.floor(number_); if (int !== number_) { const intString = String(int); const dec = string_.slice(intString.length + 1); return ( // eslint-disable-next-line unicorn/prefer-string-replace-all `${intString.replace(separatorRegex, "$&_")}.${dec.replace(/\d{3}/g, "$&_").replace(/_$/, "")}` ); } } return string_.replace(separatorRegex, "$&_"); }; const isHighSurrogate = (char) => char >= "\uD800" && char <= "\uDBFF"; const truncate = (string, length, tail = TRUNCATOR) => { string = String(string); const tailLength = tail.length; const stringLength = string.length; if (tailLength > length && stringLength > tailLength) { return tail; } if (stringLength > length && stringLength > tailLength) { let end = length - tailLength; if (end > 0 && isHighSurrogate(string[end - 1])) { end -= 1; } return `${string.slice(0, end)}${tail}`; } return string; }; const inspectBigInt = (number, options) => { let nums = truncate(number.toString(), options.truncate - 1); if (nums !== TRUNCATOR) { nums += "n"; } return options.stylize(options.numericSeparator ? addNumericSeparator(number, nums) : nums, "bigint"); }; const gPO = (typeof Reflect === "function" ? Reflect.getPrototypeOf : Object.getPrototypeOf) || ([].__proto__ === Array.prototype ? function(O) { return O.__proto__; } : null); const inspectObject$1 = (object, options, inspect, indent) => { if (globalThis.window !== void 0 && object === globalThis) { return "{ [object Window] }"; } if (typeof globalThis !== "undefined" && object === globalThis || globalThis.global !== void 0 && object === globalThis) { return "{ [object globalThis] }"; } const properties = Object.getOwnPropertyNames(object); const symbols = Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(object) : []; const isPlainObject = gPO(object) === Object.prototype || object.constructor === Object; const protoTag = object instanceof Object ? "" : "null prototype"; const stringTag = !isPlainObject && typeof Symbol !== "undefined" && Symbol.toStringTag in object ? object[Symbol.toStringTag] : protoTag ? "Object" : ""; const tag = stringTag || protoTag ? `[${[stringTag, protoTag].filter(Boolean).join(": ")}] ` : ""; if (properties.length === 0 && symbols.length === 0) { return `${tag}{}`; } options.truncate -= 4; const propertyContents = inspectList( properties.map((key) => [key, object[key]]), object, options, inspect, inspectProperty ); const symbolContents = inspectList( symbols.map((key) => [key, object[key]]), object, options, inspect, inspectProperty ); let separator = ""; if (propertyContents && symbolContents) { separator = ", "; } if (indent) { return `${tag}{${indentedJoin(propertyContents + separator + symbolContents, indent)}}`; } return `${tag}{ ${propertyContents}${separator}${symbolContents} }`; }; const inspectClass = (value, options, inspect, indent) => { let name = ""; name = name || value.constructor.name; if (!name || name === "_class") { name = "<Anonymous Class>"; } options.truncate -= name.length; return `${name} ${inspectObject$1(value, options, inspect, indent)}`; }; const inspectDate = (dateObject, options) => { const stringRepresentation = dateObject.toJSON(); if (stringRepresentation === null) { return "Invalid Date"; } const split = stringRepresentation.split("T"); const date = split[0]; return options.stylize(`${date}T${truncate(split[1], options.truncate - date.length - 1)}`, "date"); }; const errorKeys = /* @__PURE__ */ new Set(["column", "columnNumber", "description", "fileName", "line", "lineNumber", "message", "name", "number", "stack"]); const inspectObject = (error, options, inspect) => { const properties = Object.getOwnPropertyNames(error).filter((key) => !errorKeys.has(key)); const { name } = error; options.truncate -= name.length; let message = ""; if (typeof error.message === "string") { message = truncate(error.message, options.truncate); } else { properties.unshift("message"); } message = message ? `: ${message}` : ""; options.truncate -= message.length + 5; const propertyContents = inspectList( properties.map((key) => [key, error[key]]), error, options, inspect, inspectProperty ); return `${name}${message}${propertyContents ? ` { ${propertyContents} }` : ""}`; }; const inspectFunction = (function_, options) => { const functionType = function_[Symbol.toStringTag] || "Function"; const source = function_.toString(); if (source.length < options.truncate - 12) { return options.stylize(`[${functionType}: ${source}]`, "special"); } const { name } = function_; if (!name) { return options.stylize(`[${functionType}]`, "special"); } return options.stylize(`[${functionType} ${truncate(name, options.truncate - 11)}]`, "special"); }; const inspectMapEntry = ([key, value], object, options, inspect) => { options.truncate -= 4; key = inspect(key, object, options); options.truncate -= key.length; return `${key} => ${inspect(value, object, options)}`; }; const inspectMap = (map, options, inspect, indent) => { if (map.size <= 0) { return "Map (0) {}"; } options.truncate -= 7; let returnValue = inspectList([...map.entries()], map, options, inspect, inspectMapEntry); if (indent) { returnValue = indentedJoin(returnValue, indent); } return `Map (${map.size}) {${indent ? "" : " "}${returnValue}${indent ? "" : " "}}`; }; function inspectNumber(number, options) { if (Number.isNaN(number)) { return options.stylize("NaN", "number"); } if (number === Number.POSITIVE_INFINITY) { return options.stylize("Infinity", "number"); } if (number === Number.NEGATIVE_INFINITY) { return options.stylize("-Infinity", "number"); } if (number === 0) { return options.stylize(1 / number === Number.POSITIVE_INFINITY ? "+0" : "-0", "number"); } return options.stylize(truncate(options.numericSeparator ? addNumericSeparator(number, number.toString()) : number.toString(), options.truncate), "number"); } const getPromiseValue = () => "Promise{…}"; const inspectRegExp = (regExp, options) => { const sourceLength = options.truncate - (2 + regExp.flags.length); return options.stylize(`/${truncate(regExp.source, sourceLength)}/${regExp.flags}`, "regexp"); }; const inspectSet = (set, options, inspect, indent) => { if (set.size === 0) { return "Set (0) {}"; } options.truncate -= 7; let returnValue = inspectList([...set], set, options, inspect); if (indent) { returnValue = indentedJoin(returnValue, indent); } return `Set (${set.size}) {${indent ? "" : " "}${returnValue}${indent ? "" : " "}}`; }; const wrapQuotes = (string_, options) => { const quoteChar = options.quoteStyle === "double" ? '"' : "'"; if (options.quoteStyle === "double") { string_ = string_.replaceAll('"', '"'); } return quoteChar + string_ + quoteChar; }; const stringEscapeChars = new RegExp( String.raw`['\0-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5` + String.raw`\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]`, "g" ); const escapeCharacters = { " ": String.raw`\t`, "\n": String.raw`\n`, "\f": String.raw`\f`, "\r": String.raw`\r`, "\b": String.raw`\b`, "'": String.raw`\'`, "\\": "\\\\" }; const hex = 16; const escape = (char) => escapeCharacters[char] || `\\u${`0000${char.codePointAt(0).toString(hex)}`.slice(-4)}`; const inspectString = (string_, options) => { if (stringEscapeChars.test(string_)) { string_ = string_.replaceAll(stringEscapeChars, escape); } return options.stylize(wrapQuotes(truncate(string_, options.truncate - 2), options), "string"); }; const inspectSymbol = (value) => { if ("description" in Symbol.prototype) { return value.description ? `Symbol(${value.description})` : "Symbol()"; } return value.toString(); }; const getArrayName = (array) => { if (typeof Buffer === "function" && array instanceof Buffer) { return "Buffer"; } if (array[Symbol.toStringTag]) { return array[Symbol.toStringTag]; } return array.constructor.name; }; const inspectTypedArray = (array, options, inspect) => { const name = getArrayName(array); options.truncate -= name.length + 4; const nonIndexProperties = Object.keys(array).slice(array.length); if (array.length === 0 && nonIndexProperties.length === 0) { return `${name}[]`; } let output = ""; for (let index = 0; index < array.length; index++) { const string = `${options.stylize(truncate(array[index], options.truncate), "number")}${index === array.length - 1 ? "" : ", "}`; options.truncate -= string.length; if (array[index] !== array.length && options.truncate <= 3) { output += `${TRUNCATOR}(${array.length - array[index] + 1})`; break; } output += string; } let propertyContents = ""; if (nonIndexProperties.length > 0) { propertyContents = inspectList( nonIndexProperties.map((key) => [key, array[key]]), array, options, inspect, inspectProperty ); } return `${name}[ ${output}${propertyContents ? `, ${propertyContents}` : ""} ]`; }; const constructorMap = /* @__PURE__ */ new WeakMap(); const stringTagMap = {}; const baseTypesMap = { Arguments: inspectArguments, Array: inspectArray, ArrayBuffer: () => "", BigInt: inspectBigInt, bigint: inspectBigInt, Boolean: (value, options) => options.stylize(String(value), "boolean"), boolean: (value, options) => options.stylize(String(value), "boolean"), DataView: () => "", Date: inspectDate, Error: inspectObject, Float32Array: inspectTypedArray, Float64Array: inspectTypedArray, Function: inspectFunction, function: inspectFunction, Generator: () => "", HTMLCollection: inspectNodeCollection, Int8Array: inspectTypedArray, Int16Array: inspectTypedArray, Int32Array: inspectTypedArray, Map: inspectMap, NodeList: inspectNodeCollection, null: (_value, options) => options.stylize("null", "null"), Number: inspectNumber, number: inspectNumber, Promise: getPromiseValue, RegExp: inspectRegExp, Set: inspectSet, String: inspectString, string: inspectString, // A Symbol polyfill will return `Symbol` not `symbol` from typedetect Symbol: inspectSymbol, symbol: inspectSymbol, Uint8Array: inspectTypedArray, Uint8ClampedArray: inspectTypedArray, Uint16Array: inspectTypedArray, Uint32Array: inspectTypedArray, undefined: (_value, options) => options.stylize("undefined", "undefined"), // eslint-disable-next-line @typescript-eslint/no-explicit-any WeakMap: (_value, options) => options.stylize("WeakMap{…}", "special"), // WeakSet, WeakMap are totally opaque to us // eslint-disable-next-line @typescript-eslint/no-explicit-any WeakSet: (_value, options) => options.stylize("WeakSet{…}", "special") }; const inspectCustom = (value, options, type, depth) => { if (globalThis.window === void 0 && typeof value[Symbol.for("nodejs.util.inspect.custom")] === "function") { return value[Symbol.for("nodejs.util.inspect.custom")](depth, options); } if ("inspect" in value && typeof value.inspect === "function") { return value.inspect(depth, options); } if ("constructor" in value && constructorMap.has(value.constructor)) { return constructorMap.get(value.constructor)?.(value, options) ?? "unknown"; } if (stringTagMap[type]) { return stringTagMap[type](value, options); } return ""; }; const internalInspect = (value, options, depth, seen) => { if (seen.includes(value)) { return "[Circular]"; } if (depth >= options.depth && options.depth > 0 && typeof value === "object") { return Array.isArray(value) ? "[Array]" : "[Object]"; } const inspect2 = (object, from, options2) => { if (from) { seen = [...seen]; seen.push(from); } return internalInspect(object, options2, depth + 1, seen); }; const indent = options.indent ? getIndent(options.indent, depth) : void 0; let type = value === null ? "null" : typeof value; if (type === "object") { type = Object.prototype.toString.call(value).slice(8, -1); } if (baseTypesMap[type] !== void 0) { return baseTypesMap[type](value, options, inspect2, indent); } if (options.customInspect && value) { const output = inspectCustom(value, options, type, options.depth - depth); if (output) { if (typeof output === "string") { return output; } return inspect2(output, value, options); } } const proto = value ? Object.getPrototypeOf(value) : false; if (proto === Object.prototype || proto === null) { return inspectObject$1(value, options, inspect2, indent); } if (value && typeof HTMLElement === "function" && value instanceof HTMLElement) { return inspectHTMLElement(value, value, options, inspect2); } if ("constructor" in value) { if (value.constructor !== Object) { return inspectClass(value, options, inspect2, indent); } return inspectObject$1(value, options, inspect2, indent); } if (value === Object(value)) { return inspectObject$1(value, options, inspect2, indent); } return options.stylize(String(value), type); }; const inspect = (value, options_ = {}) => { const options = { breakLength: Number.POSITIVE_INFINITY, customInspect: true, depth: 5, indent: void 0, maxArrayLength: Number.POSITIVE_INFINITY, numericSeparator: true, quoteStyle: "single", showHidden: false, showProxy: false, stylize: (s) => s.toString(), truncate: Number.POSITIVE_INFINITY, ...options_ }; if (options.indent !== void 0 && options.indent !== " " && !(Number.parseInt(options.indent, 10) === options.indent && options.indent > 0)) { throw new TypeError('option "indent" must be "\\t", an integer > 0, or `undefined`'); } return internalInspect(value, options, 0, []); }; const registerConstructor = (constructor, inspector) => { if (constructorMap.has(constructor)) { return false; } constructorMap.set(constructor, inspector); return true; }; const registerStringTag = (stringTag, inspector) => { if (stringTag in stringTagMap) { return false; } stringTagMap[stringTag] = inspector; return true; }; exports.inspect = inspect; exports.registerConstructor = registerConstructor; exports.registerStringTag = registerStringTag;