char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport console extension to typescript1738b3c

main
10.6 KiB342 linesraw
1import {
2  ArrayIsArray,
3  ArrayPrototypeJoin,
4  ArrayPrototypePush,
5  DatePrototype,
6  DatePrototypeGetTime,
7  DatePrototypeToISOString,
8  ErrorGetStack,
9  ErrorPrototype,
10  JSONStringify,
11  MapIteratorPrototypeNext,
12  MapPrototype,
13  MapPrototypeEntries,
14  MathMin,
15  NumberIsFinite,
16  ObjectGetOwnPropertyDescriptor,
17  ObjectGetPrototypeOf,
18  ReflectApply,
19  ReflectOwnKeys,
20  RegExpPrototype,
21  RegExpPrototypeExec,
22  RegExpPrototypeGetFlags,
23  RegExpPrototypeGetSource,
24  Set,
25  SetIteratorPrototypeNext,
26  SetPrototype,
27  SetPrototypeAdd,
28  SetPrototypeDelete,
29  SetPrototypeHas,
30  SetPrototypeValues,
31  String,
32  StringPrototypeReplaceAll,
33  SymbolFor,
34  SymbolToStringTag,
35  TypedArrayPrototypeGetLength,
36  TypedArrayPrototypeGetTag,
37  TypeError,
38} from "ext:core/primordials";
39import { promiseState } from "ext:core/promise_hooks";
40const identifier = /^[A-Za-z_$][\w$]*$/, newline = /\n/;
41const customInspect = SymbolFor("runtime.avril.inspect");
42// Values whose custom inspector is running; inspecting one again from inside
43// its own inspector falls back to the default format instead of recursing.
44const customizing = new Set<object>();
45
46type Context = { colors: boolean };
47
48function keyName(key: string | symbol): string {
49  if (typeof key === "symbol") return `[${String(key)}]`;
50  return RegExpPrototypeExec(identifier, key) ? key : JSONStringify(key);
51}
52
53function prototypes<T>(
54  value: object | null,
55  visit: (prototype: object) => T | undefined,
56): T | undefined {
57  let prototype = value;
58  for (let index = 0; prototype !== null && index < 20; index++) {
59    const result = visit(prototype);
60    if (result !== undefined) return result;
61    prototype = ObjectGetPrototypeOf(prototype);
62  }
63}
64
65function intrinsicPrototype(value: object): object | null {
66  return prototypes(
67    ObjectGetPrototypeOf(value),
68    (prototype) =>
69      prototype === ErrorPrototype || prototype === DatePrototype ||
70        prototype === RegExpPrototype ||
71        prototype === MapPrototype || prototype === SetPrototype
72        ? prototype
73        : undefined,
74  ) ?? null;
75}
76
77/**
78 * Data properties only, so naming an object never runs its getters. The
79 * descriptor's own `value` decides, since a data descriptor inherits any
80 * `get` or `set` added to Object.prototype.
81 */
82function data(value: object, key: PropertyKey): unknown {
83  return prototypes(value, (prototype) => {
84    const descriptor = ObjectGetOwnPropertyDescriptor(prototype, key);
85    return descriptor && ObjectGetOwnPropertyDescriptor(descriptor, "value");
86  })?.value;
87}
88
89/**
90 * Names objects that are not plain, as `Class`, `Class [Tag]`, or `[Tag]`
91 * without a constructor; empty for plain and null-prototype objects.
92 */
93function label(value: object): string {
94  const constructor = data(value, "constructor");
95  let name: unknown = typeof constructor === "function"
96    ? ObjectGetOwnPropertyDescriptor(constructor, "name")?.value
97    : undefined;
98  if (typeof name !== "string" || !name) name = undefined;
99  let tag = data(value, SymbolToStringTag);
100  if (typeof tag !== "string" || !tag || tag === name) tag = undefined;
101  if (name === undefined) return tag === undefined ? "" : `[${tag}]`;
102  if (name === "Object" && tag === undefined) return "";
103  return tag === undefined ? name as string : `${name} [${tag}]`;
104}
105
106function customInspector(value: object): Function | undefined {
107  const method: unknown = prototypes(
108    value,
109    (prototype) => ObjectGetOwnPropertyDescriptor(prototype, customInspect),
110  )?.value;
111  return typeof method === "function" ? method : undefined;
112}
113
114function style(
115  context: Context,
116  open: number,
117  close: number,
118  text: string,
119): string {
120  return context.colors ? `\x1b[${open}m${text}\x1b[${close}m` : text;
121}
122
123const special = (context: Context, text: string): string =>
124  style(context, 36, 39, text);
125
126function inspect(value: unknown, options: unknown = undefined): string {
127  if (
128    options !== undefined && options !== null &&
129    typeof options !== "object" && typeof options !== "function"
130  ) {
131    throw new TypeError("Inspect options must be an object");
132  }
133  const depth = (options as { depth?: unknown } | null | undefined)?.depth;
134  return inspectValue(
135    value,
136    NumberIsFinite(depth)
137      ? MathMin(depth as number, 10)
138      : depth === null || depth === Infinity
139      ? 10
140      : 3,
141    { colors: !!(options as { colors?: unknown } | null | undefined)?.colors },
142    new Set(),
143  );
144}
145
146function inspectValue(
147  input: unknown,
148  depth: number,
149  context: Context,
150  seen: Set<object>,
151): string {
152  switch (typeof input) {
153    case "string":
154      return style(context, 32, 39, JSONStringify(input));
155    case "number":
156    case "boolean":
157      return style(context, 33, 39, String(input));
158    case "bigint":
159      return style(context, 33, 39, `${String(input)}n`);
160    case "symbol":
161      return style(context, 32, 39, String(input));
162    case "undefined":
163      return style(context, 90, 39, "undefined");
164    case "object":
165      if (input === null) return style(context, 1, 22, "null");
166  }
167  const value = input as object;
168  if (SetPrototypeHas(seen, value)) return special(context, "[Circular]");
169  SetPrototypeAdd(seen, value);
170  try {
171    const custom = SetPrototypeHas(customizing, value)
172      ? undefined
173      : customInspector(value);
174    if (custom) {
175      SetPrototypeAdd(customizing, value);
176      try {
177        const result = ReflectApply(custom, value, [inspect, {
178          depth,
179          colors: context.colors,
180        }]);
181        return typeof result === "string"
182          ? result
183          : inspectValue(result, depth, context, seen);
184      } finally {
185        SetPrototypeDelete(customizing, value);
186      }
187    }
188    if (typeof value === "function") {
189      const name = ObjectGetOwnPropertyDescriptor(value, "name")?.value;
190      return special(
191        context,
192        typeof name === "string" && name ? `[Function: ${name}]` : "[Function]",
193      );
194    }
195    const prototype = intrinsicPrototype(value);
196    if (prototype === ErrorPrototype) {
197      const stack = ErrorGetStack(value);
198      return typeof stack === "string" ? stack : "[Error]";
199    }
200    if (prototype === DatePrototype) {
201      return style(
202        context,
203        35,
204        39,
205        NumberIsFinite(DatePrototypeGetTime(value))
206          ? DatePrototypeToISOString(value)
207          : "Invalid Date",
208      );
209    }
210    if (prototype === RegExpPrototype) {
211      return style(
212        context,
213        31,
214        39,
215        `/${RegExpPrototypeGetSource(value)}/${RegExpPrototypeGetFlags(value)}`,
216      );
217    }
218    if (prototype === MapPrototype || prototype === SetPrototype) {
219      const isMap = prototype === MapPrototype;
220      if (depth < 0) return special(context, isMap ? "[Map]" : "[Set]");
221      const iterator = isMap
222        ? MapPrototypeEntries(value as Map<unknown, unknown>)
223        : SetPrototypeValues(value as Set<unknown>);
224      const next = isMap ? MapIteratorPrototypeNext : SetIteratorPrototypeNext;
225      const entries: string[] = [];
226      for (let index = 0; index < 50; index++) {
227        const step = next(iterator);
228        if (step.done) break;
229        ArrayPrototypePush(
230          entries,
231          isMap
232            ? `${
233              inspectValue(
234                (step.value as [unknown, unknown])[0],
235                depth - 1,
236                context,
237                seen,
238              )
239            } => ${
240              inspectValue(
241                (step.value as [unknown, unknown])[1],
242                depth - 1,
243                context,
244                seen,
245              )
246            }`
247            : inspectValue(step.value, depth - 1, context, seen),
248        );
249      }
250      if (!next(iterator).done) ArrayPrototypePush(entries, "...");
251      return `${isMap ? "Map" : "Set"} ${collection("{", entries, "}")}`;
252    }
253    const state = promiseState(value);
254    if (state !== undefined) {
255      const name = label(value) || "Promise";
256      if (depth < 0) return special(context, `[${name}]`);
257      const content = state[0] === 0
258        ? special(context, "<pending>")
259        : `${state[0] === 2 ? `${special(context, "<rejected>")} ` : ""}${
260          inspectValue(state[1], depth - 1, context, seen)
261        }`;
262      // Multiline content, such as an error's stack, gets its own lines.
263      return RegExpPrototypeExec(newline, content)
264        ? `${name} {\n  ${StringPrototypeReplaceAll(content, "\n", "\n  ")}\n}`
265        : `${name} { ${content} }`;
266    }
267    const isArray = ArrayIsArray(value);
268    const typedArray = TypedArrayPrototypeGetTag(value);
269    const name = isArray || typedArray ? "" : label(value);
270    if (depth < 0) {
271      return special(
272        context,
273        isArray ? "[Array]" : name[0] === "[" ? name : `[${name || "Object"}]`,
274      );
275    }
276    const entries: string[] = [];
277    if (isArray || typedArray) {
278      const length = isArray
279        ? ObjectGetOwnPropertyDescriptor(value, "length")!.value as number
280        : TypedArrayPrototypeGetLength(value);
281      for (let index = 0; index < MathMin(length, 50); index++) {
282        ArrayPrototypePush(
283          entries,
284          property(value, String(index), depth - 1, context, seen),
285        );
286      }
287      if (length > 50) {
288        ArrayPrototypePush(entries, `... ${length - 50} more items`);
289      }
290      return `${typedArray ? `${typedArray}(${length}) ` : ""}${
291        collection("[", entries, "]")
292      }`;
293    }
294    const keys = ReflectOwnKeys(value);
295    for (let index = 0; index < keys.length; index++) {
296      const key = keys[index];
297      const descriptor = ObjectGetOwnPropertyDescriptor(value, key);
298      if (!descriptor?.enumerable) continue;
299      if (entries.length === 50) {
300        ArrayPrototypePush(entries, "...");
301        break;
302      }
303      ArrayPrototypePush(
304        entries,
305        `${keyName(key)}: ${property(value, key, depth - 1, context, seen)}`,
306      );
307    }
308    return `${name ? `${name} ` : ""}${collection("{", entries, "}")}`;
309  } catch {
310    return special(context, "[Uninspectable]");
311  } finally {
312    SetPrototypeDelete(seen, value);
313  }
314}
315
316function collection(open: string, entries: string[], close: string): string {
317  return `${open}${
318    entries.length ? ` ${ArrayPrototypeJoin(entries, ", ")} ` : ""
319  }${close}`;
320}
321
322function property(
323  object: object,
324  key: PropertyKey,
325  depth: number,
326  context: Context = { colors: false },
327  seen: Set<object> = new Set(),
328): string {
329  const descriptor = ObjectGetOwnPropertyDescriptor(object, key);
330  if (!descriptor) return style(context, 90, 39, "<empty>");
331  if (!ObjectGetOwnPropertyDescriptor(descriptor, "value")) {
332    return special(
333      context,
334      descriptor.get
335        ? (descriptor.set ? "[Getter/Setter]" : "[Getter]")
336        : "[Setter]",
337    );
338  }
339  return inspectValue(descriptor.value, depth, context, seen);
340}
341
342export { inspect, intrinsicPrototype, property };