import { ArrayIsArray, ArrayPrototypeJoin, ArrayPrototypePush, DatePrototype, DatePrototypeGetTime, DatePrototypeToISOString, ErrorGetStack, ErrorPrototype, JSONStringify, MapIteratorPrototypeNext, MapPrototype, MapPrototypeEntries, MathMin, NumberIsFinite, ObjectGetOwnPropertyDescriptor, ObjectGetPrototypeOf, ReflectApply, ReflectOwnKeys, RegExpPrototype, RegExpPrototypeExec, RegExpPrototypeGetFlags, RegExpPrototypeGetSource, Set, SetIteratorPrototypeNext, SetPrototype, SetPrototypeAdd, SetPrototypeDelete, SetPrototypeHas, SetPrototypeValues, String, StringPrototypeReplaceAll, SymbolFor, SymbolToStringTag, TypedArrayPrototypeGetLength, TypedArrayPrototypeGetTag, TypeError, } from "ext:core/primordials"; import { promiseState } from "ext:core/promise_hooks"; const identifier = /^[A-Za-z_$][\w$]*$/, newline = /\n/; const customInspect = SymbolFor("runtime.avril.inspect"); // Values whose custom inspector is running; inspecting one again from inside // its own inspector falls back to the default format instead of recursing. const customizing = new Set(); type Context = { colors: boolean }; function keyName(key: string | symbol): string { if (typeof key === "symbol") return `[${String(key)}]`; return RegExpPrototypeExec(identifier, key) ? key : JSONStringify(key); } function prototypes( value: object | null, visit: (prototype: object) => T | undefined, ): T | undefined { let prototype = value; for (let index = 0; prototype !== null && index < 20; index++) { const result = visit(prototype); if (result !== undefined) return result; prototype = ObjectGetPrototypeOf(prototype); } } function intrinsicPrototype(value: object): object | null { return prototypes( ObjectGetPrototypeOf(value), (prototype) => prototype === ErrorPrototype || prototype === DatePrototype || prototype === RegExpPrototype || prototype === MapPrototype || prototype === SetPrototype ? prototype : undefined, ) ?? null; } /** * Data properties only, so naming an object never runs its getters. The * descriptor's own `value` decides, since a data descriptor inherits any * `get` or `set` added to Object.prototype. */ function data(value: object, key: PropertyKey): unknown { return prototypes(value, (prototype) => { const descriptor = ObjectGetOwnPropertyDescriptor(prototype, key); return descriptor && ObjectGetOwnPropertyDescriptor(descriptor, "value"); })?.value; } /** * Names objects that are not plain, as `Class`, `Class [Tag]`, or `[Tag]` * without a constructor; empty for plain and null-prototype objects. */ function label(value: object): string { const constructor = data(value, "constructor"); let name: unknown = typeof constructor === "function" ? ObjectGetOwnPropertyDescriptor(constructor, "name")?.value : undefined; if (typeof name !== "string" || !name) name = undefined; let tag = data(value, SymbolToStringTag); if (typeof tag !== "string" || !tag || tag === name) tag = undefined; if (name === undefined) return tag === undefined ? "" : `[${tag}]`; if (name === "Object" && tag === undefined) return ""; return tag === undefined ? name as string : `${name} [${tag}]`; } function customInspector(value: object): Function | undefined { const method: unknown = prototypes( value, (prototype) => ObjectGetOwnPropertyDescriptor(prototype, customInspect), )?.value; return typeof method === "function" ? method : undefined; } function style( context: Context, open: number, close: number, text: string, ): string { return context.colors ? `\x1b[${open}m${text}\x1b[${close}m` : text; } const special = (context: Context, text: string): string => style(context, 36, 39, text); function inspect(value: unknown, options: unknown = undefined): string { if ( options !== undefined && options !== null && typeof options !== "object" && typeof options !== "function" ) { throw new TypeError("Inspect options must be an object"); } const depth = (options as { depth?: unknown } | null | undefined)?.depth; return inspectValue( value, NumberIsFinite(depth) ? MathMin(depth as number, 10) : depth === null || depth === Infinity ? 10 : 3, { colors: !!(options as { colors?: unknown } | null | undefined)?.colors }, new Set(), ); } function inspectValue( input: unknown, depth: number, context: Context, seen: Set, ): string { switch (typeof input) { case "string": return style(context, 32, 39, JSONStringify(input)); case "number": case "boolean": return style(context, 33, 39, String(input)); case "bigint": return style(context, 33, 39, `${String(input)}n`); case "symbol": return style(context, 32, 39, String(input)); case "undefined": return style(context, 90, 39, "undefined"); case "object": if (input === null) return style(context, 1, 22, "null"); } const value = input as object; if (SetPrototypeHas(seen, value)) return special(context, "[Circular]"); SetPrototypeAdd(seen, value); try { const custom = SetPrototypeHas(customizing, value) ? undefined : customInspector(value); if (custom) { SetPrototypeAdd(customizing, value); try { const result = ReflectApply(custom, value, [inspect, { depth, colors: context.colors, }]); return typeof result === "string" ? result : inspectValue(result, depth, context, seen); } finally { SetPrototypeDelete(customizing, value); } } if (typeof value === "function") { const name = ObjectGetOwnPropertyDescriptor(value, "name")?.value; return special( context, typeof name === "string" && name ? `[Function: ${name}]` : "[Function]", ); } const prototype = intrinsicPrototype(value); if (prototype === ErrorPrototype) { const stack = ErrorGetStack(value); return typeof stack === "string" ? stack : "[Error]"; } if (prototype === DatePrototype) { return style( context, 35, 39, NumberIsFinite(DatePrototypeGetTime(value)) ? DatePrototypeToISOString(value) : "Invalid Date", ); } if (prototype === RegExpPrototype) { return style( context, 31, 39, `/${RegExpPrototypeGetSource(value)}/${RegExpPrototypeGetFlags(value)}`, ); } if (prototype === MapPrototype || prototype === SetPrototype) { const isMap = prototype === MapPrototype; if (depth < 0) return special(context, isMap ? "[Map]" : "[Set]"); const iterator = isMap ? MapPrototypeEntries(value as Map) : SetPrototypeValues(value as Set); const next = isMap ? MapIteratorPrototypeNext : SetIteratorPrototypeNext; const entries: string[] = []; for (let index = 0; index < 50; index++) { const step = next(iterator); if (step.done) break; ArrayPrototypePush( entries, isMap ? `${ inspectValue( (step.value as [unknown, unknown])[0], depth - 1, context, seen, ) } => ${ inspectValue( (step.value as [unknown, unknown])[1], depth - 1, context, seen, ) }` : inspectValue(step.value, depth - 1, context, seen), ); } if (!next(iterator).done) ArrayPrototypePush(entries, "..."); return `${isMap ? "Map" : "Set"} ${collection("{", entries, "}")}`; } const state = promiseState(value); if (state !== undefined) { const name = label(value) || "Promise"; if (depth < 0) return special(context, `[${name}]`); const content = state[0] === 0 ? special(context, "") : `${state[0] === 2 ? `${special(context, "")} ` : ""}${ inspectValue(state[1], depth - 1, context, seen) }`; // Multiline content, such as an error's stack, gets its own lines. return RegExpPrototypeExec(newline, content) ? `${name} {\n ${StringPrototypeReplaceAll(content, "\n", "\n ")}\n}` : `${name} { ${content} }`; } const isArray = ArrayIsArray(value); const typedArray = TypedArrayPrototypeGetTag(value); const name = isArray || typedArray ? "" : label(value); if (depth < 0) { return special( context, isArray ? "[Array]" : name[0] === "[" ? name : `[${name || "Object"}]`, ); } const entries: string[] = []; if (isArray || typedArray) { const length = isArray ? ObjectGetOwnPropertyDescriptor(value, "length")!.value as number : TypedArrayPrototypeGetLength(value); for (let index = 0; index < MathMin(length, 50); index++) { ArrayPrototypePush( entries, property(value, String(index), depth - 1, context, seen), ); } if (length > 50) { ArrayPrototypePush(entries, `... ${length - 50} more items`); } return `${typedArray ? `${typedArray}(${length}) ` : ""}${ collection("[", entries, "]") }`; } const keys = ReflectOwnKeys(value); for (let index = 0; index < keys.length; index++) { const key = keys[index]; const descriptor = ObjectGetOwnPropertyDescriptor(value, key); if (!descriptor?.enumerable) continue; if (entries.length === 50) { ArrayPrototypePush(entries, "..."); break; } ArrayPrototypePush( entries, `${keyName(key)}: ${property(value, key, depth - 1, context, seen)}`, ); } return `${name ? `${name} ` : ""}${collection("{", entries, "}")}`; } catch { return special(context, "[Uninspectable]"); } finally { SetPrototypeDelete(seen, value); } } function collection(open: string, entries: string[], close: string): string { return `${open}${ entries.length ? ` ${ArrayPrototypeJoin(entries, ", ")} ` : "" }${close}`; } function property( object: object, key: PropertyKey, depth: number, context: Context = { colors: false }, seen: Set = new Set(), ): string { const descriptor = ObjectGetOwnPropertyDescriptor(object, key); if (!descriptor) return style(context, 90, 39, ""); if (!ObjectGetOwnPropertyDescriptor(descriptor, "value")) { return special( context, descriptor.get ? (descriptor.set ? "[Getter/Setter]" : "[Getter]") : "[Setter]", ); } return inspectValue(descriptor.value, depth, context, seen); } export { inspect, intrinsicPrototype, property };