@visulima/inspector
Version:
Inspect utility for Node.js and Browsers.
607 lines (579 loc) • 20.9 kB
JavaScript
;
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;