Skip to content
File

Blob: src/node/internal/internal_inspect.ts

typescript3146 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Adapted from Deno, Node.js and DefinitelyTyped:
6// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
7//
8// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
9//
10// Permission is hereby granted, free of charge, to any person obtaining a
11// copy of this software and associated documentation files (the
12// "Software"), to deal in the Software without restriction, including
13// without limitation the rights to use, copy, modify, merge, publish,
14// distribute, sublicense, and/or sell copies of the Software, and to permit
15// persons to whom the Software is furnished to do so, subject to the
16// following conditions:
17//
18// The above copyright notice and this permission notice shall be included
19// in all copies or substantial portions of the Software.
20//
21// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
24// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
25// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
26// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
27// USE OR OTHER DEALINGS IN THE SOFTWARE.
28 
29/* TODO: the following is adopted code, enabling linting one day */
30/* eslint-disable */
31 
32import internalWorkers from 'cloudflare-internal:workers';
33import internal from 'node-internal:util';
34 
35import { Buffer } from 'node-internal:internal_buffer';
36import {
37 isAsyncFunction,
38 isGeneratorFunction,
39 isAnyArrayBuffer,
40 isArrayBuffer,
41 isArgumentsObject,
42 isBoxedPrimitive,
43 isDataView,
44 isMap,
45 isMapIterator,
46 isModuleNamespaceObject,
47 isNativeError,
48 isPromise,
49 isSet,
50 isSetIterator,
51 isWeakMap,
52 isWeakSet,
53 isRegExp,
54 isDate,
55 isTypedArray,
56 isStringObject,
57 isNumberObject,
58 isBooleanObject,
59 isBigIntObject,
60} from 'node-internal:internal_types';
61// import { ALL_PROPERTIES, ONLY_ENUMERABLE, getOwnNonIndexProperties } from "node-internal:internal_utils";
62import {
63 validateObject,
64 validateString,
65 kValidateObjectAllowArray,
66} from 'node-internal:validators';
67 
68// Simplified assertions to avoid `Assertions require every name in the call target to be
69// declared with an explicit type` TypeScript error
70function assert(value: boolean, message = 'Assertion failed'): asserts value {
71 if (!value) throw new Error(message);
72}
73assert.fail = function (message = 'Assertion failed'): never {
74 throw new Error(message);
75};
76 
77function isError(e: unknown): e is Error {
78 // An error could be an instance of Error while not being a native error
79 // or could be from a different realm and not be instance of Error but still
80 // be a native error.
81 return isNativeError(e) || e instanceof Error;
82}
83 
84const typedArrayPrototype = Object.getPrototypeOf(Uint8Array).prototype;
85const typedArrayPrototypeLength: (this: NodeJS.TypedArray) => number =
86 Object.getOwnPropertyDescriptor(typedArrayPrototype, 'length')!.get!;
87const typedArrayPrototypeToStringTag: (this: NodeJS.TypedArray) => string =
88 Object.getOwnPropertyDescriptor(
89 typedArrayPrototype,
90 Symbol.toStringTag
91 )!.get!;
92 
93const setPrototypeSize: (this: Set<unknown>) => number =
94 Object.getOwnPropertyDescriptor(Set.prototype, 'size')!.get!;
95const mapPrototypeSize: (this: Map<unknown, unknown>) => number =
96 Object.getOwnPropertyDescriptor(Map.prototype, 'size')!.get!;
97 
98let maxStack_ErrorName: string;
99let maxStack_ErrorMessage: string;
100function isStackOverflowError(err: Error): boolean {
101 if (maxStack_ErrorMessage === undefined) {
102 try {
103 function overflowStack() {
104 overflowStack();
105 }
106 overflowStack();
107 } catch (err) {
108 assert(isError(err));
109 maxStack_ErrorMessage = err.message;
110 maxStack_ErrorName = err.name;
111 }
112 }
113 
114 return (
115 err &&
116 err.name === maxStack_ErrorName &&
117 err.message === maxStack_ErrorMessage
118 );
119}
120 
121export const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');
122 
123const colorRegExp = /\u001b\[\d\d?m/g;
124 
125function removeColors(str: string): string {
126 return str.replace(colorRegExp, '');
127}
128 
129export interface InspectOptions {
130 /**
131 * If `true`, object's non-enumerable symbols and properties are included in the formatted result.
132 * `WeakMap` and `WeakSet` entries are also included as well as user defined prototype properties (excluding method properties).
133 * @default false
134 */
135 showHidden?: boolean;
136 /**
137 * Specifies the number of times to recurse while formatting object.
138 * This is useful for inspecting large objects.
139 * To recurse up to the maximum call stack size pass `Infinity` or `null`.
140 * @default 2
141 */
142 depth?: number | null;
143 /**
144 * If `true`, the output is styled with ANSI color codes. Colors are customizable.
145 */
146 colors?: boolean;
147 /**
148 * If `false`, `[util.inspect.custom](depth, opts, inspect)` functions are not invoked.
149 * @default true
150 */
151 customInspect?: boolean;
152 /**
153 * If `true`, `Proxy` inspection includes the target and handler objects.
154 * @default false
155 */
156 showProxy?: boolean;
157 /**
158 * Specifies the maximum number of `Array`, `TypedArray`, `WeakMap`, and `WeakSet` elements
159 * to include when formatting. Set to `null` or `Infinity` to show all elements.
160 * Set to `0` or negative to show no elements.
161 * @default 100
162 */
163 maxArrayLength?: number | null;
164 /**
165 * Specifies the maximum number of characters to
166 * include when formatting. Set to `null` or `Infinity` to show all elements.
167 * Set to `0` or negative to show no characters.
168 * @default 10000
169 */
170 maxStringLength?: number | null;
171 /**
172 * The length at which input values are split across multiple lines.
173 * Set to `Infinity` to format the input as a single line
174 * (in combination with `compact` set to `true` or any number >= `1`).
175 * @default 80
176 */
177 breakLength?: number;
178 /**
179 * Setting this to `false` causes each object key
180 * to be displayed on a new line. It will also add new lines to text that is
181 * longer than `breakLength`. If set to a number, the most `n` inner elements
182 * are united on a single line as long as all properties fit into
183 * `breakLength`. Short array elements are also grouped together. Note that no
184 * text will be reduced below 16 characters, no matter the `breakLength` size.
185 * For more information, see the example below.
186 * @default true
187 */
188 compact?: boolean | number;
189 /**
190 * If set to `true` or a function, all properties of an object, and `Set` and `Map`
191 * entries are sorted in the resulting string.
192 * If set to `true` the default sort is used.
193 * If set to a function, it is used as a compare function.
194 */
195 sorted?: boolean | ((a: string, b: string) => number);
196 /**
197 * If set to `true`, getters are going to be
198 * inspected as well. If set to `'get'` only getters without setter are going
199 * to be inspected. If set to `'set'` only getters having a corresponding
200 * setter are going to be inspected. This might cause side effects depending on
201 * the getter function.
202 * @default false
203 */
204 getters?: 'get' | 'set' | boolean;
205 /**
206 * If set to `true`, an underscore is used to separate every three digits in all bigints and numbers.
207 * @default false
208 */
209 numericSeparator?: boolean;
210}
211export type Style =
212 | 'special'
213 | 'number'
214 | 'bigint'
215 | 'boolean'
216 | 'undefined'
217 | 'null'
218 | 'string'
219 | 'symbol'
220 | 'date'
221 | 'regexp'
222 | 'module'
223 | 'name';
224export type CustomInspectFunction = (
225 depth: number,
226 options: InspectOptionsStylized
227) => any; // TODO: , inspect: inspect
228export interface InspectOptionsStylized extends InspectOptions {
229 stylize(text: string, styleType: Style): string;
230}
231 
232const builtInObjects = new Set(
233 Object.getOwnPropertyNames(globalThis).filter(
234 (e) => /^[A-Z][a-zA-Z0-9]+$/.exec(e) !== null
235 )
236);
237 
238// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
239const isUndetectableObject = (v: unknown): boolean =>
240 typeof v === 'undefined' && v !== undefined;
241 
242// These options must stay in sync with `getUserOptions`. So if any option will
243// be added or removed, `getUserOptions` must also be updated accordingly.
244export const inspectDefaultOptions = Object.seal({
245 showHidden: false,
246 depth: 2,
247 colors: false,
248 customInspect: true,
249 showProxy: false,
250 maxArrayLength: 100,
251 maxStringLength: 10000,
252 breakLength: 80,
253 compact: 3,
254 sorted: false,
255 getters: false,
256 numericSeparator: false,
257} as const);
258 
259const kObjectType = 0;
260const kArrayType = 1;
261const kArrayExtrasType = 2;
262 
263const strEscapeSequencesRegExp =
264 /[\x00-\x1f\x27\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
265const strEscapeSequencesReplacer =
266 /[\x00-\x1f\x27\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
267const strEscapeSequencesRegExpSingle =
268 /[\x00-\x1f\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
269const strEscapeSequencesReplacerSingle =
270 /[\x00-\x1f\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
271 
272const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
273const numberRegExp = /^(0|[1-9][0-9]*)$/;
274 
275const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
276 
277const classRegExp = /^(\s+[^(]*?)\s*{/;
278// eslint-disable-next-line node-core/no-unescaped-regexp-dot
279const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g;
280 
281const kMinLineLength = 16;
282 
283// Constants to map the iterator state.
284const kWeak = 0;
285const kIterator = 1;
286const kMapEntries = 2;
287 
288// Escaped control characters (plus the single quote and the backslash). Use
289// empty strings to fill up unused entries.
290const meta = [
291 '\\x00',
292 '\\x01',
293 '\\x02',
294 '\\x03',
295 '\\x04',
296 '\\x05',
297 '\\x06',
298 '\\x07', // x07
299 '\\b',
300 '\\t',
301 '\\n',
302 '\\x0B',
303 '\\f',
304 '\\r',
305 '\\x0E',
306 '\\x0F', // x0F
307 '\\x10',
308 '\\x11',
309 '\\x12',
310 '\\x13',
311 '\\x14',
312 '\\x15',
313 '\\x16',
314 '\\x17', // x17
315 '\\x18',
316 '\\x19',
317 '\\x1A',
318 '\\x1B',
319 '\\x1C',
320 '\\x1D',
321 '\\x1E',
322 '\\x1F', // x1F
323 '',
324 '',
325 '',
326 '',
327 '',
328 '',
329 '',
330 "\\'",
331 '',
332 '',
333 '',
334 '',
335 '',
336 '',
337 '',
338 '', // x2F
339 '',
340 '',
341 '',
342 '',
343 '',
344 '',
345 '',
346 '',
347 '',
348 '',
349 '',
350 '',
351 '',
352 '',
353 '',
354 '', // x3F
355 '',
356 '',
357 '',
358 '',
359 '',
360 '',
361 '',
362 '',
363 '',
364 '',
365 '',
366 '',
367 '',
368 '',
369 '',
370 '', // x4F
371 '',
372 '',
373 '',
374 '',
375 '',
376 '',
377 '',
378 '',
379 '',
380 '',
381 '',
382 '',
383 '\\\\',
384 '',
385 '',
386 '', // x5F
387 '',
388 '',
389 '',
390 '',
391 '',
392 '',
393 '',
394 '',
395 '',
396 '',
397 '',
398 '',
399 '',
400 '',
401 '',
402 '', // x6F
403 '',
404 '',
405 '',
406 '',
407 '',
408 '',
409 '',
410 '',
411 '',
412 '',
413 '',
414 '',
415 '',
416 '',
417 '',
418 '\\x7F', // x7F
419 '\\x80',
420 '\\x81',
421 '\\x82',
422 '\\x83',
423 '\\x84',
424 '\\x85',
425 '\\x86',
426 '\\x87', // x87
427 '\\x88',
428 '\\x89',
429 '\\x8A',
430 '\\x8B',
431 '\\x8C',
432 '\\x8D',
433 '\\x8E',
434 '\\x8F', // x8F
435 '\\x90',
436 '\\x91',
437 '\\x92',
438 '\\x93',
439 '\\x94',
440 '\\x95',
441 '\\x96',
442 '\\x97', // x97
443 '\\x98',
444 '\\x99',
445 '\\x9A',
446 '\\x9B',
447 '\\x9C',
448 '\\x9D',
449 '\\x9E',
450 '\\x9F', // x9F
451];
452 
453// Regex used for ansi escape code splitting
454// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js
455// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore
456// Matches all ansi escape code sequences in a string
457const ansiPattern = new RegExp(
458 '[\\u001B\\u009B][[\\]()#;?]*' +
459 '(?:(?:(?:(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]+)*' +
460 '|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]*)*)?' +
461 '(?:\\u0007|\\u001B\\u005C|\\u009C))' +
462 '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?' +
463 '[\\dA-PR-TZcf-nq-uy=><~]))',
464 'g'
465);
466const ansi = new RegExp(ansiPattern, 'g');
467 
468interface Context extends Required<InspectOptionsStylized> {
469 maxArrayLength: number;
470 maxStringLength: number;
471 budget: Record<string, number>;
472 indentationLvl: number;
473 seen: unknown[];
474 currentDepth: number;
475 userOptions?: InspectOptions;
476 circular?: Map<unknown, number>;
477}
478 
479function getUserOptions(
480 ctx: Context,
481 isCrossContext: boolean
482): InspectOptionsStylized {
483 const ret: InspectOptionsStylized = {
484 stylize: ctx.stylize,
485 showHidden: ctx.showHidden,
486 depth: ctx.depth,
487 colors: ctx.colors,
488 customInspect: ctx.customInspect,
489 showProxy: ctx.showProxy,
490 maxArrayLength: ctx.maxArrayLength,
491 maxStringLength: ctx.maxStringLength,
492 breakLength: ctx.breakLength,
493 compact: ctx.compact,
494 sorted: ctx.sorted,
495 getters: ctx.getters,
496 numericSeparator: ctx.numericSeparator,
497 ...ctx.userOptions,
498 };
499 
500 // Typically, the target value will be an instance of `Object`. If that is
501 // *not* the case, the object may come from another vm.Context, and we want
502 // to avoid passing it objects from this Context in that case, so we remove
503 // the prototype from the returned object itself + the `stylize()` function,
504 // and remove all other non-primitives, including non-primitive user options.
505 if (isCrossContext) {
506 Object.setPrototypeOf(ret, null);
507 for (const key of Object.keys(ret) as (keyof InspectOptionsStylized)[]) {
508 if (
509 (typeof ret[key] === 'object' || typeof ret[key] === 'function') &&
510 ret[key] !== null
511 ) {
512 delete ret[key];
513 }
514 }
515 ret.stylize = Object.setPrototypeOf((value: string, flavour: Style) => {
516 let stylized;
517 try {
518 stylized = `${ctx.stylize(value, flavour)}`;
519 } catch {
520 // Continue regardless of error.
521 }
522 
523 if (typeof stylized !== 'string') return value;
524 // `stylized` is a string as it should be, which is safe to pass along.
525 return stylized;
526 }, null);
527 }
528 
529 return ret;
530}
531 
532/**
533 * Echos the value of any input. Tries to print the value out
534 * in the best way possible given the different types.
535 * @param {any} value The value to print out.
536 * @param {object} opts Optional options object that alters the output.
537 */
538/* Legacy: value, showHidden, depth, colors */
539export function inspect(
540 value: unknown,
541 showHidden?: boolean,
542 depth?: number | null,
543 color?: boolean
544): string;
545export function inspect(value: unknown, opts?: InspectOptions): string;
546export function inspect(
547 value: unknown,
548 opts?: Partial<InspectOptionsStylized> | boolean
549): string {
550 // Default options
551 const ctx: Context = {
552 budget: {},
553 indentationLvl: 0,
554 seen: [],
555 currentDepth: 0,
556 stylize: stylizeNoColor,
557 showHidden: inspectDefaultOptions.showHidden,
558 depth: inspectDefaultOptions.depth,
559 colors: inspectDefaultOptions.colors,
560 customInspect: inspectDefaultOptions.customInspect,
561 showProxy: inspectDefaultOptions.showProxy,
562 maxArrayLength: inspectDefaultOptions.maxArrayLength,
563 maxStringLength: inspectDefaultOptions.maxStringLength,
564 breakLength: inspectDefaultOptions.breakLength,
565 compact: inspectDefaultOptions.compact,
566 sorted: inspectDefaultOptions.sorted,
567 getters: inspectDefaultOptions.getters,
568 numericSeparator: inspectDefaultOptions.numericSeparator,
569 };
570 if (arguments.length > 1) {
571 // Legacy...
572 if (arguments.length > 2) {
573 if (arguments[2] !== undefined) {
574 ctx.depth = arguments[2];
575 }
576 if (arguments.length > 3 && arguments[3] !== undefined) {
577 ctx.colors = arguments[3];
578 }
579 }
580 // Set user-specified options
581 if (typeof opts === 'boolean') {
582 ctx.showHidden = opts;
583 } else if (opts) {
584 const optKeys = Object.keys(opts) as (keyof InspectOptionsStylized)[];
585 for (let i = 0; i < optKeys.length; ++i) {
586 const key = optKeys[i]!;
587 // TODO(BridgeAR): Find a solution what to do about stylize. Either make
588 // this function public or add a new API with a similar or better
589 // functionality.
590 if (
591 Object.prototype.hasOwnProperty.call(inspectDefaultOptions, key) ||
592 key === 'stylize'
593 ) {
594 (ctx as Record<keyof InspectOptionsStylized, unknown>)[key] =
595 opts[key];
596 } else if (ctx.userOptions === undefined) {
597 // This is required to pass through the actual user input.
598 ctx.userOptions = opts;
599 }
600 }
601 }
602 }
603 if (ctx.colors) ctx.stylize = stylizeWithColor;
604 if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity;
605 if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity;
606 return formatValue(ctx, value, 0);
607}
608inspect.custom = customInspectSymbol;
609 
610Object.defineProperty(inspect, 'defaultOptions', {
611 get() {
612 return inspectDefaultOptions;
613 },
614 set(options) {
615 validateObject(options, 'options');
616 return Object.assign(inspectDefaultOptions, options);
617 },
618});
619 
620// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
621// Each color consists of an array with the color code as first entry and the
622// reset code as second entry.
623const defaultFG = 39;
624const defaultBG = 49;
625const colors: Record<string, [number, number]> = {
626 // @ts-ignore
627 __proto__: null,
628 reset: [0, 0],
629 bold: [1, 22],
630 dim: [2, 22], // Alias: faint
631 italic: [3, 23],
632 underline: [4, 24],
633 blink: [5, 25],
634 // Swap foreground and background colors
635 inverse: [7, 27], // Alias: swapcolors, swapColors
636 hidden: [8, 28], // Alias: conceal
637 strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut
638 doubleunderline: [21, 24], // Alias: doubleUnderline
639 black: [30, defaultFG],
640 red: [31, defaultFG],
641 green: [32, defaultFG],
642 yellow: [33, defaultFG],
643 blue: [34, defaultFG],
644 magenta: [35, defaultFG],
645 cyan: [36, defaultFG],
646 white: [37, defaultFG],
647 bgBlack: [40, defaultBG],
648 bgRed: [41, defaultBG],
649 bgGreen: [42, defaultBG],
650 bgYellow: [43, defaultBG],
651 bgBlue: [44, defaultBG],
652 bgMagenta: [45, defaultBG],
653 bgCyan: [46, defaultBG],
654 bgWhite: [47, defaultBG],
655 framed: [51, 54],
656 overlined: [53, 55],
657 gray: [90, defaultFG], // Alias: grey, blackBright
658 redBright: [91, defaultFG],
659 greenBright: [92, defaultFG],
660 yellowBright: [93, defaultFG],
661 blueBright: [94, defaultFG],
662 magentaBright: [95, defaultFG],
663 cyanBright: [96, defaultFG],
664 whiteBright: [97, defaultFG],
665 bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright
666 bgRedBright: [101, defaultBG],
667 bgGreenBright: [102, defaultBG],
668 bgYellowBright: [103, defaultBG],
669 bgBlueBright: [104, defaultBG],
670 bgMagentaBright: [105, defaultBG],
671 bgCyanBright: [106, defaultBG],
672 bgWhiteBright: [107, defaultBG],
673};
674inspect.colors = colors;
675 
676function defineColorAlias(target: string, alias: string) {
677 Object.defineProperty(inspect.colors, alias, {
678 get() {
679 return this[target];
680 },
681 set(value) {
682 this[target] = value;
683 },
684 configurable: true,
685 enumerable: false,
686 });
687}
688 
689defineColorAlias('gray', 'grey');
690defineColorAlias('gray', 'blackBright');
691defineColorAlias('bgGray', 'bgGrey');
692defineColorAlias('bgGray', 'bgBlackBright');
693defineColorAlias('dim', 'faint');
694defineColorAlias('strikethrough', 'crossedout');
695defineColorAlias('strikethrough', 'strikeThrough');
696defineColorAlias('strikethrough', 'crossedOut');
697defineColorAlias('hidden', 'conceal');
698defineColorAlias('inverse', 'swapColors');
699defineColorAlias('inverse', 'swapcolors');
700defineColorAlias('doubleunderline', 'doubleUnderline');
701 
702// TODO(BridgeAR): Add function style support for more complex styles.
703// Don't use 'blue' not visible on cmd.exe
704inspect.styles = {
705 __proto__: null,
706 special: 'cyan',
707 number: 'yellow',
708 bigint: 'yellow',
709 boolean: 'yellow',
710 undefined: 'grey',
711 null: 'bold',
712 string: 'green',
713 symbol: 'green',
714 date: 'magenta',
715 name: undefined,
716 // TODO(BridgeAR): Highlight regular expressions properly.
717 regexp: 'red',
718 module: 'underline',
719};
720 
721function addQuotes(str: string, quotes: number): string {
722 if (quotes === -1) {
723 return `"${str}"`;
724 }
725 if (quotes === -2) {
726 return `\`${str}\``;
727 }
728 return `'${str}'`;
729}
730 
731function escapeFn(str: string): string {
732 const charCode = str.charCodeAt(0);
733 return meta.length > charCode
734 ? meta[charCode]!
735 : `\\u${charCode.toString(16)}`;
736}
737 
738// Escape control characters, single quotes and the backslash.
739// This is similar to JSON stringify escaping.
740function strEscape(str: string): string {
741 let escapeTest = strEscapeSequencesRegExp;
742 let escapeReplace = strEscapeSequencesReplacer;
743 let singleQuote = 39;
744 
745 // Check for double quotes. If not present, do not escape single quotes and
746 // instead wrap the text in double quotes. If double quotes exist, check for
747 // backticks. If they do not exist, use those as fallback instead of the
748 // double quotes.
749 if (str.includes("'")) {
750 // This invalidates the charCode and therefore can not be matched for
751 // anymore.
752 if (!str.includes('"')) {
753 singleQuote = -1;
754 } else if (!str.includes('`') && !str.includes('${')) {
755 singleQuote = -2;
756 }
757 if (singleQuote !== 39) {
758 escapeTest = strEscapeSequencesRegExpSingle;
759 escapeReplace = strEscapeSequencesReplacerSingle;
760 }
761 }
762 
763 // Some magic numbers that worked out fine while benchmarking with v8 6.0
764 if (str.length < 5000 && escapeTest.exec(str) === null)
765 return addQuotes(str, singleQuote);
766 if (str.length > 100) {
767 str = str.replace(escapeReplace, escapeFn);
768 return addQuotes(str, singleQuote);
769 }
770 
771 let result = '';
772 let last = 0;
773 for (let i = 0; i < str.length; i++) {
774 const point = str.charCodeAt(i);
775 if (
776 point === singleQuote ||
777 point === 92 ||
778 point < 32 ||
779 (point > 126 && point < 160)
780 ) {
781 if (last === i) {
782 result += meta[point];
783 } else {
784 result += `${str.slice(last, i)}${meta[point]}`;
785 }
786 last = i + 1;
787 } else if (point >= 0xd800 && point <= 0xdfff) {
788 if (point <= 0xdbff && i + 1 < str.length) {
789 const point = str.charCodeAt(i + 1);
790 if (point >= 0xdc00 && point <= 0xdfff) {
791 i++;
792 continue;
793 }
794 }
795 result += `${str.slice(last, i)}\\u${point.toString(16)}`;
796 last = i + 1;
797 }
798 }
799 
800 if (last !== str.length) {
801 result += str.slice(last);
802 }
803 return addQuotes(result, singleQuote);
804}
805 
806function stylizeWithColor(str: string, styleType: Style): string {
807 const style = inspect.styles[styleType];
808 if (style !== undefined) {
809 const color = (
810 inspect.colors as unknown as Record<string, number[] | undefined>
811 )[style];
812 if (color !== undefined)
813 return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`;
814 }
815 return str;
816}
817 
818function stylizeNoColor(str: string): string {
819 return str;
820}
821 
822// Return a new empty array to push in the results of the default formatter.
823function getEmptyFormatArray(): string[] {
824 return [];
825}
826 
827function isInstanceof(object: unknown, proto: Function): boolean {
828 try {
829 return object instanceof proto;
830 } catch {
831 return false;
832 }
833}
834 
835// Special-case for some builtin prototypes in case their `constructor` property has been tampered.
836const wellKnownPrototypes = new Map()
837 .set(Array.prototype, { name: 'Array', constructor: Array })
838 .set(ArrayBuffer.prototype, { name: 'ArrayBuffer', constructor: ArrayBuffer })
839 .set(Function.prototype, { name: 'Function', constructor: Function })
840 .set(Map.prototype, { name: 'Map', constructor: Map })
841 .set(Set.prototype, { name: 'Set', constructor: Set })
842 .set(Object.prototype, { name: 'Object', constructor: Object })
843 .set(Object.getPrototypeOf(Uint8Array).prototype, {
844 name: 'TypedArray',
845 constructor: Object.getPrototypeOf(Uint8Array),
846 })
847 .set(RegExp.prototype, { name: 'RegExp', constructor: RegExp })
848 .set(Date.prototype, { name: 'Date', constructor: Date })
849 .set(DataView.prototype, { name: 'DataView', constructor: DataView })
850 .set(Error.prototype, { name: 'Error', constructor: Error })
851 .set(Boolean.prototype, { name: 'Boolean', constructor: Boolean })
852 .set(Number.prototype, { name: 'Number', constructor: Number })
853 .set(String.prototype, { name: 'String', constructor: String })
854 .set(Promise.prototype, { name: 'Promise', constructor: Promise })
855 .set(WeakMap.prototype, { name: 'WeakMap', constructor: WeakMap })
856 .set(WeakSet.prototype, { name: 'WeakSet', constructor: WeakSet });
857 
858function getConstructorName(
859 obj: object,
860 ctx: Context,
861 recurseTimes: number,
862 protoProps?: string[]
863): string | null {
864 let firstProto: unknown;
865 const tmp = obj;
866 while (obj || isUndetectableObject(obj)) {
867 const wellKnownPrototypeNameAndConstructor = wellKnownPrototypes.get(obj);
868 if (wellKnownPrototypeNameAndConstructor !== undefined) {
869 const { name, constructor } = wellKnownPrototypeNameAndConstructor;
870 if (Function.prototype[Symbol.hasInstance].call(constructor, tmp)) {
871 if (protoProps !== undefined && firstProto !== obj) {
872 addPrototypeProperties(
873 ctx,
874 tmp,
875 firstProto || tmp,
876 recurseTimes,
877 protoProps
878 );
879 }
880 return name;
881 }
882 }
883 const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
884 if (
885 descriptor !== undefined &&
886 typeof descriptor.value === 'function' &&
887 descriptor.value.name !== '' &&
888 isInstanceof(tmp, descriptor.value)
889 ) {
890 if (
891 protoProps !== undefined &&
892 (firstProto !== obj || !builtInObjects.has(descriptor.value.name))
893 ) {
894 addPrototypeProperties(
895 ctx,
896 tmp,
897 firstProto || tmp,
898 recurseTimes,
899 protoProps
900 );
901 }
902 return String(descriptor.value.name);
903 }
904 
905 obj = Object.getPrototypeOf(obj);
906 if (firstProto === undefined) {
907 firstProto = obj;
908 }
909 }
910 
911 if (firstProto === null) {
912 return null;
913 }
914 
915 const res = internal.getConstructorName(tmp);
916 
917 if (ctx.depth !== null && recurseTimes > ctx.depth) {
918 return `${res} <Complex prototype>`;
919 }
920 
921 const protoConstr = getConstructorName(
922 firstProto!,
923 ctx,
924 recurseTimes + 1,
925 protoProps
926 );
927 
928 if (protoConstr === null) {
929 return `${res} <${inspect(firstProto, {
930 ...ctx,
931 customInspect: false,
932 depth: -1,
933 })}>`;
934 }
935 
936 return `${res} <${protoConstr}>`;
937}
938 
939// This function has the side effect of adding prototype properties to the
940// `output` argument (which is an array). This is intended to highlight user
941// defined prototype properties.
942function addPrototypeProperties(
943 ctx: Context,
944 main: object,
945 obj: Object,
946 recurseTimes: number,
947 output: string[]
948): void {
949 let depth = 0;
950 let keys: PropertyKey[] | undefined;
951 let keySet: Set<PropertyKey> | undefined;
952 do {
953 if (depth !== 0 || main === obj) {
954 obj = Object.getPrototypeOf(obj);
955 // Stop as soon as a null prototype is encountered.
956 if (obj === null) {
957 return;
958 }
959 // Stop as soon as a built-in object type is detected.
960 const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
961 if (
962 descriptor !== undefined &&
963 typeof descriptor.value === 'function' &&
964 builtInObjects.has(descriptor.value.name)
965 ) {
966 return;
967 }
968 }
969 
970 if (depth === 0) {
971 keySet = new Set();
972 } else {
973 keys!.forEach((key) => keySet!.add(key));
974 }
975 // Get all own property names and symbols.
976 keys = Reflect.ownKeys(obj);
977 ctx.seen.push(main);
978 for (const key of keys) {
979 // Ignore the `constructor` property and keys that exist on layers above.
980 if (
981 key === 'constructor' ||
982 Object.prototype.hasOwnProperty.call(main, key) ||
983 (depth !== 0 && keySet!.has(key))
984 ) {
985 continue;
986 }
987 const desc = Object.getOwnPropertyDescriptor(obj, key);
988 if (typeof desc?.value === 'function') {
989 continue;
990 }
991 const value = formatProperty(
992 ctx,
993 obj,
994 recurseTimes,
995 key,
996 kObjectType,
997 desc,
998 main
999 );
1000 if (ctx.colors) {
1001 // Faint!
1002 output.push(`\u001b[2m${value}\u001b[22m`);
1003 } else {
1004 output.push(value);
1005 }
1006 }
1007 ctx.seen.pop();
1008 // Limit the inspection to up to three prototype layers. Using `recurseTimes`
1009 // is not a good choice here, because it's as if the properties are declared
1010 // on the current object from the users perspective.
1011 } while (++depth !== 3);
1012}
1013 
1014function getPrefix(
1015 constructor: string | null,
1016 tag: string,
1017 fallback: string,
1018 size = ''
1019): string {
1020 if (constructor === null) {
1021 if (tag !== '' && fallback !== tag) {
1022 return `[${fallback}${size}: null prototype] [${tag}] `;
1023 }
1024 return `[${fallback}${size}: null prototype] `;
1025 }
1026 
1027 if (tag !== '' && constructor !== tag) {
1028 return `${constructor}${size} [${tag}] `;
1029 }
1030 return `${constructor}${size} `;
1031}
1032 
1033// Look up the keys of the object.
1034function getKeys(value: object, showHidden: boolean): PropertyKey[] {
1035 let keys: PropertyKey[];
1036 const symbols = Object.getOwnPropertySymbols(value);
1037 if (showHidden) {
1038 keys = Object.getOwnPropertyNames(value);
1039 if (symbols.length !== 0) keys.push(...symbols);
1040 } else {
1041 // This might throw if `value` is a Module Namespace Object from an
1042 // unevaluated module, but we don't want to perform the actual type
1043 // check because it's expensive.
1044 // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209
1045 // and modify this logic as needed.
1046 try {
1047 keys = Object.keys(value);
1048 } catch (err: any) {
1049 assert(
1050 isNativeError(err) &&
1051 err.name === 'ReferenceError' &&
1052 isModuleNamespaceObject(value)
1053 );
1054 keys = Object.getOwnPropertyNames(value);
1055 }
1056 if (symbols.length !== 0) {
1057 const filter = (key: PropertyKey) =>
1058 Object.prototype.propertyIsEnumerable.call(value, key);
1059 keys.push(...symbols.filter(filter));
1060 }
1061 }
1062 return keys;
1063}
1064 
1065function getCtxStyle(
1066 value: unknown,
1067 constructor: string | null,
1068 tag: string
1069): string {
1070 let fallback = '';
1071 if (constructor === null) {
1072 fallback = internal.getConstructorName(value);
1073 if (fallback === tag) {
1074 fallback = 'Object';
1075 }
1076 }
1077 return getPrefix(constructor, tag, fallback);
1078}
1079 
1080function formatProxy(
1081 ctx: Context,
1082 proxy: internal.ProxyDetails,
1083 recurseTimes: number
1084): string {
1085 if (ctx.depth !== null && recurseTimes > ctx.depth) {
1086 return ctx.stylize('Proxy [Array]', 'special');
1087 }
1088 recurseTimes += 1;
1089 ctx.indentationLvl += 2;
1090 const res: string[] = [
1091 formatValue(ctx, proxy.target, recurseTimes),
1092 formatValue(ctx, proxy.handler, recurseTimes),
1093 ];
1094 ctx.indentationLvl -= 2;
1095 return reduceToSingleString(
1096 ctx,
1097 res,
1098 '',
1099 ['Proxy [', ']'],
1100 kArrayExtrasType,
1101 recurseTimes
1102 );
1103}
1104 
1105// Note: using `formatValue` directly requires the indentation level to be
1106// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
1107// value afterwards again.
1108function formatValue(
1109 ctx: Context,
1110 value: unknown,
1111 recurseTimes: number,
1112 typedArray?: unknown
1113): string {
1114 // Primitive types cannot have properties.
1115 if (
1116 typeof value !== 'object' &&
1117 typeof value !== 'function' &&
1118 !isUndetectableObject(value)
1119 ) {
1120 return formatPrimitive(ctx.stylize, value as Primitive, ctx);
1121 }
1122 if (value === null) {
1123 return ctx.stylize('null', 'null');
1124 }
1125 
1126 // Memorize the context for custom inspection on proxies.
1127 const context = value;
1128 let proxies = 0;
1129 // Always check for proxies to prevent side effects and to prevent triggering
1130 // any proxy handlers.
1131 let proxy = internal.getProxyDetails(value);
1132 if (proxy !== undefined) {
1133 if (proxy === null || proxy.target === null) {
1134 return ctx.stylize('<Revoked Proxy>', 'special');
1135 }
1136 if (ctx.showProxy) {
1137 return formatProxy(ctx, proxy, recurseTimes);
1138 }
1139 do {
1140 if (proxy === null || proxy.target === null) {
1141 let formatted = ctx.stylize('<Revoked Proxy>', 'special');
1142 for (let i = 0; i < proxies; i++) {
1143 formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;
1144 }
1145 return formatted;
1146 }
1147 value = proxy.target;
1148 proxy = internal.getProxyDetails(value);
1149 proxies += 1;
1150 } while (proxy !== undefined);
1151 }
1152 
1153 // Provide a hook for user-specified inspect functions.
1154 // Check that value is an object with an inspect function on it.
1155 if (ctx.customInspect) {
1156 let maybeCustom = (value as Record<PropertyKey, unknown>)[
1157 customInspectSymbol
1158 ];
1159 
1160 // WORKERD SPECIFIC PATCH: if `value` is a JSG resource type, use a well-known custom inspect
1161 const maybeResourceTypeInspect = (value as Record<PropertyKey, unknown>)[
1162 internal.kResourceTypeInspect
1163 ];
1164 if (typeof maybeResourceTypeInspect === 'object') {
1165 maybeCustom = formatJsgResourceType.bind(
1166 context as Record<PropertyKey, unknown>,
1167 maybeResourceTypeInspect as Record<string, symbol>
1168 );
1169 }
1170 
1171 if (
1172 typeof maybeCustom === 'function' &&
1173 // Filter out the util module, its inspect function is special.
1174 maybeCustom !== inspect &&
1175 // Also filter out any prototype objects using the circular check.
1176 !(
1177 (value as object).constructor &&
1178 (value as object).constructor.prototype === value
1179 )
1180 ) {
1181 // This makes sure the recurseTimes are reported as before while using
1182 // a counter internally.
1183 const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
1184 const isCrossContext = proxies !== 0 || !(context instanceof Object);
1185 const ret = Function.prototype.call.call(
1186 maybeCustom,
1187 context,
1188 depth,
1189 getUserOptions(ctx, isCrossContext),
1190 inspect
1191 );
1192 // If the custom inspection method returned `this`, don't go into
1193 // infinite recursion.
1194 if (ret !== context) {
1195 if (typeof ret !== 'string') {
1196 return formatValue(ctx, ret, recurseTimes);
1197 }
1198 return ret.replaceAll('\n', `\n${' '.repeat(ctx.indentationLvl)}`);
1199 }
1200 }
1201 }
1202 
1203 // Using an array here is actually better for the average case than using
1204 // a Set. `seen` will only check for the depth and will never grow too large.
1205 if (ctx.seen.includes(value)) {
1206 let index: number | undefined = 1;
1207 if (ctx.circular === undefined) {
1208 ctx.circular = new Map();
1209 ctx.circular.set(value, index);
1210 } else {
1211 index = ctx.circular.get(value);
1212 if (index === undefined) {
1213 index = ctx.circular.size + 1;
1214 ctx.circular.set(value, index);
1215 }
1216 }
1217 return ctx.stylize(`[Circular *${index}]`, 'special');
1218 }
1219 
1220 let formatted = formatRaw(ctx, value, recurseTimes, typedArray);
1221 
1222 if (proxies !== 0) {
1223 for (let i = 0; i < proxies; i++) {
1224 formatted = `${ctx.stylize('Proxy(', 'special')}${formatted}${ctx.stylize(')', 'special')}`;
1225 }
1226 }
1227 
1228 return formatted;
1229}
1230 
1231function formatRaw(
1232 ctx: Context,
1233 value: unknown,
1234 recurseTimes: number,
1235 typedArray: unknown
1236): string {
1237 let keys: PropertyKey[] | undefined;
1238 let protoProps: string[] | undefined;
1239 if (ctx.showHidden && (ctx.depth === null || recurseTimes <= ctx.depth)) {
1240 protoProps = [];
1241 }
1242 
1243 const constructor = getConstructorName(
1244 value as object,
1245 ctx,
1246 recurseTimes,
1247 protoProps
1248 );
1249 // Reset the variable to check for this later on.
1250 if (protoProps !== undefined && protoProps.length === 0) {
1251 protoProps = undefined;
1252 }
1253 
1254 let tag = (value as { [Symbol.toStringTag]?: string })[Symbol.toStringTag];
1255 // Only list the tag in case it's non-enumerable / not an own property.
1256 // Otherwise we'd print this twice.
1257 if (
1258 typeof tag !== 'string' ||
1259 (tag !== '' &&
1260 (ctx.showHidden
1261 ? Object.prototype.hasOwnProperty
1262 : Object.prototype.propertyIsEnumerable
1263 ).call(value, Symbol.toStringTag))
1264 ) {
1265 tag = '';
1266 }
1267 let base = '';
1268 let formatter: (ctx: Context, value: any, recurseTimes: number) => string[] =
1269 getEmptyFormatArray;
1270 let braces: [string, string] | undefined;
1271 let noIterator = true;
1272 let i = 0;
1273 const filter = ctx.showHidden
1274 ? internal.ALL_PROPERTIES
1275 : internal.ONLY_ENUMERABLE;
1276 
1277 let extrasType = kObjectType;
1278 
1279 // Iterators and the rest are split to reduce checks.
1280 // We have to check all values in case the constructor is set to null.
1281 // Otherwise it would not possible to identify all types properly.
1282 
1283 const isEntriesObject = hasEntries(value);
1284 if (
1285 Symbol.iterator in (value as object) ||
1286 constructor === null ||
1287 isEntriesObject
1288 ) {
1289 noIterator = false;
1290 if (isEntriesObject) {
1291 // WORKERD SPECIFIC PATCH: if `value` is an object with entries, format them like a map
1292 const size = value[kEntries].length;
1293 const prefix = getPrefix(constructor, tag, 'Object', `(${size})`);
1294 keys = getKeys(value, ctx.showHidden);
1295 
1296 // Remove `kEntries` and `size` from keys
1297 keys.splice(keys.indexOf(kEntries), 1);
1298 const sizeIndex = keys.indexOf('size');
1299 if (sizeIndex !== -1) keys.splice(sizeIndex, 1);
1300 
1301 formatter = formatMap.bind(null, value[kEntries][Symbol.iterator]());
1302 if (size === 0 && keys.length === 0 && protoProps === undefined)
1303 return `${prefix}{}`;
1304 braces = [`${prefix}{`, '}'];
1305 } else if (Array.isArray(value)) {
1306 // Only set the constructor for non ordinary ("Array [...]") arrays.
1307 const prefix =
1308 constructor !== 'Array' || tag !== ''
1309 ? getPrefix(constructor, tag, 'Array', `(${value.length})`)
1310 : '';
1311 keys = internal.getOwnNonIndexProperties(value, filter);
1312 braces = [`${prefix}[`, ']'];
1313 if (value.length === 0 && keys.length === 0 && protoProps === undefined)
1314 return `${braces[0]}]`;
1315 extrasType = kArrayExtrasType;
1316 formatter = formatArray;
1317 } else if (isSet(value)) {
1318 const size = setPrototypeSize.call(value);
1319 const prefix = getPrefix(constructor, tag, 'Set', `(${size})`);
1320 keys = getKeys(value, ctx.showHidden);
1321 formatter =
1322 constructor !== null
1323 ? formatSet.bind(null, value)
1324 : formatSet.bind(null, Set.prototype.values.call(value));
1325 if (size === 0 && keys.length === 0 && protoProps === undefined)
1326 return `${prefix}{}`;
1327 braces = [`${prefix}{`, '}'];
1328 } else if (isMap(value)) {
1329 const size = mapPrototypeSize.call(value);
1330 const prefix = getPrefix(constructor, tag, 'Map', `(${size})`);
1331 keys = getKeys(value, ctx.showHidden);
1332 formatter =
1333 constructor !== null
1334 ? formatMap.bind(null, value)
1335 : formatMap.bind(null, Map.prototype.entries.call(value));
1336 if (size === 0 && keys.length === 0 && protoProps === undefined)
1337 return `${prefix}{}`;
1338 braces = [`${prefix}{`, '}'];
1339 } else if (isTypedArray(value)) {
1340 keys = internal.getOwnNonIndexProperties(value, filter);
1341 let bound = value;
1342 let fallback = '';
1343 if (constructor === null) {
1344 fallback = typedArrayPrototypeToStringTag.call(value);
1345 // Reconstruct the array information.
1346 bound = new (
1347 globalThis as unknown as Record<
1348 string,
1349 { new (value: NodeJS.TypedArray): NodeJS.TypedArray }
1350 >
1351 )[fallback]!(value);
1352 }
1353 const size = typedArrayPrototypeLength.call(value);
1354 const prefix = getPrefix(constructor, tag, fallback, `(${size})`);
1355 braces = [`${prefix}[`, ']'];
1356 if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
1357 return `${braces[0]}]`;
1358 // Special handle the value. The original value is required below. The
1359 // bound function is required to reconstruct missing information.
1360 formatter = formatTypedArray.bind(null, bound, size);
1361 extrasType = kArrayExtrasType;
1362 } else if (isMapIterator(value)) {
1363 keys = getKeys(value, ctx.showHidden);
1364 braces = getIteratorBraces('Map', tag);
1365 // Add braces to the formatter parameters.
1366 formatter = formatIterator.bind(null, braces);
1367 } else if (isSetIterator(value)) {
1368 keys = getKeys(value, ctx.showHidden);
1369 braces = getIteratorBraces('Set', tag);
1370 // Add braces to the formatter parameters.
1371 formatter = formatIterator.bind(null, braces);
1372 } else {
1373 noIterator = true;
1374 }
1375 }
1376 if (noIterator) {
1377 keys = getKeys(value as object, ctx.showHidden);
1378 braces = ['{', '}'];
1379 if (constructor === 'Object') {
1380 if (isArgumentsObject(value)) {
1381 braces[0] = '[Arguments] {';
1382 } else if (tag !== '') {
1383 braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
1384 }
1385 if (keys.length === 0 && protoProps === undefined) {
1386 return `${braces[0]}}`;
1387 }
1388 } else if (typeof value === 'function') {
1389 base = getFunctionBase(ctx, value, constructor, tag);
1390 if (keys.length === 0 && protoProps === undefined)
1391 return ctx.stylize(base, 'special');
1392 } else if (isRegExp(value)) {
1393 // Make RegExps say that they are RegExps
1394 base = RegExp.prototype.toString.call(
1395 constructor !== null ? value : new RegExp(value)
1396 );
1397 const prefix = getPrefix(constructor, tag, 'RegExp');
1398 if (prefix !== 'RegExp ') base = `${prefix}${base}`;
1399 if (
1400 (keys.length === 0 && protoProps === undefined) ||
1401 (ctx.depth !== null && recurseTimes > ctx.depth)
1402 ) {
1403 return ctx.stylize(base, 'regexp');
1404 }
1405 } else if (isDate(value)) {
1406 // Make dates with properties first say the date
1407 base = Number.isNaN(Date.prototype.getTime.call(value))
1408 ? Date.prototype.toString.call(value)
1409 : Date.prototype.toISOString.call(value);
1410 const prefix = getPrefix(constructor, tag, 'Date');
1411 if (prefix !== 'Date ') base = `${prefix}${base}`;
1412 if (keys.length === 0 && protoProps === undefined) {
1413 return ctx.stylize(base, 'date');
1414 }
1415 } else if (isError(value)) {
1416 base = formatError(value, constructor, tag, ctx, keys);
1417 if (keys.length === 0 && protoProps === undefined) return base;
1418 } else if (isAnyArrayBuffer(value)) {
1419 // Fast path for ArrayBuffer and SharedArrayBuffer.
1420 // Can't do the same for DataView because it has a non-primitive
1421 // .buffer property that we need to recurse for.
1422 const arrayType = isArrayBuffer(value)
1423 ? 'ArrayBuffer'
1424 : 'SharedArrayBuffer';
1425 const prefix = getPrefix(constructor, tag, arrayType);
1426 if (typedArray === undefined) {
1427 formatter = formatArrayBuffer;
1428 } else if (keys.length === 0 && protoProps === undefined) {
1429 return (
1430 prefix +
1431 `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`
1432 );
1433 }
1434 braces[0] = `${prefix}{`;
1435 keys.unshift('byteLength');
1436 } else if (isDataView(value)) {
1437 braces[0] = `${getPrefix(constructor, tag, 'DataView')}{`;
1438 // .buffer goes last, it's not a primitive like the others.
1439 keys.unshift('byteLength', 'byteOffset', 'buffer');
1440 } else if (isPromise(value)) {
1441 braces[0] = `${getPrefix(constructor, tag, 'Promise')}{`;
1442 formatter = formatPromise;
1443 } else if (isWeakSet(value)) {
1444 braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`;
1445 formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection;
1446 } else if (isWeakMap(value)) {
1447 braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`;
1448 formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
1449 } else if (isModuleNamespaceObject(value)) {
1450 braces[0] = `${getPrefix(constructor, tag, 'Module')}{`;
1451 // Special handle keys for namespace objects.
1452 formatter = formatNamespaceObject.bind(null, keys);
1453 } else if (isBoxedPrimitive(value)) {
1454 base = getBoxedBase(value, ctx, keys, constructor, tag);
1455 if (keys.length === 0 && protoProps === undefined) {
1456 return base;
1457 }
1458 } else {
1459 if (keys.length === 0 && protoProps === undefined) {
1460 return `${getCtxStyle(value, constructor, tag)}{}`;
1461 }
1462 braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
1463 }
1464 }
1465 
1466 if (ctx.depth !== null && recurseTimes > ctx.depth) {
1467 let constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
1468 if (constructor !== null) constructorName = `[${constructorName}]`;
1469 return ctx.stylize(constructorName, 'special');
1470 }
1471 recurseTimes += 1;
1472 
1473 ctx.seen.push(value);
1474 ctx.currentDepth = recurseTimes;
1475 let output;
1476 const indentationLvl = ctx.indentationLvl;
1477 try {
1478 output = formatter(ctx, value, recurseTimes);
1479 for (i = 0; i < keys!.length; i++) {
1480 output.push(
1481 formatProperty(
1482 ctx,
1483 value as object,
1484 recurseTimes,
1485 keys![i]!,
1486 extrasType
1487 )
1488 );
1489 }
1490 if (protoProps !== undefined) {
1491 output.push(...protoProps);
1492 }
1493 } catch (err) {
1494 const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
1495 return handleMaxCallStackSize(
1496 ctx,
1497 err as Error,
1498 constructorName,
1499 indentationLvl
1500 );
1501 }
1502 if (ctx.circular !== undefined) {
1503 const index = ctx.circular.get(value);
1504 if (index !== undefined) {
1505 const reference = ctx.stylize(`<ref *${index}>`, 'special');
1506 // Add reference always to the very beginning of the output.
1507 if (ctx.compact !== true) {
1508 base = base === '' ? reference : `${reference} ${base}`;
1509 } else {
1510 braces![0] = `${reference} ${braces![0]}`;
1511 }
1512 }
1513 }
1514 ctx.seen.pop();
1515 
1516 if (ctx.sorted) {
1517 const comparator = ctx.sorted === true ? undefined : ctx.sorted;
1518 if (extrasType === kObjectType) {
1519 output.sort(comparator);
1520 } else if (keys!.length > 1) {
1521 const sorted = output
1522 .slice(output.length - keys!.length)
1523 .sort(comparator);
1524 output.splice(output.length - keys!.length, keys!.length, ...sorted);
1525 }
1526 }
1527 
1528 const res = reduceToSingleString(
1529 ctx,
1530 output,
1531 base,
1532 braces!,
1533 extrasType,
1534 recurseTimes,
1535 value
1536 );
1537 const budget = ctx.budget[ctx.indentationLvl] || 0;
1538 const newLength = budget + res.length;
1539 ctx.budget[ctx.indentationLvl] = newLength;
1540 // If any indentationLvl exceeds this limit, limit further inspecting to the
1541 // minimum. Otherwise the recursive algorithm might continue inspecting the
1542 // object even though the maximum string size (~2 ** 28 on 32 bit systems and
1543 // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at
1544 // exactly 2 ** 27 but a bit higher. This depends on the object shape.
1545 // This limit also makes sure that huge objects don't block the event loop
1546 // significantly.
1547 if (newLength > 2 ** 27) {
1548 ctx.depth = -1;
1549 }
1550 return res;
1551}
1552 
1553function getIteratorBraces(type: string, tag: string): [string, string] {
1554 if (tag !== `${type} Iterator`) {
1555 if (tag !== '') tag += '] [';
1556 tag += `${type} Iterator`;
1557 }
1558 return [`[${tag}] {`, '}'];
1559}
1560 
1561function getBoxedBase(
1562 value: unknown,
1563 ctx: Context,
1564 keys: PropertyKey[],
1565 constructor: string | null,
1566 tag: string
1567): string {
1568 let fn: (this: unknown) => Primitive;
1569 let type: Capitalize<Exclude<Style, 'bigint'>> | 'BigInt';
1570 if (isNumberObject(value)) {
1571 fn = Number.prototype.valueOf;
1572 type = 'Number';
1573 } else if (isStringObject(value)) {
1574 fn = String.prototype.valueOf;
1575 type = 'String';
1576 // For boxed Strings, we have to remove the 0-n indexed entries,
1577 // since they just noisy up the output and are redundant
1578 // Make boxed primitive Strings look like such
1579 keys.splice(0, value.length);
1580 } else if (isBooleanObject(value)) {
1581 fn = Boolean.prototype.valueOf;
1582 type = 'Boolean';
1583 } else if (isBigIntObject(value)) {
1584 fn = BigInt.prototype.valueOf;
1585 type = 'BigInt';
1586 } else {
1587 fn = Symbol.prototype.valueOf;
1588 type = 'Symbol';
1589 }
1590 let base = `[${type}`;
1591 if (type !== constructor) {
1592 if (constructor === null) {
1593 base += ' (null prototype)';
1594 } else {
1595 base += ` (${constructor})`;
1596 }
1597 }
1598 base += `: ${formatPrimitive(stylizeNoColor, fn.call(value), ctx)}]`;
1599 if (tag !== '' && tag !== constructor) {
1600 base += ` [${tag}]`;
1601 }
1602 if (keys.length !== 0 || ctx.stylize === stylizeNoColor) return base;
1603 return ctx.stylize(base, type.toLowerCase() as Style);
1604}
1605 
1606function getClassBase(
1607 value: any,
1608 constructor: string | null,
1609 tag: string
1610): string {
1611 const hasName = Object.prototype.hasOwnProperty.call(value, 'name');
1612 const name = (hasName && value.name) || '(anonymous)';
1613 let base = `class ${name}`;
1614 if (constructor !== 'Function' && constructor !== null) {
1615 base += ` [${constructor}]`;
1616 }
1617 if (tag !== '' && constructor !== tag) {
1618 base += ` [${tag}]`;
1619 }
1620 if (constructor !== null) {
1621 const superName = Object.getPrototypeOf(value).name;
1622 if (superName) {
1623 base += ` extends ${superName}`;
1624 }
1625 } else {
1626 base += ' extends [null prototype]';
1627 }
1628 return `[${base}]`;
1629}
1630 
1631function getFunctionBase(
1632 ctx: Context,
1633 value: Function,
1634 constructor: string | null,
1635 tag: string
1636): string {
1637 const stringified = Function.prototype.toString.call(value);
1638 if (stringified.startsWith('class') && stringified.endsWith('}')) {
1639 const slice = stringified.slice(5, -1);
1640 const bracketIndex = slice.indexOf('{');
1641 if (
1642 bracketIndex !== -1 &&
1643 (!slice.slice(0, bracketIndex).includes('(') ||
1644 // Slow path to guarantee that it's indeed a class.
1645 classRegExp.exec(slice.replace(stripCommentsRegExp, '')) !== null)
1646 ) {
1647 return getClassBase(value, constructor, tag);
1648 }
1649 }
1650 let type = 'Function';
1651 if (isGeneratorFunction(value)) {
1652 type = `Generator${type}`;
1653 }
1654 if (isAsyncFunction(value)) {
1655 type = `Async${type}`;
1656 }
1657 let base = `[${type}`;
1658 if (constructor === null) {
1659 base += ' (null prototype)';
1660 }
1661 if (value.name === '') {
1662 base += ' (anonymous)';
1663 } else {
1664 base += `: ${typeof value.name === 'string' ? value.name : formatValue(ctx, value.name, NaN)}`;
1665 }
1666 base += ']';
1667 if (constructor !== type && constructor !== null) {
1668 base += ` ${constructor}`;
1669 }
1670 if (tag !== '' && constructor !== tag) {
1671 base += ` [${tag}]`;
1672 }
1673 return base;
1674}
1675 
1676export function identicalSequenceRange(
1677 a: unknown[],
1678 b: unknown[]
1679): { len: number; offset: number } {
1680 for (let i = 0; i < a.length - 3; i++) {
1681 // Find the first entry of b that matches the current entry of a.
1682 const pos = b.indexOf(a[i]);
1683 if (pos !== -1) {
1684 const rest = b.length - pos;
1685 if (rest > 3) {
1686 let len = 1;
1687 const maxLen = Math.min(a.length - i, rest);
1688 // Count the number of consecutive entries.
1689 while (maxLen > len && a[i + len] === b[pos + len]) {
1690 len++;
1691 }
1692 if (len > 3) {
1693 return { len, offset: i };
1694 }
1695 }
1696 }
1697 }
1698 
1699 return { len: 0, offset: 0 };
1700}
1701 
1702function getStackString(ctx: Context, error: Error): string {
1703 if (error.stack) {
1704 if (typeof error.stack === 'string') {
1705 return error.stack;
1706 }
1707 // This 'NaN' is a very strange Nodeism, but is necessary for correct behaviour!
1708 return formatValue(ctx, error.stack, NaN);
1709 }
1710 return Error.prototype.toString.call(error);
1711}
1712 
1713function getStackFrames(ctx: Context, err: Error, stack: string): string[] {
1714 const frames = stack.split('\n');
1715 
1716 let cause;
1717 try {
1718 ({ cause } = err);
1719 } catch {
1720 // If 'cause' is a getter that throws, ignore it.
1721 }
1722 
1723 // Remove stack frames identical to frames in cause.
1724 if (cause != null && isError(cause)) {
1725 const causeStack = getStackString(ctx, cause);
1726 const causeStackStart = causeStack.indexOf('\n at');
1727 if (causeStackStart !== -1) {
1728 const causeFrames = causeStack.slice(causeStackStart + 1).split('\n');
1729 const { len, offset } = identicalSequenceRange(frames, causeFrames);
1730 if (len > 0) {
1731 const skipped = len - 2;
1732 const msg = ` ... ${skipped} lines matching cause stack trace ...`;
1733 frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined'));
1734 }
1735 }
1736 }
1737 return frames;
1738}
1739 
1740function improveStack(
1741 stack: string,
1742 constructor: string | null,
1743 name: string | object,
1744 tag: string
1745): string {
1746 if (typeof name !== 'string') {
1747 stack = stack.replace(
1748 `${name}`,
1749 `${name} [${getPrefix(constructor, tag, 'Error').slice(0, -1)}]`
1750 );
1751 }
1752 
1753 // A stack trace may contain arbitrary data. Only manipulate the output
1754 // for "regular errors" (errors that "look normal") for now.
1755 let len = typeof name === 'string' ? name.length : undefined;
1756 
1757 if (
1758 constructor === null ||
1759 (typeof name === 'string' &&
1760 name.endsWith('Error') &&
1761 stack.startsWith(name) &&
1762 (stack.length === len ||
1763 stack[len as number] === ':' ||
1764 stack[len as number] === '\n'))
1765 ) {
1766 let fallback = 'Error';
1767 if (constructor === null) {
1768 const start =
1769 /^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/.exec(stack) ||
1770 /^([a-z_A-Z0-9-]*Error)$/.exec(stack);
1771 fallback = (start && start[1]) || '';
1772 len = fallback.length;
1773 fallback = fallback || 'Error';
1774 }
1775 const prefix = getPrefix(constructor, tag, fallback).slice(0, -1);
1776 if (name !== prefix) {
1777 if (typeof name === 'string' && prefix.includes(name)) {
1778 if (len === 0) {
1779 stack = `${prefix}: ${stack}`;
1780 } else {
1781 stack = `${prefix}${stack.slice(len)}`;
1782 }
1783 } else {
1784 stack = `${prefix} [${name}]${stack.slice(len)}`;
1785 }
1786 }
1787 }
1788 return stack;
1789}
1790 
1791function removeDuplicateErrorKeys(
1792 ctx: Context,
1793 keys: PropertyKey[],
1794 err: Error,
1795 stack: string
1796): void {
1797 if (!ctx.showHidden && keys.length !== 0) {
1798 for (const name of ['name', 'message', 'stack'] as const) {
1799 const index = keys.indexOf(name);
1800 // Only hide the property in case it's part of the original stack
1801 if (
1802 index !== -1 &&
1803 (typeof err[name] !== 'string' || stack.includes(err[name]!))
1804 ) {
1805 keys.splice(index, 1);
1806 }
1807 }
1808 }
1809}
1810 
1811function markNodeModules(ctx: Context, line: string): string {
1812 let tempLine = '';
1813 let nodeModule;
1814 let pos = 0;
1815 while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {
1816 // '/node_modules/'.length === 14
1817 tempLine += line.slice(pos, nodeModule.index + 14);
1818 tempLine += ctx.stylize(nodeModule[1]!, 'module');
1819 pos = nodeModule.index + nodeModule[0].length;
1820 }
1821 if (pos !== 0) {
1822 line = tempLine + line.slice(pos);
1823 }
1824 return line;
1825}
1826 
1827function formatError(
1828 err: Error,
1829 constructor: string | null,
1830 tag: string,
1831 ctx: Context,
1832 keys: PropertyKey[]
1833): string {
1834 const name = err.name != null ? (err.name as string | object) : 'Error';
1835 let stack = getStackString(ctx, err);
1836 
1837 removeDuplicateErrorKeys(ctx, keys, err, stack);
1838 
1839 if ('cause' in err && (keys.length === 0 || !keys.includes('cause'))) {
1840 keys.push('cause');
1841 }
1842 
1843 // Print errors aggregated into AggregateError
1844 if (
1845 Array.isArray((err as { errors?: unknown }).errors) &&
1846 (keys.length === 0 || !keys.includes('errors'))
1847 ) {
1848 keys.push('errors');
1849 }
1850 
1851 stack = improveStack(stack, constructor, name, tag);
1852 
1853 // Ignore the error message if it's contained in the stack.
1854 let pos = (err.message && stack.indexOf(err.message)) || -1;
1855 if (pos !== -1) pos += err.message.length;
1856 // Wrap the error in brackets in case it has no stack trace.
1857 const stackStart = stack.indexOf('\n at', pos);
1858 if (stackStart === -1) {
1859 stack = `[${stack}]`;
1860 } else {
1861 let newStack = stack.slice(0, stackStart);
1862 const stackFramePart = stack.slice(stackStart + 1);
1863 const lines = getStackFrames(ctx, err, stackFramePart);
1864 if (ctx.colors) {
1865 // Highlight userland code and node modules.
1866 for (let line of lines) {
1867 newStack += '\n';
1868 
1869 line = markNodeModules(ctx, line);
1870 
1871 newStack += line;
1872 }
1873 } else {
1874 newStack += `\n${lines.join('\n')}`;
1875 }
1876 stack = newStack;
1877 }
1878 // The message and the stack have to be indented as well!
1879 if (ctx.indentationLvl !== 0) {
1880 const indentation = ' '.repeat(ctx.indentationLvl);
1881 stack = stack.replaceAll('\n', `\n${indentation}`);
1882 }
1883 return stack;
1884}
1885 
1886function groupArrayElements(
1887 ctx: Context,
1888 output: string[],
1889 value: unknown[] | undefined
1890): string[] {
1891 let totalLength = 0;
1892 let maxLength = 0;
1893 let i = 0;
1894 let outputLength = output.length;
1895 if (ctx.maxArrayLength !== null && ctx.maxArrayLength < output.length) {
1896 // This makes sure the "... n more items" part is not taken into account.
1897 outputLength--;
1898 }
1899 const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
1900 const dataLen = Array.from<number>({ length: outputLength });
1901 // Calculate the total length of all output entries and the individual max
1902 // entries length of all output entries. We have to remove colors first,
1903 // otherwise the length would not be calculated properly.
1904 for (; i < outputLength; i++) {
1905 const len = getStringWidth(output[i] as string, ctx.colors);
1906 dataLen[i] = len;
1907 totalLength += len + separatorSpace;
1908 if (maxLength < len) maxLength = len;
1909 }
1910 // Add two to `maxLength` as we add a single whitespace character plus a comma
1911 // in-between two entries.
1912 const actualMax = maxLength + separatorSpace;
1913 // Check if at least three entries fit next to each other and prevent grouping
1914 // of arrays that contains entries of very different length (i.e., if a single
1915 // entry is longer than 1/5 of all other entries combined). Otherwise the
1916 // space in-between small entries would be enormous.
1917 if (
1918 actualMax * 3 + ctx.indentationLvl < ctx.breakLength &&
1919 (totalLength / actualMax > 5 || maxLength <= 6)
1920 ) {
1921 const approxCharHeights = 2.5;
1922 const averageBias = Math.sqrt(actualMax - totalLength / output.length);
1923 const biasedMax = Math.max(actualMax - 3 - averageBias, 1);
1924 // Dynamically check how many columns seem possible.
1925 const columns = Math.min(
1926 // Ideally a square should be drawn. We expect a character to be about 2.5
1927 // times as high as wide. This is the area formula to calculate a square
1928 // which contains n rectangles of size `actualMax * approxCharHeights`.
1929 // Divide that by `actualMax` to receive the correct number of columns.
1930 // The added bias increases the columns for short entries.
1931 Math.round(
1932 Math.sqrt(approxCharHeights * biasedMax * outputLength) / biasedMax
1933 ),
1934 // Do not exceed the breakLength.
1935 Math.floor((ctx.breakLength - ctx.indentationLvl) / actualMax),
1936 // Limit array grouping for small `compact` modes as the user requested
1937 // minimal grouping.
1938 (ctx.compact === false
1939 ? 0
1940 : ctx.compact === true
1941 ? inspectDefaultOptions.compact
1942 : ctx.compact) * 4,
1943 // Limit the columns to a maximum of fifteen.
1944 15
1945 );
1946 // Return with the original output if no grouping should happen.
1947 if (columns <= 1) {
1948 return output;
1949 }
1950 const tmp: string[] = [];
1951 const maxLineLength: number[] = [];
1952 for (let i = 0; i < columns; i++) {
1953 let lineMaxLength = 0;
1954 for (let j = i; j < output.length; j += columns) {
1955 if ((dataLen[j] as number) > lineMaxLength) {
1956 lineMaxLength = dataLen[j] as number;
1957 }
1958 }
1959 lineMaxLength += separatorSpace;
1960 maxLineLength[i] = lineMaxLength;
1961 }
1962 let order = String.prototype.padStart;
1963 if (value !== undefined) {
1964 for (let i = 0; i < output.length; i++) {
1965 if (typeof value[i] !== 'number' && typeof value[i] !== 'bigint') {
1966 order = String.prototype.padEnd;
1967 break;
1968 }
1969 }
1970 }
1971 // Each iteration creates a single line of grouped entries.
1972 for (let i = 0; i < outputLength; i += columns) {
1973 // The last lines may contain less entries than columns.
1974 const max = Math.min(i + columns, outputLength);
1975 let str = '';
1976 let j = i;
1977 for (; j < max - 1; j++) {
1978 // Calculate extra color padding in case it's active. This has to be
1979 // done line by line as some lines might contain more colors than
1980 // others.
1981 const padding =
1982 maxLineLength[j - i]! + output[j]!.length - (dataLen[j] as number);
1983 str += order.call(`${output[j]}, `, padding, ' ');
1984 }
1985 if (order === String.prototype.padStart) {
1986 const padding =
1987 maxLineLength[j - i]! +
1988 output[j]!.length -
1989 (dataLen[j] as number) -
1990 separatorSpace;
1991 str += output[j]!.padStart(padding, ' ');
1992 } else {
1993 str += output[j];
1994 }
1995 tmp.push(str);
1996 }
1997 if (ctx.maxArrayLength !== null && ctx.maxArrayLength < output.length) {
1998 tmp.push(output[outputLength]!);
1999 }
2000 output = tmp;
2001 }
2002 return output;
2003}
2004 
2005function handleMaxCallStackSize(
2006 ctx: Context,
2007 err: Error,
2008 constructorName: string,
2009 indentationLvl: number
2010): string {
2011 if (isStackOverflowError(err)) {
2012 ctx.seen.pop();
2013 ctx.indentationLvl = indentationLvl;
2014 return ctx.stylize(
2015 `[${constructorName}: Inspection interrupted ` +
2016 'prematurely. Maximum call stack size exceeded.]',
2017 'special'
2018 );
2019 }
2020 /* c8 ignore next */
2021 assert.fail(err.stack);
2022}
2023 
2024function addNumericSeparator(integerString: string): string {
2025 let result = '';
2026 let i = integerString.length;
2027 const start = integerString.startsWith('-') ? 1 : 0;
2028 for (; i >= start + 4; i -= 3) {
2029 result = `_${integerString.slice(i - 3, i)}${result}`;
2030 }
2031 return i === integerString.length
2032 ? integerString
2033 : `${integerString.slice(0, i)}${result}`;
2034}
2035 
2036function addNumericSeparatorEnd(integerString: string): string {
2037 let result = '';
2038 let i = 0;
2039 for (; i < integerString.length - 3; i += 3) {
2040 result += `${integerString.slice(i, i + 3)}_`;
2041 }
2042 return i === 0 ? integerString : `${result}${integerString.slice(i)}`;
2043}
2044 
2045const remainingText = (remaining: number) =>
2046 `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
2047 
2048function formatNumber(
2049 fn: InspectOptionsStylized['stylize'],
2050 number: number,
2051 numericSeparator?: boolean
2052): string {
2053 if (!numericSeparator) {
2054 // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.
2055 if (Object.is(number, -0)) {
2056 return fn('-0', 'number');
2057 }
2058 return fn(`${number}`, 'number');
2059 }
2060 const integer = Math.trunc(number);
2061 const string = String(integer);
2062 if (integer === number) {
2063 if (!Number.isFinite(number) || string.includes('e')) {
2064 return fn(string, 'number');
2065 }
2066 return fn(`${addNumericSeparator(string)}`, 'number');
2067 }
2068 if (Number.isNaN(number)) {
2069 return fn(string, 'number');
2070 }
2071 return fn(
2072 `${addNumericSeparator(string)}.${addNumericSeparatorEnd(
2073 String(number).slice(string.length + 1)
2074 )}`,
2075 'number'
2076 );
2077}
2078 
2079function formatBigInt(
2080 fn: InspectOptionsStylized['stylize'],
2081 bigint: bigint,
2082 numericSeparator?: boolean
2083): string {
2084 const string = String(bigint);
2085 if (!numericSeparator) {
2086 return fn(`${string}n`, 'bigint');
2087 }
2088 return fn(`${addNumericSeparator(string)}n`, 'bigint');
2089}
2090 
2091type Primitive = string | number | bigint | boolean | undefined | symbol;
2092function formatPrimitive(
2093 fn: InspectOptionsStylized['stylize'],
2094 value: Primitive,
2095 ctx: Context
2096): string {
2097 if (typeof value === 'string') {
2098 let trailer = '';
2099 if (ctx.maxStringLength !== null && value.length > ctx.maxStringLength) {
2100 const remaining = value.length - ctx.maxStringLength;
2101 value = value.slice(0, ctx.maxStringLength);
2102 trailer = `... ${remaining} more character${remaining > 1 ? 's' : ''}`;
2103 }
2104 if (
2105 ctx.compact !== true &&
2106 // We do not support handling Unicode characters width with
2107 // the readline getStringWidth function as there are
2108 // performance implications.
2109 value.length > kMinLineLength &&
2110 value.length > ctx.breakLength - ctx.indentationLvl - 4
2111 ) {
2112 return (
2113 value
2114 .split(/(?<=\n)/)
2115 .map((line) => fn(strEscape(line), 'string'))
2116 .join(` +\n${' '.repeat(ctx.indentationLvl + 2)}`) + trailer
2117 );
2118 }
2119 return fn(strEscape(value), 'string') + trailer;
2120 }
2121 if (typeof value === 'number')
2122 return formatNumber(fn, value, ctx.numericSeparator);
2123 if (typeof value === 'bigint')
2124 return formatBigInt(fn, value, ctx.numericSeparator);
2125 if (typeof value === 'boolean') return fn(`${value}`, 'boolean');
2126 if (typeof value === 'undefined') return fn('undefined', 'undefined');
2127 // es6 symbol primitive
2128 return fn(Symbol.prototype.toString.call(value), 'symbol');
2129}
2130 
2131function formatNamespaceObject(
2132 keys: PropertyKey[],
2133 ctx: Context,
2134 value: object,
2135 recurseTimes: number
2136): string[] {
2137 const output = new Array<string>(keys.length);
2138 for (let i = 0; i < keys.length; i++) {
2139 try {
2140 output[i] = formatProperty(
2141 ctx,
2142 value,
2143 recurseTimes,
2144 keys[i]!,
2145 kObjectType
2146 );
2147 } catch (err) {
2148 assert(isNativeError(err) && err.name === 'ReferenceError');
2149 // Use the existing functionality. This makes sure the indentation and
2150 // line breaks are always correct. Otherwise it is very difficult to keep
2151 // this aligned, even though this is a hacky way of dealing with this.
2152 const tmp = { [keys[i]!]: '' };
2153 output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i]!, kObjectType);
2154 const pos = output[i]!.lastIndexOf(' ');
2155 // We have to find the last whitespace and have to replace that value as
2156 // it will be visualized as a regular string.
2157 output[i] =
2158 output[i]!.slice(0, pos + 1) +
2159 ctx.stylize('<uninitialized>', 'special');
2160 }
2161 }
2162 // Reset the keys to an empty array. This prevents duplicated inspection.
2163 keys.length = 0;
2164 return output;
2165}
2166 
2167// The array is sparse and/or has extra keys
2168function formatSpecialArray(
2169 ctx: Context,
2170 value: unknown[],
2171 recurseTimes: number,
2172 maxLength: number,
2173 output: string[],
2174 i: number
2175): string[] {
2176 const keys = Object.keys(value);
2177 let index = i;
2178 for (; i < keys.length && output.length < maxLength; i++) {
2179 const key = keys[i]!;
2180 const tmp = +key;
2181 // Arrays can only have up to 2^32 - 1 entries
2182 if (tmp > 2 ** 32 - 2) {
2183 break;
2184 }
2185 if (`${index}` !== key) {
2186 if (numberRegExp.exec(key) === null) {
2187 break;
2188 }
2189 const emptyItems = tmp - index;
2190 const ending = emptyItems > 1 ? 's' : '';
2191 const message = `<${emptyItems} empty item${ending}>`;
2192 output.push(ctx.stylize(message, 'undefined'));
2193 index = tmp;
2194 if (output.length === maxLength) {
2195 break;
2196 }
2197 }
2198 output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType));
2199 index++;
2200 }
2201 const remaining = value.length - index;
2202 if (output.length !== maxLength) {
2203 if (remaining > 0) {
2204 const ending = remaining > 1 ? 's' : '';
2205 const message = `<${remaining} empty item${ending}>`;
2206 output.push(ctx.stylize(message, 'undefined'));
2207 }
2208 } else if (remaining > 0) {
2209 output.push(remainingText(remaining));
2210 }
2211 return output;
2212}
2213 
2214function formatArrayBuffer(ctx: Context, value: ArrayBuffer): string[] {
2215 let buffer;
2216 try {
2217 buffer = new Uint8Array(value);
2218 } catch {
2219 return [ctx.stylize('(detached)', 'special')];
2220 }
2221 const maxArrayLength = ctx.maxArrayLength;
2222 let str = Buffer.prototype.hexSlice
2223 .call(buffer, 0, Math.min(maxArrayLength, buffer.length))
2224 .replace(/(.{2})/g, '$1 ')
2225 .trim();
2226 const remaining = buffer.length - maxArrayLength;
2227 if (remaining > 0)
2228 str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
2229 return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`];
2230}
2231 
2232function formatArray(
2233 ctx: Context,
2234 value: unknown[],
2235 recurseTimes: number
2236): string[] {
2237 const valLen = value.length;
2238 const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen);
2239 
2240 const remaining = valLen - len;
2241 const output: string[] = [];
2242 for (let i = 0; i < len; i++) {
2243 // Special handle sparse arrays.
2244 if (!Object.prototype.hasOwnProperty.call(value, i)) {
2245 return formatSpecialArray(ctx, value, recurseTimes, len, output, i);
2246 }
2247 output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType));
2248 }
2249 if (remaining > 0) {
2250 output.push(remainingText(remaining));
2251 }
2252 return output;
2253}
2254 
2255function formatTypedArray(
2256 value: NodeJS.TypedArray,
2257 length: number,
2258 ctx: Context,
2259 _ignored: unknown,
2260 recurseTimes: number
2261): string[] {
2262 const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);
2263 const remaining = value.length - maxLength;
2264 const output = new Array<string>(maxLength);
2265 const elementFormatter =
2266 value.length > 0 && typeof value[0] === 'number'
2267 ? formatNumber
2268 : formatBigInt;
2269 for (let i = 0; i < maxLength; ++i) {
2270 // @ts-expect-error `value[i]` assumed to be of correct numeric type
2271 output[i] = elementFormatter(ctx.stylize, value[i], ctx.numericSeparator);
2272 }
2273 if (remaining > 0) {
2274 output[maxLength] = remainingText(remaining);
2275 }
2276 if (ctx.showHidden) {
2277 // .buffer goes last, it's not a primitive like the others.
2278 // All besides `BYTES_PER_ELEMENT` are actually getters.
2279 ctx.indentationLvl += 2;
2280 for (const key of [
2281 'BYTES_PER_ELEMENT',
2282 'length',
2283 'byteLength',
2284 'byteOffset',
2285 'buffer',
2286 ] as const) {
2287 const str = formatValue(ctx, value[key], recurseTimes, true);
2288 output.push(`[${key}]: ${str}`);
2289 }
2290 ctx.indentationLvl -= 2;
2291 }
2292 return output;
2293}
2294 
2295function formatSet(
2296 value: Set<unknown> | IterableIterator<unknown>,
2297 ctx: Context,
2298 _ignored: unknown,
2299 recurseTimes: number
2300): string[] {
2301 const length = isSet(value) ? value.size : NaN;
2302 const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);
2303 const remaining = length - maxLength;
2304 const output: string[] = [];
2305 ctx.indentationLvl += 2;
2306 let i = 0;
2307 for (const v of value) {
2308 if (i >= maxLength) break;
2309 output.push(formatValue(ctx, v, recurseTimes));
2310 i++;
2311 }
2312 if (remaining > 0) {
2313 output.push(remainingText(remaining));
2314 }
2315 ctx.indentationLvl -= 2;
2316 return output;
2317}
2318 
2319function formatMap(
2320 value: Map<unknown, unknown> | IterableIterator<[unknown, unknown]>,
2321 ctx: Context,
2322 _ignored: unknown,
2323 recurseTimes: number
2324): string[] {
2325 const length = isMap(value) ? value.size : NaN;
2326 const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length);
2327 const remaining = length - maxLength;
2328 const output: string[] = [];
2329 ctx.indentationLvl += 2;
2330 let i = 0;
2331 for (const { 0: k, 1: v } of value) {
2332 if (i >= maxLength) break;
2333 output.push(
2334 `${formatValue(ctx, k, recurseTimes)} => ${formatValue(ctx, v, recurseTimes)}`
2335 );
2336 i++;
2337 }
2338 if (remaining > 0) {
2339 output.push(remainingText(remaining));
2340 }
2341 ctx.indentationLvl -= 2;
2342 return output;
2343}
2344 
2345function formatSetIterInner(
2346 ctx: Context,
2347 recurseTimes: number,
2348 entries: unknown[],
2349 state: number
2350): string[] {
2351 const maxArrayLength = Math.max(ctx.maxArrayLength, 0);
2352 const maxLength = Math.min(maxArrayLength, entries.length);
2353 const output = new Array<string>(maxLength);
2354 ctx.indentationLvl += 2;
2355 for (let i = 0; i < maxLength; i++) {
2356 output[i] = formatValue(ctx, entries[i], recurseTimes);
2357 }
2358 ctx.indentationLvl -= 2;
2359 if (state === kWeak && !ctx.sorted) {
2360 // Sort all entries to have a halfway reliable output (if more entries than
2361 // retrieved ones exist, we can not reliably return the same output) if the
2362 // output is not sorted anyway.
2363 output.sort();
2364 }
2365 const remaining = entries.length - maxLength;
2366 if (remaining > 0) {
2367 output.push(remainingText(remaining));
2368 }
2369 return output;
2370}
2371 
2372function formatMapIterInner(
2373 ctx: Context,
2374 recurseTimes: number,
2375 entries: unknown[],
2376 state: number
2377): string[] {
2378 const maxArrayLength = Math.max(ctx.maxArrayLength, 0);
2379 // Entries exist as [key1, val1, key2, val2, ...]
2380 const len = entries.length / 2;
2381 const remaining = len - maxArrayLength;
2382 const maxLength = Math.min(maxArrayLength, len);
2383 const output = new Array<string>(maxLength);
2384 let i = 0;
2385 ctx.indentationLvl += 2;
2386 if (state === kWeak) {
2387 for (; i < maxLength; i++) {
2388 const pos = i * 2;
2389 output[i] =
2390 `${formatValue(ctx, entries[pos], recurseTimes)} => ${formatValue(ctx, entries[pos + 1], recurseTimes)}`;
2391 }
2392 // Sort all entries to have a halfway reliable output (if more entries than
2393 // retrieved ones exist, we can not reliably return the same output) if the
2394 // output is not sorted anyway.
2395 if (!ctx.sorted) output.sort();
2396 } else {
2397 for (; i < maxLength; i++) {
2398 const pos = i * 2;
2399 const res = [
2400 formatValue(ctx, entries[pos], recurseTimes),
2401 formatValue(ctx, entries[pos + 1], recurseTimes),
2402 ];
2403 output[i] = reduceToSingleString(
2404 ctx,
2405 res,
2406 '',
2407 ['[', ']'],
2408 kArrayExtrasType,
2409 recurseTimes
2410 );
2411 }
2412 }
2413 ctx.indentationLvl -= 2;
2414 if (remaining > 0) {
2415 output.push(remainingText(remaining));
2416 }
2417 return output;
2418}
2419 
2420function formatWeakCollection(ctx: Context): string[] {
2421 return [ctx.stylize('<items unknown>', 'special')];
2422}
2423 
2424function formatWeakSet(
2425 ctx: Context,
2426 value: WeakSet<any>,
2427 recurseTimes: number
2428): string[] {
2429 const { entries } = internal.previewEntries(value)!;
2430 return formatSetIterInner(ctx, recurseTimes, entries, kWeak);
2431}
2432 
2433function formatWeakMap(
2434 ctx: Context,
2435 value: WeakMap<any, unknown>,
2436 recurseTimes: number
2437): string[] {
2438 const { entries } = internal.previewEntries(value)!;
2439 return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
2440}
2441 
2442function formatIterator(
2443 braces: [string, string],
2444 ctx: Context,
2445 value: Iterator<unknown>,
2446 recurseTimes: number
2447): string[] {
2448 const { entries, isKeyValue } = internal.previewEntries(value)!;
2449 if (isKeyValue) {
2450 // Mark entry iterators as such.
2451 braces[0] = braces[0].replace(/ Iterator] {$/, ' Entries] {');
2452 return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries);
2453 }
2454 
2455 return formatSetIterInner(ctx, recurseTimes, entries, kIterator);
2456}
2457 
2458function formatPromise(
2459 ctx: Context,
2460 value: Promise<unknown>,
2461 recurseTimes: number
2462): string[] {
2463 let output: string[];
2464 const { state, result } = internal.getPromiseDetails(value)!;
2465 if (state === internal.kPending) {
2466 output = [ctx.stylize('<pending>', 'special')];
2467 } else {
2468 ctx.indentationLvl += 2;
2469 const str = formatValue(ctx, result, recurseTimes);
2470 ctx.indentationLvl -= 2;
2471 output = [
2472 state === internal.kRejected
2473 ? `${ctx.stylize('<rejected>', 'special')} ${str}`
2474 : str,
2475 ];
2476 }
2477 return output;
2478}
2479 
2480function formatProperty(
2481 ctx: Context,
2482 value: object,
2483 recurseTimes: number,
2484 key: PropertyKey,
2485 type: number,
2486 desc?: PropertyDescriptor,
2487 original = value
2488): string {
2489 let name: string, str: string;
2490 let extra = ' ';
2491 desc = desc ||
2492 Object.getOwnPropertyDescriptor(value, key) || {
2493 value: (value as Record<PropertyKey, unknown>)[key],
2494 enumerable: true,
2495 };
2496 if (desc.value !== undefined) {
2497 const diff = ctx.compact !== true || type !== kObjectType ? 2 : 3;
2498 ctx.indentationLvl += diff;
2499 str = formatValue(ctx, desc.value, recurseTimes);
2500 if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) {
2501 extra = `\n${' '.repeat(ctx.indentationLvl)}`;
2502 }
2503 ctx.indentationLvl -= diff;
2504 } else if (desc.get !== undefined) {
2505 const label = desc.set !== undefined ? 'Getter/Setter' : 'Getter';
2506 const s = ctx.stylize;
2507 const sp = 'special';
2508 if (
2509 ctx.getters &&
2510 (ctx.getters === true ||
2511 (ctx.getters === 'get' && desc.set === undefined) ||
2512 (ctx.getters === 'set' && desc.set !== undefined))
2513 ) {
2514 try {
2515 const tmp = desc.get.call(original);
2516 ctx.indentationLvl += 2;
2517 if (tmp === null) {
2518 str = `${s(`[${label}:`, sp)} ${s('null', 'null')}${s(']', sp)}`;
2519 } else if (typeof tmp === 'object') {
2520 str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`;
2521 } else {
2522 const primitive = formatPrimitive(s, tmp, ctx);
2523 str = `${s(`[${label}:`, sp)} ${primitive}${s(']', sp)}`;
2524 }
2525 ctx.indentationLvl -= 2;
2526 } catch (err) {
2527 const message = `<Inspection threw (${isError(err) ? err.message : String(err)})>`;
2528 str = `${s(`[${label}:`, sp)} ${message}${s(']', sp)}`;
2529 }
2530 } else {
2531 str = ctx.stylize(`[${label}]`, sp);
2532 }
2533 } else if (desc.set !== undefined) {
2534 str = ctx.stylize('[Setter]', 'special');
2535 } else {
2536 str = ctx.stylize('undefined', 'undefined');
2537 }
2538 if (type === kArrayType) {
2539 return str;
2540 }
2541 if (typeof key === 'symbol') {
2542 const tmp = Symbol.prototype.toString
2543 .call(key)
2544 .replace(strEscapeSequencesReplacer, escapeFn);
2545 name = ctx.stylize(tmp, 'symbol');
2546 } else if (keyStrRegExp.exec(key as string) !== null) {
2547 name =
2548 key === '__proto__'
2549 ? "['__proto__']"
2550 : ctx.stylize(key as string, 'name');
2551 } else {
2552 name = ctx.stylize(strEscape(key as string), 'string');
2553 }
2554 if (desc.enumerable === false) {
2555 name = `[${name}]`;
2556 }
2557 return `${name}:${extra}${str}`;
2558}
2559 
2560function isBelowBreakLength(
2561 ctx: Context,
2562 output: string[],
2563 start: number,
2564 base: string
2565): boolean {
2566 // Each entry is separated by at least a comma. Thus, we start with a total
2567 // length of at least `output.length`. In addition, some cases have a
2568 // whitespace in-between each other that is added to the total as well.
2569 // TODO(BridgeAR): Add Unicode support. Use the readline getStringWidth
2570 // function. Check the performance overhead and make it an opt-in in case it's
2571 // significant.
2572 let totalLength = output.length + start;
2573 if (totalLength + output.length > ctx.breakLength) return false;
2574 for (let i = 0; i < output.length; i++) {
2575 if (ctx.colors) {
2576 totalLength += removeColors(output[i]!).length;
2577 } else {
2578 totalLength += output[i]!.length;
2579 }
2580 if (totalLength > ctx.breakLength) {
2581 return false;
2582 }
2583 }
2584 // Do not line up properties on the same line if `base` contains line breaks.
2585 return base === '' || !base.includes('\n');
2586}
2587 
2588function reduceToSingleString(
2589 ctx: Context,
2590 output: string[],
2591 base: string,
2592 braces: [string, string],
2593 extrasType: number,
2594 recurseTimes: number,
2595 value?: unknown
2596): string {
2597 if (ctx.compact !== true) {
2598 if (typeof ctx.compact === 'number' && ctx.compact >= 1) {
2599 // Memorize the original output length. In case the output is grouped,
2600 // prevent lining up the entries on a single line.
2601 const entries = output.length;
2602 // Group array elements together if the array contains at least six
2603 // separate entries.
2604 if (extrasType === kArrayExtrasType && entries > 6) {
2605 output = groupArrayElements(ctx, output, value as unknown[]);
2606 }
2607 // `ctx.currentDepth` is set to the most inner depth of the currently
2608 // inspected object part while `recurseTimes` is the actual current depth
2609 // that is inspected.
2610 //
2611 // Example:
2612 //
2613 // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } }
2614 //
2615 // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max
2616 // depth of 1.
2617 //
2618 // Consolidate all entries of the local most inner depth up to
2619 // `ctx.compact`, as long as the properties are smaller than
2620 // `ctx.breakLength`.
2621 if (
2622 ctx.currentDepth - recurseTimes < ctx.compact &&
2623 entries === output.length
2624 ) {
2625 // Line up all entries on a single line in case the entries do not
2626 // exceed `breakLength`. Add 10 as constant to start next to all other
2627 // factors that may reduce `breakLength`.
2628 const start =
2629 output.length +
2630 ctx.indentationLvl +
2631 braces[0].length +
2632 base.length +
2633 10;
2634 if (isBelowBreakLength(ctx, output, start, base)) {
2635 const joinedOutput = output.join(', ');
2636 if (!joinedOutput.includes('\n')) {
2637 return (
2638 `${base ? `${base} ` : ''}${braces[0]} ${joinedOutput}` +
2639 ` ${braces[1]}`
2640 );
2641 }
2642 }
2643 }
2644 }
2645 // Line up each entry on an individual line.
2646 const indentation = `\n${' '.repeat(ctx.indentationLvl)}`;
2647 return (
2648 `${base ? `${base} ` : ''}${braces[0]}${indentation} ` +
2649 `${output.join(`,${indentation} `)}${indentation}${braces[1]}`
2650 );
2651 }
2652 // Line up all entries on a single line in case the entries do not exceed
2653 // `breakLength`.
2654 if (isBelowBreakLength(ctx, output, 0, base)) {
2655 return (
2656 `${braces[0]}${base ? ` ${base}` : ''} ${output.join(', ')} ` + braces[1]
2657 );
2658 }
2659 const indentation = ' '.repeat(ctx.indentationLvl);
2660 // If the opening "brace" is too large, like in the case of "Set {",
2661 // we need to force the first item to be on the next line or the
2662 // items will not line up correctly.
2663 const ln =
2664 base === '' && braces[0].length === 1
2665 ? ' '
2666 : `${base ? ` ${base}` : ''}\n${indentation} `;
2667 // Line up each entry on an individual line.
2668 return `${braces[0]}${ln}${output.join(`,\n${indentation} `)} ${braces[1]}`;
2669}
2670 
2671function hasBuiltInToString(value: object): boolean {
2672 // Prevent triggering proxy traps.
2673 const proxyTarget = internal.getProxyDetails(value);
2674 if (proxyTarget !== undefined) {
2675 if (proxyTarget === null || proxyTarget.target === null) {
2676 return true;
2677 }
2678 return hasBuiltInToString(proxyTarget.target as object);
2679 }
2680 
2681 // Count objects that have no `toString` function as built-in.
2682 if (typeof value?.toString !== 'function') {
2683 return true;
2684 }
2685 
2686 // The object has a own `toString` property. Thus it's not not a built-in one.
2687 if (Object.prototype.hasOwnProperty.call(value, 'toString')) {
2688 return false;
2689 }
2690 
2691 // Find the object that has the `toString` property as own property in the
2692 // prototype chain.
2693 let pointer = value;
2694 do {
2695 pointer = Object.getPrototypeOf(pointer);
2696 } while (!Object.prototype.hasOwnProperty.call(pointer, 'toString'));
2697 
2698 // Check closer if the object is a built-in.
2699 const descriptor = Object.getOwnPropertyDescriptor(pointer, 'constructor');
2700 return (
2701 descriptor !== undefined &&
2702 typeof descriptor.value === 'function' &&
2703 builtInObjects.has(descriptor.value.name)
2704 );
2705}
2706 
2707const firstErrorLine = (error: unknown) =>
2708 (isError(error) ? error.message : String(error)).split('\n', 1)[0];
2709let CIRCULAR_ERROR_MESSAGE: string | undefined;
2710function tryStringify(arg: unknown): string {
2711 try {
2712 return JSON.stringify(arg);
2713 } catch (err) {
2714 // Populate the circular error message lazily
2715 if (!CIRCULAR_ERROR_MESSAGE) {
2716 try {
2717 const a: { a?: unknown } = {};
2718 a.a = a;
2719 JSON.stringify(a);
2720 } catch (circularError) {
2721 CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);
2722 }
2723 }
2724 if (
2725 typeof err === 'object' &&
2726 err !== null &&
2727 'name' in err &&
2728 err.name === 'TypeError' &&
2729 firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE
2730 ) {
2731 return '[Circular]';
2732 }
2733 throw err;
2734 }
2735}
2736 
2737export function format(...args: unknown[]): string {
2738 return formatWithOptionsInternal(undefined, args);
2739}
2740 
2741export function formatWithOptions(
2742 inspectOptions: InspectOptions,
2743 ...args: unknown[]
2744): string {
2745 validateObject(inspectOptions, 'inspectOptions', kValidateObjectAllowArray);
2746 return formatWithOptionsInternal(inspectOptions, args);
2747}
2748 
2749function formatNumberNoColor(number: number, options?: InspectOptions): string {
2750 return formatNumber(
2751 stylizeNoColor,
2752 number,
2753 options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
2754 );
2755}
2756 
2757function formatBigIntNoColor(bigint: bigint, options?: InspectOptions): string {
2758 return formatBigInt(
2759 stylizeNoColor,
2760 bigint,
2761 options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
2762 );
2763}
2764 
2765function formatWithOptionsInternal(
2766 inspectOptions: InspectOptions | undefined,
2767 args: unknown[]
2768): string {
2769 const first = args[0];
2770 let a = 0;
2771 let str = '';
2772 let join = '';
2773 
2774 if (typeof first === 'string') {
2775 if (args.length === 1) {
2776 return first;
2777 }
2778 let tempStr;
2779 let lastPos = 0;
2780 
2781 for (let i = 0; i < first.length - 1; i++) {
2782 if (first.charCodeAt(i) === 37) {
2783 // '%'
2784 const nextChar = first.charCodeAt(++i);
2785 if (a + 1 !== args.length) {
2786 switch (nextChar) {
2787 case 115: {
2788 // 's'
2789 const tempArg = args[++a];
2790 if (typeof tempArg === 'number') {
2791 tempStr = formatNumberNoColor(tempArg, inspectOptions);
2792 } else if (typeof tempArg === 'bigint') {
2793 tempStr = formatBigIntNoColor(tempArg, inspectOptions);
2794 } else if (
2795 typeof tempArg !== 'object' ||
2796 tempArg === null ||
2797 !hasBuiltInToString(tempArg)
2798 ) {
2799 tempStr = String(tempArg);
2800 } else {
2801 tempStr = inspect(tempArg, {
2802 ...inspectOptions,
2803 compact: 3,
2804 colors: false,
2805 depth: 0,
2806 });
2807 }
2808 break;
2809 }
2810 case 106: // 'j'
2811 tempStr = tryStringify(args[++a]);
2812 break;
2813 case 100: {
2814 // 'd'
2815 const tempNum = args[++a];
2816 if (typeof tempNum === 'bigint') {
2817 tempStr = formatBigIntNoColor(tempNum, inspectOptions);
2818 } else if (typeof tempNum === 'symbol') {
2819 tempStr = 'NaN';
2820 } else {
2821 tempStr = formatNumberNoColor(Number(tempNum), inspectOptions);
2822 }
2823 break;
2824 }
2825 case 79: // 'O'
2826 tempStr = inspect(args[++a], inspectOptions);
2827 break;
2828 case 111: // 'o'
2829 tempStr = inspect(args[++a], {
2830 ...inspectOptions,
2831 showHidden: true,
2832 showProxy: true,
2833 depth: 4,
2834 });
2835 break;
2836 case 105: {
2837 // 'i'
2838 const tempInteger = args[++a];
2839 if (typeof tempInteger === 'bigint') {
2840 tempStr = formatBigIntNoColor(tempInteger, inspectOptions);
2841 } else if (typeof tempInteger === 'symbol') {
2842 tempStr = 'NaN';
2843 } else {
2844 tempStr = formatNumberNoColor(
2845 Number.parseInt(tempInteger as unknown as string),
2846 inspectOptions
2847 );
2848 }
2849 break;
2850 }
2851 case 102: {
2852 // 'f'
2853 const tempFloat = args[++a];
2854 if (typeof tempFloat === 'symbol') {
2855 tempStr = 'NaN';
2856 } else {
2857 tempStr = formatNumberNoColor(
2858 Number.parseFloat(tempFloat as unknown as string),
2859 inspectOptions
2860 );
2861 }
2862 break;
2863 }
2864 case 99: // 'c'
2865 a += 1;
2866 tempStr = '';
2867 break;
2868 case 37: // '%'
2869 str += first.slice(lastPos, i);
2870 lastPos = i + 1;
2871 continue;
2872 default: // Any other character is not a correct placeholder
2873 continue;
2874 }
2875 if (lastPos !== i - 1) {
2876 str += first.slice(lastPos, i - 1);
2877 }
2878 str += tempStr;
2879 lastPos = i + 1;
2880 } else if (nextChar === 37) {
2881 str += first.slice(lastPos, i);
2882 lastPos = i + 1;
2883 }
2884 }
2885 }
2886 if (lastPos !== 0) {
2887 a++;
2888 join = ' ';
2889 if (lastPos < first.length) {
2890 str += first.slice(lastPos);
2891 }
2892 }
2893 }
2894 
2895 while (a < args.length) {
2896 const value = args[a];
2897 str += join;
2898 str += typeof value !== 'string' ? inspect(value, inspectOptions) : value;
2899 join = ' ';
2900 a++;
2901 }
2902 return str;
2903}
2904 
2905export function isZeroWidthCodePoint(code: number): boolean {
2906 return (
2907 code <= 0x1f || // C0 control codes
2908 (code >= 0x7f && code <= 0x9f) || // C1 control codes
2909 (code >= 0x300 && code <= 0x36f) || // Combining Diacritical Marks
2910 (code >= 0x200b && code <= 0x200f) || // Modifying Invisible Characters
2911 // Combining Diacritical Marks for Symbols
2912 (code >= 0x20d0 && code <= 0x20ff) ||
2913 (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors
2914 (code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks
2915 (code >= 0xe0100 && code <= 0xe01ef)
2916 ); // Variation Selectors
2917}
2918 
2919/**
2920 * Returns the number of columns required to display the given string.
2921 */
2922export function getStringWidth(str: string, removeControlChars = true): number {
2923 let width = 0;
2924 
2925 if (removeControlChars) str = stripVTControlCharacters(str);
2926 str = str.normalize('NFC');
2927 for (const char of str) {
2928 const code = char.codePointAt(0)!;
2929 if (isFullWidthCodePoint(code)) {
2930 width += 2;
2931 } else if (!isZeroWidthCodePoint(code)) {
2932 width++;
2933 }
2934 }
2935 
2936 return width;
2937}
2938 
2939/**
2940 * Returns true if the character represented by a given
2941 * Unicode code point is full-width. Otherwise returns false.
2942 */
2943const isFullWidthCodePoint = (code: number) => {
2944 // Code points are partially derived from:
2945 // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
2946 return (
2947 code >= 0x1100 &&
2948 (code <= 0x115f || // Hangul Jamo
2949 code === 0x2329 || // LEFT-POINTING ANGLE BRACKET
2950 code === 0x232a || // RIGHT-POINTING ANGLE BRACKET
2951 // CJK Radicals Supplement .. Enclosed CJK Letters and Months
2952 (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
2953 // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
2954 (code >= 0x3250 && code <= 0x4dbf) ||
2955 // CJK Unified Ideographs .. Yi Radicals
2956 (code >= 0x4e00 && code <= 0xa4c6) ||
2957 // Hangul Jamo Extended-A
2958 (code >= 0xa960 && code <= 0xa97c) ||
2959 // Hangul Syllables
2960 (code >= 0xac00 && code <= 0xd7a3) ||
2961 // CJK Compatibility Ideographs
2962 (code >= 0xf900 && code <= 0xfaff) ||
2963 // Vertical Forms
2964 (code >= 0xfe10 && code <= 0xfe19) ||
2965 // CJK Compatibility Forms .. Small Form Variants
2966 (code >= 0xfe30 && code <= 0xfe6b) ||
2967 // Halfwidth and Fullwidth Forms
2968 (code >= 0xff01 && code <= 0xff60) ||
2969 (code >= 0xffe0 && code <= 0xffe6) ||
2970 // Kana Supplement
2971 (code >= 0x1b000 && code <= 0x1b001) ||
2972 // Enclosed Ideographic Supplement
2973 (code >= 0x1f200 && code <= 0x1f251) ||
2974 // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff
2975 // Emoticons 0x1f600 - 0x1f64f
2976 (code >= 0x1f300 && code <= 0x1f64f) ||
2977 // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
2978 (code >= 0x20000 && code <= 0x3fffd))
2979 );
2980};
2981 
2982/**
2983 * Remove all VT control characters. Use to estimate displayed string width.
2984 */
2985export function stripVTControlCharacters(str: string): string {
2986 validateString(str, 'str');
2987 
2988 return str.replace(ansi, '');
2989}
2990 
2991// ================================================================================================
2992// WORKERD SPECIFIC CODE
2993 
2994// Called from C++ on `console.log()`s to format values
2995export function formatLog(
2996 ...args: [
2997 ...values: unknown[],
2998 colors: boolean,
2999 structuredLogging: boolean,
3000 level: string,
3001 ]
3002): string {
3003 const level = args.pop() as string;
3004 const structuredLogging = args.pop() as boolean;
3005 const colors = args.pop() as boolean;
3006 const inspectOptions: InspectOptions = { colors };
3007 
3008 try {
3009 const message = formatWithOptions(inspectOptions, ...args);
3010 if (structuredLogging) {
3011 return JSON.stringify({
3012 timestamp: Date.now(),
3013 level,
3014 message,
3015 });
3016 } else {
3017 return message;
3018 }
3019 } catch (err) {
3020 return `<Formatting threw (${isError(err) ? err.stack : String(err)})>`;
3021 }
3022}
3023 
3024function isBuiltinPrototype(proto: unknown) {
3025 if (proto === null) return true;
3026 // JSG resource type prototypes carry the kResourceTypeInspect symbol.
3027 // These are not "built-in" in the JS-engine sense even though their
3028 // constructors are own properties of globalThis (per Web IDL). The
3029 // prototype walk must continue through them to collect accessor
3030 // properties (e.g. Blob.prototype.size).
3031 if (
3032 typeof proto === 'object' &&
3033 proto !== null &&
3034 internal.kResourceTypeInspect in (proto as Record<PropertyKey, unknown>)
3035 ) {
3036 return false;
3037 }
3038 const descriptor = Object.getOwnPropertyDescriptor(proto, 'constructor');
3039 return (
3040 descriptor !== undefined &&
3041 typeof descriptor.value === 'function' &&
3042 builtInObjects.has(descriptor.value.name)
3043 );
3044}
3045 
3046function isRpcWildcardType(value: unknown) {
3047 return (
3048 value instanceof internalWorkers.RpcStub ||
3049 value instanceof internalWorkers.RpcPromise ||
3050 value instanceof internalWorkers.RpcProperty
3051 );
3052}
3053 
3054function isEntry(value: unknown): value is [unknown, unknown] {
3055 return Array.isArray(value) && value.length === 2;
3056}
3057function maybeGetEntries(
3058 value: Record<PropertyKey, unknown>
3059): [unknown, unknown][] | undefined {
3060 // If this value is an RPC type with a wildcard property handler (e.g. `RpcStub`), don't try to
3061 // call `entries()` on it. This won't be an `entries()` function, and calling it with `.call()`
3062 // would dispose the stub.
3063 if (isRpcWildcardType(value)) return;
3064 
3065 const entriesFunction = value['entries'] as any;
3066 if (typeof entriesFunction !== 'function') return;
3067 const entriesIterator: unknown = entriesFunction.call(value);
3068 if (typeof entriesIterator !== 'object' || entriesIterator === null) return;
3069 if (!(Symbol.iterator in entriesIterator)) return;
3070 const entries = Array.from(entriesIterator as Iterable<unknown>);
3071 if (!entries.every(isEntry)) return;
3072 return entries;
3073}
3074 
3075const kEntries = Symbol('kEntries');
3076function hasEntries(
3077 value: unknown
3078): value is { [kEntries]: [unknown, unknown][] } {
3079 return typeof value === 'object' && value !== null && kEntries in value;
3080}
3081 
3082// Default custom inspect implementation for JSG resource types
3083function formatJsgResourceType(
3084 this: Record<PropertyKey, unknown>,
3085 additionalProperties: Record<
3086 string,
3087 symbol /* value-func */ | false /* unimplemented-marker */
3088 >,
3089 depth: number,
3090 options: InspectOptionsStylized
3091): unknown {
3092 const name = this.constructor.name;
3093 if (depth < 0) return options.stylize(`[${name}]`, 'special');
3094 
3095 // Build a plain object for inspection. If this value has an `entries()` function, add those
3096 // entries for map-like `K => V` formatting. Note we can't use a `Map` here as a key may have
3097 // multiple values (e.g. URLSearchParams).
3098 const record: Record<PropertyKey, unknown> = {};
3099 const maybeEntries = maybeGetEntries(this);
3100 if (maybeEntries !== undefined) record[kEntries] = maybeEntries;
3101 
3102 // Add all instance and prototype non-function-valued properties
3103 let current: object = this;
3104 do {
3105 // `Object.getOwnPropertyDescriptor()` throws `Illegal Invocation` for our prototypes here.
3106 for (const key of Object.getOwnPropertyNames(current)) {
3107 // If this property is unimplemented, don't try to log it
3108 if (additionalProperties[key] === false) continue;
3109 const value = this[key];
3110 // Ignore function-valued and static properties
3111 if (
3112 typeof value === 'function' ||
3113 this.constructor.propertyIsEnumerable(key)
3114 )
3115 continue;
3116 record[key] = value;
3117 }
3118 } while (!isBuiltinPrototype((current = Object.getPrototypeOf(current))));
3119 
3120 // Add additional inspect-only properties as non-enumerable so they appear in square brackets
3121 for (const [key, symbol] of Object.entries(additionalProperties)) {
3122 // This is an additional property if it's not an unimplemented marker
3123 if (symbol !== false) {
3124 Object.defineProperty(record, key, {
3125 value: this[symbol],
3126 enumerable: false,
3127 });
3128 }
3129 }
3130 
3131 // Format the plain object
3132 const inspected = inspect(record, {
3133 ...options,
3134 depth: options.depth == null ? null : depth,
3135 showHidden: true, // Show non-enumerable inspect-only properties
3136 });
3137 
3138 if (maybeEntries === undefined) {
3139 return `${name} ${inspected}`;
3140 } else {
3141 // Inspecting a entries object gives something like `Object(1) { 'a' => '1' }`, whereas we want
3142 // something like `Headers(1) { 'a' => '1' }`.
3143 return `${name}${inspected.replace('Object', '')}`;
3144 }
3145}