• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  Array,
5  ArrayIsArray,
6  ArrayPrototypeFilter,
7  ArrayPrototypeForEach,
8  ArrayPrototypeIncludes,
9  ArrayPrototypeIndexOf,
10  ArrayPrototypeJoin,
11  ArrayPrototypeMap,
12  ArrayPrototypePop,
13  ArrayPrototypePush,
14  ArrayPrototypePushApply,
15  ArrayPrototypeSlice,
16  ArrayPrototypeSplice,
17  ArrayPrototypeSort,
18  ArrayPrototypeUnshift,
19  BigIntPrototypeValueOf,
20  BooleanPrototypeValueOf,
21  DatePrototypeGetTime,
22  DatePrototypeToISOString,
23  DatePrototypeToString,
24  ErrorPrototypeToString,
25  FunctionPrototypeBind,
26  FunctionPrototypeCall,
27  FunctionPrototypeToString,
28  JSONStringify,
29  MapPrototypeGetSize,
30  MapPrototypeEntries,
31  MathFloor,
32  MathMax,
33  MathMin,
34  MathRound,
35  MathSqrt,
36  MathTrunc,
37  Number,
38  NumberIsFinite,
39  NumberIsNaN,
40  NumberParseFloat,
41  NumberParseInt,
42  NumberPrototypeToString,
43  NumberPrototypeValueOf,
44  Object,
45  ObjectAssign,
46  ObjectCreate,
47  ObjectDefineProperty,
48  ObjectGetOwnPropertyDescriptor,
49  ObjectGetOwnPropertyNames,
50  ObjectGetOwnPropertySymbols,
51  ObjectGetPrototypeOf,
52  ObjectIs,
53  ObjectKeys,
54  ObjectPrototypeHasOwnProperty,
55  ObjectPrototypePropertyIsEnumerable,
56  ObjectSeal,
57  ObjectSetPrototypeOf,
58  ReflectApply,
59  ReflectOwnKeys,
60  RegExp,
61  RegExpPrototypeExec,
62  RegExpPrototypeSymbolReplace,
63  RegExpPrototypeSymbolSplit,
64  RegExpPrototypeToString,
65  SafeStringIterator,
66  SafeMap,
67  SafeSet,
68  SetPrototypeGetSize,
69  SetPrototypeValues,
70  String,
71  StringPrototypeCharCodeAt,
72  StringPrototypeCodePointAt,
73  StringPrototypeIncludes,
74  StringPrototypeIndexOf,
75  StringPrototypeLastIndexOf,
76  StringPrototypeNormalize,
77  StringPrototypePadEnd,
78  StringPrototypePadStart,
79  StringPrototypeRepeat,
80  StringPrototypeReplaceAll,
81  StringPrototypeSlice,
82  StringPrototypeSplit,
83  StringPrototypeEndsWith,
84  StringPrototypeStartsWith,
85  StringPrototypeToLowerCase,
86  StringPrototypeTrim,
87  StringPrototypeValueOf,
88  SymbolPrototypeToString,
89  SymbolPrototypeValueOf,
90  SymbolIterator,
91  SymbolToStringTag,
92  TypedArrayPrototypeGetLength,
93  TypedArrayPrototypeGetSymbolToStringTag,
94  Uint8Array,
95  globalThis,
96  uncurryThis,
97} = primordials;
98
99const {
100  constants: {
101    ALL_PROPERTIES,
102    ONLY_ENUMERABLE,
103    kPending,
104    kRejected,
105  },
106  getOwnNonIndexProperties,
107  getPromiseDetails,
108  getProxyDetails,
109  previewEntries,
110  getConstructorName: internalGetConstructorName,
111  getExternalValue,
112} = internalBinding('util');
113
114const {
115  customInspectSymbol,
116  isError,
117  join,
118  removeColors,
119} = require('internal/util');
120
121const {
122  isStackOverflowError,
123} = require('internal/errors');
124
125const {
126  isAsyncFunction,
127  isGeneratorFunction,
128  isAnyArrayBuffer,
129  isArrayBuffer,
130  isArgumentsObject,
131  isBoxedPrimitive,
132  isDataView,
133  isExternal,
134  isMap,
135  isMapIterator,
136  isModuleNamespaceObject,
137  isNativeError,
138  isPromise,
139  isSet,
140  isSetIterator,
141  isWeakMap,
142  isWeakSet,
143  isRegExp,
144  isDate,
145  isTypedArray,
146  isStringObject,
147  isNumberObject,
148  isBooleanObject,
149  isBigIntObject,
150} = require('internal/util/types');
151
152const assert = require('internal/assert');
153
154const { BuiltinModule } = require('internal/bootstrap/realm');
155const {
156  validateObject,
157  validateString,
158} = require('internal/validators');
159
160let hexSlice;
161let internalUrl;
162
163function pathToFileUrlHref(filepath) {
164  internalUrl ??= require('internal/url');
165  return internalUrl.pathToFileURL(filepath).href;
166}
167
168const builtInObjects = new SafeSet(
169  ArrayPrototypeFilter(
170    ObjectGetOwnPropertyNames(globalThis),
171    (e) => RegExpPrototypeExec(/^[A-Z][a-zA-Z0-9]+$/, e) !== null,
172  ),
173);
174
175// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
176const isUndetectableObject = (v) => typeof v === 'undefined' && v !== undefined;
177
178// These options must stay in sync with `getUserOptions`. So if any option will
179// be added or removed, `getUserOptions` must also be updated accordingly.
180const inspectDefaultOptions = ObjectSeal({
181  showHidden: false,
182  depth: 2,
183  colors: false,
184  customInspect: true,
185  showProxy: false,
186  maxArrayLength: 100,
187  maxStringLength: 10000,
188  breakLength: 80,
189  compact: 3,
190  sorted: false,
191  getters: false,
192  numericSeparator: false,
193});
194
195const kObjectType = 0;
196const kArrayType = 1;
197const kArrayExtrasType = 2;
198
199/* eslint-disable no-control-regex */
200const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
201const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
202const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
203const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c\x7f-\x9f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
204/* eslint-enable no-control-regex */
205
206const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
207const numberRegExp = /^(0|[1-9][0-9]*)$/;
208
209const coreModuleRegExp = /^ {4}at (?:[^/\\(]+ \(|)node:(.+):\d+:\d+\)?$/;
210const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
211
212const classRegExp = /^(\s+[^(]*?)\s*{/;
213// eslint-disable-next-line node-core/no-unescaped-regexp-dot
214const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g;
215
216const kMinLineLength = 16;
217
218// Constants to map the iterator state.
219const kWeak = 0;
220const kIterator = 1;
221const kMapEntries = 2;
222
223// Escaped control characters (plus the single quote and the backslash). Use
224// empty strings to fill up unused entries.
225const meta = [
226  '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07
227  '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F',           // x0F
228  '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17
229  '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F
230  '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '',      // x2F
231  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',         // x3F
232  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',         // x4F
233  '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '',     // x5F
234  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',         // x6F
235  '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F',    // x7F
236  '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87
237  '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F
238  '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97
239  '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F
240];
241
242// Regex used for ansi escape code splitting
243// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js
244// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore
245// Matches all ansi escape code sequences in a string
246const ansiPattern = '[\\u001B\\u009B][[\\]()#;?]*' +
247  '(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*' +
248  '|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)' +
249  '|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))';
250const ansi = new RegExp(ansiPattern, 'g');
251
252let getStringWidth;
253
254function getUserOptions(ctx, isCrossContext) {
255  const ret = {
256    stylize: ctx.stylize,
257    showHidden: ctx.showHidden,
258    depth: ctx.depth,
259    colors: ctx.colors,
260    customInspect: ctx.customInspect,
261    showProxy: ctx.showProxy,
262    maxArrayLength: ctx.maxArrayLength,
263    maxStringLength: ctx.maxStringLength,
264    breakLength: ctx.breakLength,
265    compact: ctx.compact,
266    sorted: ctx.sorted,
267    getters: ctx.getters,
268    numericSeparator: ctx.numericSeparator,
269    ...ctx.userOptions,
270  };
271
272  // Typically, the target value will be an instance of `Object`. If that is
273  // *not* the case, the object may come from another vm.Context, and we want
274  // to avoid passing it objects from this Context in that case, so we remove
275  // the prototype from the returned object itself + the `stylize()` function,
276  // and remove all other non-primitives, including non-primitive user options.
277  if (isCrossContext) {
278    ObjectSetPrototypeOf(ret, null);
279    for (const key of ObjectKeys(ret)) {
280      if ((typeof ret[key] === 'object' || typeof ret[key] === 'function') &&
281          ret[key] !== null) {
282        delete ret[key];
283      }
284    }
285    ret.stylize = ObjectSetPrototypeOf((value, flavour) => {
286      let stylized;
287      try {
288        stylized = `${ctx.stylize(value, flavour)}`;
289      } catch {
290        // Continue regardless of error.
291      }
292
293      if (typeof stylized !== 'string') return value;
294      // `stylized` is a string as it should be, which is safe to pass along.
295      return stylized;
296    }, null);
297  }
298
299  return ret;
300}
301
302/**
303 * Echos the value of any input. Tries to print the value out
304 * in the best way possible given the different types.
305 * @param {any} value The value to print out.
306 * @param {object} opts Optional options object that alters the output.
307 */
308/* Legacy: value, showHidden, depth, colors */
309function inspect(value, opts) {
310  // Default options
311  const ctx = {
312    budget: {},
313    indentationLvl: 0,
314    seen: [],
315    currentDepth: 0,
316    stylize: stylizeNoColor,
317    showHidden: inspectDefaultOptions.showHidden,
318    depth: inspectDefaultOptions.depth,
319    colors: inspectDefaultOptions.colors,
320    customInspect: inspectDefaultOptions.customInspect,
321    showProxy: inspectDefaultOptions.showProxy,
322    maxArrayLength: inspectDefaultOptions.maxArrayLength,
323    maxStringLength: inspectDefaultOptions.maxStringLength,
324    breakLength: inspectDefaultOptions.breakLength,
325    compact: inspectDefaultOptions.compact,
326    sorted: inspectDefaultOptions.sorted,
327    getters: inspectDefaultOptions.getters,
328    numericSeparator: inspectDefaultOptions.numericSeparator,
329  };
330  if (arguments.length > 1) {
331    // Legacy...
332    if (arguments.length > 2) {
333      if (arguments[2] !== undefined) {
334        ctx.depth = arguments[2];
335      }
336      if (arguments.length > 3 && arguments[3] !== undefined) {
337        ctx.colors = arguments[3];
338      }
339    }
340    // Set user-specified options
341    if (typeof opts === 'boolean') {
342      ctx.showHidden = opts;
343    } else if (opts) {
344      const optKeys = ObjectKeys(opts);
345      for (let i = 0; i < optKeys.length; ++i) {
346        const key = optKeys[i];
347        // TODO(BridgeAR): Find a solution what to do about stylize. Either make
348        // this function public or add a new API with a similar or better
349        // functionality.
350        if (
351          ObjectPrototypeHasOwnProperty(inspectDefaultOptions, key) ||
352          key === 'stylize') {
353          ctx[key] = opts[key];
354        } else if (ctx.userOptions === undefined) {
355          // This is required to pass through the actual user input.
356          ctx.userOptions = opts;
357        }
358      }
359    }
360  }
361  if (ctx.colors) ctx.stylize = stylizeWithColor;
362  if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity;
363  if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity;
364  return formatValue(ctx, value, 0);
365}
366inspect.custom = customInspectSymbol;
367
368ObjectDefineProperty(inspect, 'defaultOptions', {
369  __proto__: null,
370  get() {
371    return inspectDefaultOptions;
372  },
373  set(options) {
374    validateObject(options, 'options');
375    return ObjectAssign(inspectDefaultOptions, options);
376  },
377});
378
379// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics
380// Each color consists of an array with the color code as first entry and the
381// reset code as second entry.
382const defaultFG = 39;
383const defaultBG = 49;
384inspect.colors = {
385  __proto__: null,
386  reset: [0, 0],
387  bold: [1, 22],
388  dim: [2, 22], // Alias: faint
389  italic: [3, 23],
390  underline: [4, 24],
391  blink: [5, 25],
392  // Swap foreground and background colors
393  inverse: [7, 27], // Alias: swapcolors, swapColors
394  hidden: [8, 28], // Alias: conceal
395  strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut
396  doubleunderline: [21, 24], // Alias: doubleUnderline
397  black: [30, defaultFG],
398  red: [31, defaultFG],
399  green: [32, defaultFG],
400  yellow: [33, defaultFG],
401  blue: [34, defaultFG],
402  magenta: [35, defaultFG],
403  cyan: [36, defaultFG],
404  white: [37, defaultFG],
405  bgBlack: [40, defaultBG],
406  bgRed: [41, defaultBG],
407  bgGreen: [42, defaultBG],
408  bgYellow: [43, defaultBG],
409  bgBlue: [44, defaultBG],
410  bgMagenta: [45, defaultBG],
411  bgCyan: [46, defaultBG],
412  bgWhite: [47, defaultBG],
413  framed: [51, 54],
414  overlined: [53, 55],
415  gray: [90, defaultFG], // Alias: grey, blackBright
416  redBright: [91, defaultFG],
417  greenBright: [92, defaultFG],
418  yellowBright: [93, defaultFG],
419  blueBright: [94, defaultFG],
420  magentaBright: [95, defaultFG],
421  cyanBright: [96, defaultFG],
422  whiteBright: [97, defaultFG],
423  bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright
424  bgRedBright: [101, defaultBG],
425  bgGreenBright: [102, defaultBG],
426  bgYellowBright: [103, defaultBG],
427  bgBlueBright: [104, defaultBG],
428  bgMagentaBright: [105, defaultBG],
429  bgCyanBright: [106, defaultBG],
430  bgWhiteBright: [107, defaultBG],
431};
432
433function defineColorAlias(target, alias) {
434  ObjectDefineProperty(inspect.colors, alias, {
435    __proto__: null,
436    get() {
437      return this[target];
438    },
439    set(value) {
440      this[target] = value;
441    },
442    configurable: true,
443    enumerable: false,
444  });
445}
446
447defineColorAlias('gray', 'grey');
448defineColorAlias('gray', 'blackBright');
449defineColorAlias('bgGray', 'bgGrey');
450defineColorAlias('bgGray', 'bgBlackBright');
451defineColorAlias('dim', 'faint');
452defineColorAlias('strikethrough', 'crossedout');
453defineColorAlias('strikethrough', 'strikeThrough');
454defineColorAlias('strikethrough', 'crossedOut');
455defineColorAlias('hidden', 'conceal');
456defineColorAlias('inverse', 'swapColors');
457defineColorAlias('inverse', 'swapcolors');
458defineColorAlias('doubleunderline', 'doubleUnderline');
459
460// TODO(BridgeAR): Add function style support for more complex styles.
461// Don't use 'blue' not visible on cmd.exe
462inspect.styles = ObjectAssign(ObjectCreate(null), {
463  special: 'cyan',
464  number: 'yellow',
465  bigint: 'yellow',
466  boolean: 'yellow',
467  undefined: 'grey',
468  null: 'bold',
469  string: 'green',
470  symbol: 'green',
471  date: 'magenta',
472  // "name": intentionally not styling
473  // TODO(BridgeAR): Highlight regular expressions properly.
474  regexp: 'red',
475  module: 'underline',
476});
477
478function addQuotes(str, quotes) {
479  if (quotes === -1) {
480    return `"${str}"`;
481  }
482  if (quotes === -2) {
483    return `\`${str}\``;
484  }
485  return `'${str}'`;
486}
487
488function escapeFn(str) {
489  const charCode = StringPrototypeCharCodeAt(str);
490  return meta.length > charCode ? meta[charCode] : `\\u${NumberPrototypeToString(charCode, 16)}`;
491}
492
493// Escape control characters, single quotes and the backslash.
494// This is similar to JSON stringify escaping.
495function strEscape(str) {
496  let escapeTest = strEscapeSequencesRegExp;
497  let escapeReplace = strEscapeSequencesReplacer;
498  let singleQuote = 39;
499
500  // Check for double quotes. If not present, do not escape single quotes and
501  // instead wrap the text in double quotes. If double quotes exist, check for
502  // backticks. If they do not exist, use those as fallback instead of the
503  // double quotes.
504  if (StringPrototypeIncludes(str, "'")) {
505    // This invalidates the charCode and therefore can not be matched for
506    // anymore.
507    if (!StringPrototypeIncludes(str, '"')) {
508      singleQuote = -1;
509    } else if (!StringPrototypeIncludes(str, '`') &&
510               !StringPrototypeIncludes(str, '${')) {
511      singleQuote = -2;
512    }
513    if (singleQuote !== 39) {
514      escapeTest = strEscapeSequencesRegExpSingle;
515      escapeReplace = strEscapeSequencesReplacerSingle;
516    }
517  }
518
519  // Some magic numbers that worked out fine while benchmarking with v8 6.0
520  if (str.length < 5000 && RegExpPrototypeExec(escapeTest, str) === null)
521    return addQuotes(str, singleQuote);
522  if (str.length > 100) {
523    str = RegExpPrototypeSymbolReplace(escapeReplace, str, escapeFn);
524    return addQuotes(str, singleQuote);
525  }
526
527  let result = '';
528  let last = 0;
529  for (let i = 0; i < str.length; i++) {
530    const point = StringPrototypeCharCodeAt(str, i);
531    if (point === singleQuote ||
532        point === 92 ||
533        point < 32 ||
534        (point > 126 && point < 160)) {
535      if (last === i) {
536        result += meta[point];
537      } else {
538        result += `${StringPrototypeSlice(str, last, i)}${meta[point]}`;
539      }
540      last = i + 1;
541    } else if (point >= 0xd800 && point <= 0xdfff) {
542      if (point <= 0xdbff && i + 1 < str.length) {
543        const point = StringPrototypeCharCodeAt(str, i + 1);
544        if (point >= 0xdc00 && point <= 0xdfff) {
545          i++;
546          continue;
547        }
548      }
549      result += `${StringPrototypeSlice(str, last, i)}\\u${NumberPrototypeToString(point, 16)}`;
550      last = i + 1;
551    }
552  }
553
554  if (last !== str.length) {
555    result += StringPrototypeSlice(str, last);
556  }
557  return addQuotes(result, singleQuote);
558}
559
560function stylizeWithColor(str, styleType) {
561  const style = inspect.styles[styleType];
562  if (style !== undefined) {
563    const color = inspect.colors[style];
564    if (color !== undefined)
565      return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`;
566  }
567  return str;
568}
569
570function stylizeNoColor(str) {
571  return str;
572}
573
574// Return a new empty array to push in the results of the default formatter.
575function getEmptyFormatArray() {
576  return [];
577}
578
579function isInstanceof(object, proto) {
580  try {
581    return object instanceof proto;
582  } catch {
583    return false;
584  }
585}
586
587function getConstructorName(obj, ctx, recurseTimes, protoProps) {
588  let firstProto;
589  const tmp = obj;
590  while (obj || isUndetectableObject(obj)) {
591    const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
592    if (descriptor !== undefined &&
593        typeof descriptor.value === 'function' &&
594        descriptor.value.name !== '' &&
595        isInstanceof(tmp, descriptor.value)) {
596      if (protoProps !== undefined &&
597         (firstProto !== obj ||
598         !builtInObjects.has(descriptor.value.name))) {
599        addPrototypeProperties(
600          ctx, tmp, firstProto || tmp, recurseTimes, protoProps);
601      }
602      return String(descriptor.value.name);
603    }
604
605    obj = ObjectGetPrototypeOf(obj);
606    if (firstProto === undefined) {
607      firstProto = obj;
608    }
609  }
610
611  if (firstProto === null) {
612    return null;
613  }
614
615  const res = internalGetConstructorName(tmp);
616
617  if (recurseTimes > ctx.depth && ctx.depth !== null) {
618    return `${res} <Complex prototype>`;
619  }
620
621  const protoConstr = getConstructorName(
622    firstProto, ctx, recurseTimes + 1, protoProps);
623
624  if (protoConstr === null) {
625    return `${res} <${inspect(firstProto, {
626      ...ctx,
627      customInspect: false,
628      depth: -1,
629    })}>`;
630  }
631
632  return `${res} <${protoConstr}>`;
633}
634
635// This function has the side effect of adding prototype properties to the
636// `output` argument (which is an array). This is intended to highlight user
637// defined prototype properties.
638function addPrototypeProperties(ctx, main, obj, recurseTimes, output) {
639  let depth = 0;
640  let keys;
641  let keySet;
642  do {
643    if (depth !== 0 || main === obj) {
644      obj = ObjectGetPrototypeOf(obj);
645      // Stop as soon as a null prototype is encountered.
646      if (obj === null) {
647        return;
648      }
649      // Stop as soon as a built-in object type is detected.
650      const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
651      if (descriptor !== undefined &&
652          typeof descriptor.value === 'function' &&
653          builtInObjects.has(descriptor.value.name)) {
654        return;
655      }
656    }
657
658    if (depth === 0) {
659      keySet = new SafeSet();
660    } else {
661      ArrayPrototypeForEach(keys, (key) => keySet.add(key));
662    }
663    // Get all own property names and symbols.
664    keys = ReflectOwnKeys(obj);
665    ArrayPrototypePush(ctx.seen, main);
666    for (const key of keys) {
667      // Ignore the `constructor` property and keys that exist on layers above.
668      if (key === 'constructor' ||
669          ObjectPrototypeHasOwnProperty(main, key) ||
670          (depth !== 0 && keySet.has(key))) {
671        continue;
672      }
673      const desc = ObjectGetOwnPropertyDescriptor(obj, key);
674      if (typeof desc.value === 'function') {
675        continue;
676      }
677      const value = formatProperty(
678        ctx, obj, recurseTimes, key, kObjectType, desc, main);
679      if (ctx.colors) {
680        // Faint!
681        ArrayPrototypePush(output, `\u001b[2m${value}\u001b[22m`);
682      } else {
683        ArrayPrototypePush(output, value);
684      }
685    }
686    ArrayPrototypePop(ctx.seen);
687  // Limit the inspection to up to three prototype layers. Using `recurseTimes`
688  // is not a good choice here, because it's as if the properties are declared
689  // on the current object from the users perspective.
690  } while (++depth !== 3);
691}
692
693function getPrefix(constructor, tag, fallback, size = '') {
694  if (constructor === null) {
695    if (tag !== '' && fallback !== tag) {
696      return `[${fallback}${size}: null prototype] [${tag}] `;
697    }
698    return `[${fallback}${size}: null prototype] `;
699  }
700
701  if (tag !== '' && constructor !== tag) {
702    return `${constructor}${size} [${tag}] `;
703  }
704  return `${constructor}${size} `;
705}
706
707// Look up the keys of the object.
708function getKeys(value, showHidden) {
709  let keys;
710  const symbols = ObjectGetOwnPropertySymbols(value);
711  if (showHidden) {
712    keys = ObjectGetOwnPropertyNames(value);
713    if (symbols.length !== 0)
714      ArrayPrototypePushApply(keys, symbols);
715  } else {
716    // This might throw if `value` is a Module Namespace Object from an
717    // unevaluated module, but we don't want to perform the actual type
718    // check because it's expensive.
719    // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209
720    // and modify this logic as needed.
721    try {
722      keys = ObjectKeys(value);
723    } catch (err) {
724      assert(isNativeError(err) && err.name === 'ReferenceError' &&
725             isModuleNamespaceObject(value));
726      keys = ObjectGetOwnPropertyNames(value);
727    }
728    if (symbols.length !== 0) {
729      const filter = (key) => ObjectPrototypePropertyIsEnumerable(value, key);
730      ArrayPrototypePushApply(keys, ArrayPrototypeFilter(symbols, filter));
731    }
732  }
733  return keys;
734}
735
736function getCtxStyle(value, constructor, tag) {
737  let fallback = '';
738  if (constructor === null) {
739    fallback = internalGetConstructorName(value);
740    if (fallback === tag) {
741      fallback = 'Object';
742    }
743  }
744  return getPrefix(constructor, tag, fallback);
745}
746
747function formatProxy(ctx, proxy, recurseTimes) {
748  if (recurseTimes > ctx.depth && ctx.depth !== null) {
749    return ctx.stylize('Proxy [Array]', 'special');
750  }
751  recurseTimes += 1;
752  ctx.indentationLvl += 2;
753  const res = [
754    formatValue(ctx, proxy[0], recurseTimes),
755    formatValue(ctx, proxy[1], recurseTimes),
756  ];
757  ctx.indentationLvl -= 2;
758  return reduceToSingleString(
759    ctx, res, '', ['Proxy [', ']'], kArrayExtrasType, recurseTimes);
760}
761
762// Note: using `formatValue` directly requires the indentation level to be
763// corrected by setting `ctx.indentationLvL += diff` and then to decrease the
764// value afterwards again.
765function formatValue(ctx, value, recurseTimes, typedArray) {
766  // Primitive types cannot have properties.
767  if (typeof value !== 'object' &&
768      typeof value !== 'function' &&
769      !isUndetectableObject(value)) {
770    return formatPrimitive(ctx.stylize, value, ctx);
771  }
772  if (value === null) {
773    return ctx.stylize('null', 'null');
774  }
775
776  // Memorize the context for custom inspection on proxies.
777  const context = value;
778  // Always check for proxies to prevent side effects and to prevent triggering
779  // any proxy handlers.
780  const proxy = getProxyDetails(value, !!ctx.showProxy);
781  if (proxy !== undefined) {
782    if (proxy === null || proxy[0] === null) {
783      return ctx.stylize('<Revoked Proxy>', 'special');
784    }
785    if (ctx.showProxy) {
786      return formatProxy(ctx, proxy, recurseTimes);
787    }
788    value = proxy;
789  }
790
791  // Provide a hook for user-specified inspect functions.
792  // Check that value is an object with an inspect function on it.
793  if (ctx.customInspect) {
794    const maybeCustom = value[customInspectSymbol];
795    if (typeof maybeCustom === 'function' &&
796        // Filter out the util module, its inspect function is special.
797        maybeCustom !== inspect &&
798        // Also filter out any prototype objects using the circular check.
799        !(value.constructor && value.constructor.prototype === value)) {
800      // This makes sure the recurseTimes are reported as before while using
801      // a counter internally.
802      const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
803      const isCrossContext =
804        proxy !== undefined || !(context instanceof Object);
805      const ret = FunctionPrototypeCall(
806        maybeCustom,
807        context,
808        depth,
809        getUserOptions(ctx, isCrossContext),
810        inspect,
811      );
812      // If the custom inspection method returned `this`, don't go into
813      // infinite recursion.
814      if (ret !== context) {
815        if (typeof ret !== 'string') {
816          return formatValue(ctx, ret, recurseTimes);
817        }
818        return StringPrototypeReplaceAll(ret, '\n', `\n${StringPrototypeRepeat(' ', ctx.indentationLvl)}`);
819      }
820    }
821  }
822
823  // Using an array here is actually better for the average case than using
824  // a Set. `seen` will only check for the depth and will never grow too large.
825  if (ctx.seen.includes(value)) {
826    let index = 1;
827    if (ctx.circular === undefined) {
828      ctx.circular = new SafeMap();
829      ctx.circular.set(value, index);
830    } else {
831      index = ctx.circular.get(value);
832      if (index === undefined) {
833        index = ctx.circular.size + 1;
834        ctx.circular.set(value, index);
835      }
836    }
837    return ctx.stylize(`[Circular *${index}]`, 'special');
838  }
839
840  return formatRaw(ctx, value, recurseTimes, typedArray);
841}
842
843function formatRaw(ctx, value, recurseTimes, typedArray) {
844  let keys;
845  let protoProps;
846  if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) {
847    protoProps = [];
848  }
849
850  const constructor = getConstructorName(value, ctx, recurseTimes, protoProps);
851  // Reset the variable to check for this later on.
852  if (protoProps !== undefined && protoProps.length === 0) {
853    protoProps = undefined;
854  }
855
856  let tag = value[SymbolToStringTag];
857  // Only list the tag in case it's non-enumerable / not an own property.
858  // Otherwise we'd print this twice.
859  if (typeof tag !== 'string' ||
860      (tag !== '' &&
861      (ctx.showHidden ?
862        ObjectPrototypeHasOwnProperty :
863        ObjectPrototypePropertyIsEnumerable)(
864        value, SymbolToStringTag,
865      ))) {
866    tag = '';
867  }
868  let base = '';
869  let formatter = getEmptyFormatArray;
870  let braces;
871  let noIterator = true;
872  let i = 0;
873  const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE;
874
875  let extrasType = kObjectType;
876
877  // Iterators and the rest are split to reduce checks.
878  // We have to check all values in case the constructor is set to null.
879  // Otherwise it would not possible to identify all types properly.
880  if (SymbolIterator in value || constructor === null) {
881    noIterator = false;
882    if (ArrayIsArray(value)) {
883      // Only set the constructor for non ordinary ("Array [...]") arrays.
884      const prefix = (constructor !== 'Array' || tag !== '') ?
885        getPrefix(constructor, tag, 'Array', `(${value.length})`) :
886        '';
887      keys = getOwnNonIndexProperties(value, filter);
888      braces = [`${prefix}[`, ']'];
889      if (value.length === 0 && keys.length === 0 && protoProps === undefined)
890        return `${braces[0]}]`;
891      extrasType = kArrayExtrasType;
892      formatter = formatArray;
893    } else if (isSet(value)) {
894      const size = SetPrototypeGetSize(value);
895      const prefix = getPrefix(constructor, tag, 'Set', `(${size})`);
896      keys = getKeys(value, ctx.showHidden);
897      formatter = constructor !== null ?
898        FunctionPrototypeBind(formatSet, null, value) :
899        FunctionPrototypeBind(formatSet, null, SetPrototypeValues(value));
900      if (size === 0 && keys.length === 0 && protoProps === undefined)
901        return `${prefix}{}`;
902      braces = [`${prefix}{`, '}'];
903    } else if (isMap(value)) {
904      const size = MapPrototypeGetSize(value);
905      const prefix = getPrefix(constructor, tag, 'Map', `(${size})`);
906      keys = getKeys(value, ctx.showHidden);
907      formatter = constructor !== null ?
908        FunctionPrototypeBind(formatMap, null, value) :
909        FunctionPrototypeBind(formatMap, null, MapPrototypeEntries(value));
910      if (size === 0 && keys.length === 0 && protoProps === undefined)
911        return `${prefix}{}`;
912      braces = [`${prefix}{`, '}'];
913    } else if (isTypedArray(value)) {
914      keys = getOwnNonIndexProperties(value, filter);
915      let bound = value;
916      let fallback = '';
917      if (constructor === null) {
918        fallback = TypedArrayPrototypeGetSymbolToStringTag(value);
919        // Reconstruct the array information.
920        bound = new primordials[fallback](value);
921      }
922      const size = TypedArrayPrototypeGetLength(value);
923      const prefix = getPrefix(constructor, tag, fallback, `(${size})`);
924      braces = [`${prefix}[`, ']'];
925      if (value.length === 0 && keys.length === 0 && !ctx.showHidden)
926        return `${braces[0]}]`;
927      // Special handle the value. The original value is required below. The
928      // bound function is required to reconstruct missing information.
929      formatter = FunctionPrototypeBind(formatTypedArray, null, bound, size);
930      extrasType = kArrayExtrasType;
931    } else if (isMapIterator(value)) {
932      keys = getKeys(value, ctx.showHidden);
933      braces = getIteratorBraces('Map', tag);
934      // Add braces to the formatter parameters.
935      formatter = FunctionPrototypeBind(formatIterator, null, braces);
936    } else if (isSetIterator(value)) {
937      keys = getKeys(value, ctx.showHidden);
938      braces = getIteratorBraces('Set', tag);
939      // Add braces to the formatter parameters.
940      formatter = FunctionPrototypeBind(formatIterator, null, braces);
941    } else {
942      noIterator = true;
943    }
944  }
945  if (noIterator) {
946    keys = getKeys(value, ctx.showHidden);
947    braces = ['{', '}'];
948    if (constructor === 'Object') {
949      if (isArgumentsObject(value)) {
950        braces[0] = '[Arguments] {';
951      } else if (tag !== '') {
952        braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
953      }
954      if (keys.length === 0 && protoProps === undefined) {
955        return `${braces[0]}}`;
956      }
957    } else if (typeof value === 'function') {
958      base = getFunctionBase(value, constructor, tag);
959      if (keys.length === 0 && protoProps === undefined)
960        return ctx.stylize(base, 'special');
961    } else if (isRegExp(value)) {
962      // Make RegExps say that they are RegExps
963      base = RegExpPrototypeToString(
964        constructor !== null ? value : new RegExp(value),
965      );
966      const prefix = getPrefix(constructor, tag, 'RegExp');
967      if (prefix !== 'RegExp ')
968        base = `${prefix}${base}`;
969      if ((keys.length === 0 && protoProps === undefined) ||
970          (recurseTimes > ctx.depth && ctx.depth !== null)) {
971        return ctx.stylize(base, 'regexp');
972      }
973    } else if (isDate(value)) {
974      // Make dates with properties first say the date
975      base = NumberIsNaN(DatePrototypeGetTime(value)) ?
976        DatePrototypeToString(value) :
977        DatePrototypeToISOString(value);
978      const prefix = getPrefix(constructor, tag, 'Date');
979      if (prefix !== 'Date ')
980        base = `${prefix}${base}`;
981      if (keys.length === 0 && protoProps === undefined) {
982        return ctx.stylize(base, 'date');
983      }
984    } else if (isError(value)) {
985      base = formatError(value, constructor, tag, ctx, keys);
986      if (keys.length === 0 && protoProps === undefined)
987        return base;
988    } else if (isAnyArrayBuffer(value)) {
989      // Fast path for ArrayBuffer and SharedArrayBuffer.
990      // Can't do the same for DataView because it has a non-primitive
991      // .buffer property that we need to recurse for.
992      const arrayType = isArrayBuffer(value) ? 'ArrayBuffer' :
993        'SharedArrayBuffer';
994      const prefix = getPrefix(constructor, tag, arrayType);
995      if (typedArray === undefined) {
996        formatter = formatArrayBuffer;
997      } else if (keys.length === 0 && protoProps === undefined) {
998        return prefix +
999              `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`;
1000      }
1001      braces[0] = `${prefix}{`;
1002      ArrayPrototypeUnshift(keys, 'byteLength');
1003    } else if (isDataView(value)) {
1004      braces[0] = `${getPrefix(constructor, tag, 'DataView')}{`;
1005      // .buffer goes last, it's not a primitive like the others.
1006      ArrayPrototypeUnshift(keys, 'byteLength', 'byteOffset', 'buffer');
1007    } else if (isPromise(value)) {
1008      braces[0] = `${getPrefix(constructor, tag, 'Promise')}{`;
1009      formatter = formatPromise;
1010    } else if (isWeakSet(value)) {
1011      braces[0] = `${getPrefix(constructor, tag, 'WeakSet')}{`;
1012      formatter = ctx.showHidden ? formatWeakSet : formatWeakCollection;
1013    } else if (isWeakMap(value)) {
1014      braces[0] = `${getPrefix(constructor, tag, 'WeakMap')}{`;
1015      formatter = ctx.showHidden ? formatWeakMap : formatWeakCollection;
1016    } else if (isModuleNamespaceObject(value)) {
1017      braces[0] = `${getPrefix(constructor, tag, 'Module')}{`;
1018      // Special handle keys for namespace objects.
1019      formatter = FunctionPrototypeBind(formatNamespaceObject, null, keys);
1020    } else if (isBoxedPrimitive(value)) {
1021      base = getBoxedBase(value, ctx, keys, constructor, tag);
1022      if (keys.length === 0 && protoProps === undefined) {
1023        return base;
1024      }
1025    } else {
1026      if (keys.length === 0 && protoProps === undefined) {
1027        if (isExternal(value)) {
1028          const address = getExternalValue(value).toString(16);
1029          return ctx.stylize(`[External: ${address}]`, 'special');
1030        }
1031        return `${getCtxStyle(value, constructor, tag)}{}`;
1032      }
1033      braces[0] = `${getCtxStyle(value, constructor, tag)}{`;
1034    }
1035  }
1036
1037  if (recurseTimes > ctx.depth && ctx.depth !== null) {
1038    let constructorName = StringPrototypeSlice(getCtxStyle(value, constructor, tag), 0, -1);
1039    if (constructor !== null)
1040      constructorName = `[${constructorName}]`;
1041    return ctx.stylize(constructorName, 'special');
1042  }
1043  recurseTimes += 1;
1044
1045  ArrayPrototypePush(ctx.seen, value);
1046  ctx.currentDepth = recurseTimes;
1047  let output;
1048  const indentationLvl = ctx.indentationLvl;
1049  try {
1050    output = formatter(ctx, value, recurseTimes);
1051    for (i = 0; i < keys.length; i++) {
1052      ArrayPrototypePush(
1053        output,
1054        formatProperty(ctx, value, recurseTimes, keys[i], extrasType),
1055      );
1056    }
1057    if (protoProps !== undefined) {
1058      ArrayPrototypePushApply(output, protoProps);
1059    }
1060  } catch (err) {
1061    const constructorName = StringPrototypeSlice(getCtxStyle(value, constructor, tag), 0, -1);
1062    return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
1063  }
1064  if (ctx.circular !== undefined) {
1065    const index = ctx.circular.get(value);
1066    if (index !== undefined) {
1067      const reference = ctx.stylize(`<ref *${index}>`, 'special');
1068      // Add reference always to the very beginning of the output.
1069      if (ctx.compact !== true) {
1070        base = base === '' ? reference : `${reference} ${base}`;
1071      } else {
1072        braces[0] = `${reference} ${braces[0]}`;
1073      }
1074    }
1075  }
1076  ArrayPrototypePop(ctx.seen);
1077
1078  if (ctx.sorted) {
1079    const comparator = ctx.sorted === true ? undefined : ctx.sorted;
1080    if (extrasType === kObjectType) {
1081      ArrayPrototypeSort(output, comparator);
1082    } else if (keys.length > 1) {
1083      const sorted = ArrayPrototypeSort(ArrayPrototypeSlice(output, output.length - keys.length), comparator);
1084      ArrayPrototypeUnshift(sorted, output, output.length - keys.length, keys.length);
1085      ReflectApply(ArrayPrototypeSplice, null, sorted);
1086    }
1087  }
1088
1089  const res = reduceToSingleString(
1090    ctx, output, base, braces, extrasType, recurseTimes, value);
1091  const budget = ctx.budget[ctx.indentationLvl] || 0;
1092  const newLength = budget + res.length;
1093  ctx.budget[ctx.indentationLvl] = newLength;
1094  // If any indentationLvl exceeds this limit, limit further inspecting to the
1095  // minimum. Otherwise the recursive algorithm might continue inspecting the
1096  // object even though the maximum string size (~2 ** 28 on 32 bit systems and
1097  // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at
1098  // exactly 2 ** 27 but a bit higher. This depends on the object shape.
1099  // This limit also makes sure that huge objects don't block the event loop
1100  // significantly.
1101  if (newLength > 2 ** 27) {
1102    ctx.depth = -1;
1103  }
1104  return res;
1105}
1106
1107function getIteratorBraces(type, tag) {
1108  if (tag !== `${type} Iterator`) {
1109    if (tag !== '')
1110      tag += '] [';
1111    tag += `${type} Iterator`;
1112  }
1113  return [`[${tag}] {`, '}'];
1114}
1115
1116function getBoxedBase(value, ctx, keys, constructor, tag) {
1117  let fn;
1118  let type;
1119  if (isNumberObject(value)) {
1120    fn = NumberPrototypeValueOf;
1121    type = 'Number';
1122  } else if (isStringObject(value)) {
1123    fn = StringPrototypeValueOf;
1124    type = 'String';
1125    // For boxed Strings, we have to remove the 0-n indexed entries,
1126    // since they just noisy up the output and are redundant
1127    // Make boxed primitive Strings look like such
1128    keys.splice(0, value.length);
1129  } else if (isBooleanObject(value)) {
1130    fn = BooleanPrototypeValueOf;
1131    type = 'Boolean';
1132  } else if (isBigIntObject(value)) {
1133    fn = BigIntPrototypeValueOf;
1134    type = 'BigInt';
1135  } else {
1136    fn = SymbolPrototypeValueOf;
1137    type = 'Symbol';
1138  }
1139  let base = `[${type}`;
1140  if (type !== constructor) {
1141    if (constructor === null) {
1142      base += ' (null prototype)';
1143    } else {
1144      base += ` (${constructor})`;
1145    }
1146  }
1147  base += `: ${formatPrimitive(stylizeNoColor, fn(value), ctx)}]`;
1148  if (tag !== '' && tag !== constructor) {
1149    base += ` [${tag}]`;
1150  }
1151  if (keys.length !== 0 || ctx.stylize === stylizeNoColor)
1152    return base;
1153  return ctx.stylize(base, StringPrototypeToLowerCase(type));
1154}
1155
1156function getClassBase(value, constructor, tag) {
1157  const hasName = ObjectPrototypeHasOwnProperty(value, 'name');
1158  const name = (hasName && value.name) || '(anonymous)';
1159  let base = `class ${name}`;
1160  if (constructor !== 'Function' && constructor !== null) {
1161    base += ` [${constructor}]`;
1162  }
1163  if (tag !== '' && constructor !== tag) {
1164    base += ` [${tag}]`;
1165  }
1166  if (constructor !== null) {
1167    const superName = ObjectGetPrototypeOf(value).name;
1168    if (superName) {
1169      base += ` extends ${superName}`;
1170    }
1171  } else {
1172    base += ' extends [null prototype]';
1173  }
1174  return `[${base}]`;
1175}
1176
1177function getFunctionBase(value, constructor, tag) {
1178  const stringified = FunctionPrototypeToString(value);
1179  if (StringPrototypeStartsWith(stringified, 'class') && StringPrototypeEndsWith(stringified, '}')) {
1180    const slice = StringPrototypeSlice(stringified, 5, -1);
1181    const bracketIndex = StringPrototypeIndexOf(slice, '{');
1182    if (bracketIndex !== -1 &&
1183        (!StringPrototypeIncludes(StringPrototypeSlice(slice, 0, bracketIndex), '(') ||
1184        // Slow path to guarantee that it's indeed a class.
1185        RegExpPrototypeExec(classRegExp, RegExpPrototypeSymbolReplace(stripCommentsRegExp, slice)) !== null)
1186    ) {
1187      return getClassBase(value, constructor, tag);
1188    }
1189  }
1190  let type = 'Function';
1191  if (isGeneratorFunction(value)) {
1192    type = `Generator${type}`;
1193  }
1194  if (isAsyncFunction(value)) {
1195    type = `Async${type}`;
1196  }
1197  let base = `[${type}`;
1198  if (constructor === null) {
1199    base += ' (null prototype)';
1200  }
1201  if (value.name === '') {
1202    base += ' (anonymous)';
1203  } else {
1204    base += `: ${value.name}`;
1205  }
1206  base += ']';
1207  if (constructor !== type && constructor !== null) {
1208    base += ` ${constructor}`;
1209  }
1210  if (tag !== '' && constructor !== tag) {
1211    base += ` [${tag}]`;
1212  }
1213  return base;
1214}
1215
1216function identicalSequenceRange(a, b) {
1217  for (let i = 0; i < a.length - 3; i++) {
1218    // Find the first entry of b that matches the current entry of a.
1219    const pos = ArrayPrototypeIndexOf(b, a[i]);
1220    if (pos !== -1) {
1221      const rest = b.length - pos;
1222      if (rest > 3) {
1223        let len = 1;
1224        const maxLen = MathMin(a.length - i, rest);
1225        // Count the number of consecutive entries.
1226        while (maxLen > len && a[i + len] === b[pos + len]) {
1227          len++;
1228        }
1229        if (len > 3) {
1230          return { len, offset: i };
1231        }
1232      }
1233    }
1234  }
1235
1236  return { len: 0, offset: 0 };
1237}
1238
1239function getStackString(error) {
1240  return error.stack ? String(error.stack) : ErrorPrototypeToString(error);
1241}
1242
1243function getStackFrames(ctx, err, stack) {
1244  const frames = StringPrototypeSplit(stack, '\n');
1245
1246  let cause;
1247  try {
1248    ({ cause } = err);
1249  } catch {
1250    // If 'cause' is a getter that throws, ignore it.
1251  }
1252
1253  // Remove stack frames identical to frames in cause.
1254  if (cause != null && isError(cause)) {
1255    const causeStack = getStackString(cause);
1256    const causeStackStart = StringPrototypeIndexOf(causeStack, '\n    at');
1257    if (causeStackStart !== -1) {
1258      const causeFrames = StringPrototypeSplit(StringPrototypeSlice(causeStack, causeStackStart + 1), '\n');
1259      const { len, offset } = identicalSequenceRange(frames, causeFrames);
1260      if (len > 0) {
1261        const skipped = len - 2;
1262        const msg = `    ... ${skipped} lines matching cause stack trace ...`;
1263        frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined'));
1264      }
1265    }
1266  }
1267  return frames;
1268}
1269
1270function improveStack(stack, constructor, name, tag) {
1271  // A stack trace may contain arbitrary data. Only manipulate the output
1272  // for "regular errors" (errors that "look normal") for now.
1273  let len = name.length;
1274
1275  if (constructor === null ||
1276      (StringPrototypeEndsWith(name, 'Error') &&
1277      StringPrototypeStartsWith(stack, name) &&
1278      (stack.length === len || stack[len] === ':' || stack[len] === '\n'))) {
1279    let fallback = 'Error';
1280    if (constructor === null) {
1281      const start = RegExpPrototypeExec(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/, stack) ||
1282      RegExpPrototypeExec(/^([a-z_A-Z0-9-]*Error)$/, stack);
1283      fallback = (start && start[1]) || '';
1284      len = fallback.length;
1285      fallback = fallback || 'Error';
1286    }
1287    const prefix = StringPrototypeSlice(getPrefix(constructor, tag, fallback), 0, -1);
1288    if (name !== prefix) {
1289      if (StringPrototypeIncludes(prefix, name)) {
1290        if (len === 0) {
1291          stack = `${prefix}: ${stack}`;
1292        } else {
1293          stack = `${prefix}${StringPrototypeSlice(stack, len)}`;
1294        }
1295      } else {
1296        stack = `${prefix} [${name}]${StringPrototypeSlice(stack, len)}`;
1297      }
1298    }
1299  }
1300  return stack;
1301}
1302
1303function removeDuplicateErrorKeys(ctx, keys, err, stack) {
1304  if (!ctx.showHidden && keys.length !== 0) {
1305    for (const name of ['name', 'message', 'stack']) {
1306      const index = ArrayPrototypeIndexOf(keys, name);
1307      // Only hide the property in case it's part of the original stack
1308      if (index !== -1 && StringPrototypeIncludes(stack, err[name])) {
1309        ArrayPrototypeSplice(keys, index, 1);
1310      }
1311    }
1312  }
1313}
1314
1315function markNodeModules(ctx, line) {
1316  let tempLine = '';
1317  let nodeModule;
1318  let pos = 0;
1319  while ((nodeModule = nodeModulesRegExp.exec(line)) !== null) {
1320    // '/node_modules/'.length === 14
1321    tempLine += StringPrototypeSlice(line, pos, nodeModule.index + 14);
1322    tempLine += ctx.stylize(nodeModule[1], 'module');
1323    pos = nodeModule.index + nodeModule[0].length;
1324  }
1325  if (pos !== 0) {
1326    line = tempLine + StringPrototypeSlice(line, pos);
1327  }
1328  return line;
1329}
1330
1331function markCwd(ctx, line, workingDirectory) {
1332  let cwdStartPos = StringPrototypeIndexOf(line, workingDirectory);
1333  let tempLine = '';
1334  let cwdLength = workingDirectory.length;
1335  if (cwdStartPos !== -1) {
1336    if (StringPrototypeSlice(line, cwdStartPos - 7, cwdStartPos) === 'file://') {
1337      cwdLength += 7;
1338      cwdStartPos -= 7;
1339    }
1340    const start = line[cwdStartPos - 1] === '(' ? cwdStartPos - 1 : cwdStartPos;
1341    const end = start !== cwdStartPos && StringPrototypeEndsWith(line, ')') ? -1 : line.length;
1342    const workingDirectoryEndPos = cwdStartPos + cwdLength + 1;
1343    const cwdSlice = StringPrototypeSlice(line, start, workingDirectoryEndPos);
1344
1345    tempLine += StringPrototypeSlice(line, 0, start);
1346    tempLine += ctx.stylize(cwdSlice, 'undefined');
1347    tempLine += StringPrototypeSlice(line, workingDirectoryEndPos, end);
1348    if (end === -1) {
1349      tempLine += ctx.stylize(')', 'undefined');
1350    }
1351  } else {
1352    tempLine += line;
1353  }
1354  return tempLine;
1355}
1356
1357function safeGetCWD() {
1358  let workingDirectory;
1359  try {
1360    workingDirectory = process.cwd();
1361  } catch {
1362    return;
1363  }
1364  return workingDirectory;
1365}
1366
1367function formatError(err, constructor, tag, ctx, keys) {
1368  const name = err.name != null ? String(err.name) : 'Error';
1369  let stack = getStackString(err);
1370
1371  removeDuplicateErrorKeys(ctx, keys, err, stack);
1372
1373  if ('cause' in err &&
1374      (keys.length === 0 || !ArrayPrototypeIncludes(keys, 'cause'))) {
1375    ArrayPrototypePush(keys, 'cause');
1376  }
1377
1378  // Print errors aggregated into AggregateError
1379  if (ArrayIsArray(err.errors) &&
1380      (keys.length === 0 || !ArrayPrototypeIncludes(keys, 'errors'))) {
1381    ArrayPrototypePush(keys, 'errors');
1382  }
1383
1384  stack = improveStack(stack, constructor, name, tag);
1385
1386  // Ignore the error message if it's contained in the stack.
1387  let pos = (err.message && StringPrototypeIndexOf(stack, err.message)) || -1;
1388  if (pos !== -1)
1389    pos += err.message.length;
1390  // Wrap the error in brackets in case it has no stack trace.
1391  const stackStart = StringPrototypeIndexOf(stack, '\n    at', pos);
1392  if (stackStart === -1) {
1393    stack = `[${stack}]`;
1394  } else {
1395    let newStack = StringPrototypeSlice(stack, 0, stackStart);
1396    const stackFramePart = StringPrototypeSlice(stack, stackStart + 1);
1397    const lines = getStackFrames(ctx, err, stackFramePart);
1398    if (ctx.colors) {
1399      // Highlight userland code and node modules.
1400      const workingDirectory = safeGetCWD();
1401      let esmWorkingDirectory;
1402      for (let line of lines) {
1403        const core = RegExpPrototypeExec(coreModuleRegExp, line);
1404        if (core !== null && BuiltinModule.exists(core[1])) {
1405          newStack += `\n${ctx.stylize(line, 'undefined')}`;
1406        } else {
1407          newStack += '\n';
1408
1409          line = markNodeModules(ctx, line);
1410          if (workingDirectory !== undefined) {
1411            let newLine = markCwd(ctx, line, workingDirectory);
1412            if (newLine === line) {
1413              esmWorkingDirectory ??= pathToFileUrlHref(workingDirectory);
1414              newLine = markCwd(ctx, line, esmWorkingDirectory);
1415            }
1416            line = newLine;
1417          }
1418
1419          newStack += line;
1420        }
1421      }
1422    } else {
1423      newStack += `\n${ArrayPrototypeJoin(lines, '\n')}`;
1424    }
1425    stack = newStack;
1426  }
1427  // The message and the stack have to be indented as well!
1428  if (ctx.indentationLvl !== 0) {
1429    const indentation = StringPrototypeRepeat(' ', ctx.indentationLvl);
1430    stack = StringPrototypeReplaceAll(stack, '\n', `\n${indentation}`);
1431  }
1432  return stack;
1433}
1434
1435function groupArrayElements(ctx, output, value) {
1436  let totalLength = 0;
1437  let maxLength = 0;
1438  let i = 0;
1439  let outputLength = output.length;
1440  if (ctx.maxArrayLength < output.length) {
1441    // This makes sure the "... n more items" part is not taken into account.
1442    outputLength--;
1443  }
1444  const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
1445  const dataLen = new Array(outputLength);
1446  // Calculate the total length of all output entries and the individual max
1447  // entries length of all output entries. We have to remove colors first,
1448  // otherwise the length would not be calculated properly.
1449  for (; i < outputLength; i++) {
1450    const len = getStringWidth(output[i], ctx.colors);
1451    dataLen[i] = len;
1452    totalLength += len + separatorSpace;
1453    if (maxLength < len)
1454      maxLength = len;
1455  }
1456  // Add two to `maxLength` as we add a single whitespace character plus a comma
1457  // in-between two entries.
1458  const actualMax = maxLength + separatorSpace;
1459  // Check if at least three entries fit next to each other and prevent grouping
1460  // of arrays that contains entries of very different length (i.e., if a single
1461  // entry is longer than 1/5 of all other entries combined). Otherwise the
1462  // space in-between small entries would be enormous.
1463  if (actualMax * 3 + ctx.indentationLvl < ctx.breakLength &&
1464      (totalLength / actualMax > 5 || maxLength <= 6)) {
1465
1466    const approxCharHeights = 2.5;
1467    const averageBias = MathSqrt(actualMax - totalLength / output.length);
1468    const biasedMax = MathMax(actualMax - 3 - averageBias, 1);
1469    // Dynamically check how many columns seem possible.
1470    const columns = MathMin(
1471      // Ideally a square should be drawn. We expect a character to be about 2.5
1472      // times as high as wide. This is the area formula to calculate a square
1473      // which contains n rectangles of size `actualMax * approxCharHeights`.
1474      // Divide that by `actualMax` to receive the correct number of columns.
1475      // The added bias increases the columns for short entries.
1476      MathRound(
1477        MathSqrt(
1478          approxCharHeights * biasedMax * outputLength,
1479        ) / biasedMax,
1480      ),
1481      // Do not exceed the breakLength.
1482      MathFloor((ctx.breakLength - ctx.indentationLvl) / actualMax),
1483      // Limit array grouping for small `compact` modes as the user requested
1484      // minimal grouping.
1485      ctx.compact * 4,
1486      // Limit the columns to a maximum of fifteen.
1487      15,
1488    );
1489    // Return with the original output if no grouping should happen.
1490    if (columns <= 1) {
1491      return output;
1492    }
1493    const tmp = [];
1494    const maxLineLength = [];
1495    for (let i = 0; i < columns; i++) {
1496      let lineMaxLength = 0;
1497      for (let j = i; j < output.length; j += columns) {
1498        if (dataLen[j] > lineMaxLength)
1499          lineMaxLength = dataLen[j];
1500      }
1501      lineMaxLength += separatorSpace;
1502      maxLineLength[i] = lineMaxLength;
1503    }
1504    let order = StringPrototypePadStart;
1505    if (value !== undefined) {
1506      for (let i = 0; i < output.length; i++) {
1507        if (typeof value[i] !== 'number' && typeof value[i] !== 'bigint') {
1508          order = StringPrototypePadEnd;
1509          break;
1510        }
1511      }
1512    }
1513    // Each iteration creates a single line of grouped entries.
1514    for (let i = 0; i < outputLength; i += columns) {
1515      // The last lines may contain less entries than columns.
1516      const max = MathMin(i + columns, outputLength);
1517      let str = '';
1518      let j = i;
1519      for (; j < max - 1; j++) {
1520        // Calculate extra color padding in case it's active. This has to be
1521        // done line by line as some lines might contain more colors than
1522        // others.
1523        const padding = maxLineLength[j - i] + output[j].length - dataLen[j];
1524        str += order(`${output[j]}, `, padding, ' ');
1525      }
1526      if (order === StringPrototypePadStart) {
1527        const padding = maxLineLength[j - i] +
1528                        output[j].length -
1529                        dataLen[j] -
1530                        separatorSpace;
1531        str += StringPrototypePadStart(output[j], padding, ' ');
1532      } else {
1533        str += output[j];
1534      }
1535      ArrayPrototypePush(tmp, str);
1536    }
1537    if (ctx.maxArrayLength < output.length) {
1538      ArrayPrototypePush(tmp, output[outputLength]);
1539    }
1540    output = tmp;
1541  }
1542  return output;
1543}
1544
1545function handleMaxCallStackSize(ctx, err, constructorName, indentationLvl) {
1546  if (isStackOverflowError(err)) {
1547    ArrayPrototypePop(ctx.seen);
1548    ctx.indentationLvl = indentationLvl;
1549    return ctx.stylize(
1550      `[${constructorName}: Inspection interrupted ` +
1551        'prematurely. Maximum call stack size exceeded.]',
1552      'special',
1553    );
1554  }
1555  /* c8 ignore next */
1556  assert.fail(err.stack);
1557}
1558
1559function addNumericSeparator(integerString) {
1560  let result = '';
1561  let i = integerString.length;
1562  const start = StringPrototypeStartsWith(integerString, '-') ? 1 : 0;
1563  for (; i >= start + 4; i -= 3) {
1564    result = `_${StringPrototypeSlice(integerString, i - 3, i)}${result}`;
1565  }
1566  return i === integerString.length ?
1567    integerString :
1568    `${StringPrototypeSlice(integerString, 0, i)}${result}`;
1569}
1570
1571function addNumericSeparatorEnd(integerString) {
1572  let result = '';
1573  let i = 0;
1574  for (; i < integerString.length - 3; i += 3) {
1575    result += `${StringPrototypeSlice(integerString, i, i + 3)}_`;
1576  }
1577  return i === 0 ?
1578    integerString :
1579    `${result}${StringPrototypeSlice(integerString, i)}`;
1580}
1581
1582function formatNumber(fn, number, numericSeparator) {
1583  if (!numericSeparator) {
1584    // Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.
1585    if (ObjectIs(number, -0)) {
1586      return fn('-0', 'number');
1587    }
1588    return fn(`${number}`, 'number');
1589  }
1590  const integer = MathTrunc(number);
1591  const string = String(integer);
1592  if (integer === number) {
1593    if (!NumberIsFinite(number) || StringPrototypeIncludes(string, 'e')) {
1594      return fn(string, 'number');
1595    }
1596    return fn(`${addNumericSeparator(string)}`, 'number');
1597  }
1598  if (NumberIsNaN(number)) {
1599    return fn(string, 'number');
1600  }
1601  return fn(`${
1602    addNumericSeparator(string)
1603  }.${
1604    addNumericSeparatorEnd(
1605      StringPrototypeSlice(String(number), string.length + 1),
1606    )
1607  }`, 'number');
1608}
1609
1610function formatBigInt(fn, bigint, numericSeparator) {
1611  const string = String(bigint);
1612  if (!numericSeparator) {
1613    return fn(`${string}n`, 'bigint');
1614  }
1615  return fn(`${addNumericSeparator(string)}n`, 'bigint');
1616}
1617
1618function formatPrimitive(fn, value, ctx) {
1619  if (typeof value === 'string') {
1620    let trailer = '';
1621    if (value.length > ctx.maxStringLength) {
1622      const remaining = value.length - ctx.maxStringLength;
1623      value = StringPrototypeSlice(value, 0, ctx.maxStringLength);
1624      trailer = `... ${remaining} more character${remaining > 1 ? 's' : ''}`;
1625    }
1626    if (ctx.compact !== true &&
1627        // We do not support handling unicode characters width with
1628        // the readline getStringWidth function as there are
1629        // performance implications.
1630        value.length > kMinLineLength &&
1631        value.length > ctx.breakLength - ctx.indentationLvl - 4) {
1632      return ArrayPrototypeJoin(
1633        ArrayPrototypeMap(
1634          RegExpPrototypeSymbolSplit(/(?<=\n)/, value),
1635          (line) => fn(strEscape(line), 'string'),
1636        ),
1637        ` +\n${StringPrototypeRepeat(' ', ctx.indentationLvl + 2)}`,
1638      ) + trailer;
1639    }
1640    return fn(strEscape(value), 'string') + trailer;
1641  }
1642  if (typeof value === 'number')
1643    return formatNumber(fn, value, ctx.numericSeparator);
1644  if (typeof value === 'bigint')
1645    return formatBigInt(fn, value, ctx.numericSeparator);
1646  if (typeof value === 'boolean')
1647    return fn(`${value}`, 'boolean');
1648  if (typeof value === 'undefined')
1649    return fn('undefined', 'undefined');
1650  // es6 symbol primitive
1651  return fn(SymbolPrototypeToString(value), 'symbol');
1652}
1653
1654function formatNamespaceObject(keys, ctx, value, recurseTimes) {
1655  const output = new Array(keys.length);
1656  for (let i = 0; i < keys.length; i++) {
1657    try {
1658      output[i] = formatProperty(ctx, value, recurseTimes, keys[i],
1659                                 kObjectType);
1660    } catch (err) {
1661      assert(isNativeError(err) && err.name === 'ReferenceError');
1662      // Use the existing functionality. This makes sure the indentation and
1663      // line breaks are always correct. Otherwise it is very difficult to keep
1664      // this aligned, even though this is a hacky way of dealing with this.
1665      const tmp = { [keys[i]]: '' };
1666      output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType);
1667      const pos = StringPrototypeLastIndexOf(output[i], ' ');
1668      // We have to find the last whitespace and have to replace that value as
1669      // it will be visualized as a regular string.
1670      output[i] = StringPrototypeSlice(output[i], 0, pos + 1) +
1671                  ctx.stylize('<uninitialized>', 'special');
1672    }
1673  }
1674  // Reset the keys to an empty array. This prevents duplicated inspection.
1675  keys.length = 0;
1676  return output;
1677}
1678
1679// The array is sparse and/or has extra keys
1680function formatSpecialArray(ctx, value, recurseTimes, maxLength, output, i) {
1681  const keys = ObjectKeys(value);
1682  let index = i;
1683  for (; i < keys.length && output.length < maxLength; i++) {
1684    const key = keys[i];
1685    const tmp = +key;
1686    // Arrays can only have up to 2^32 - 1 entries
1687    if (tmp > 2 ** 32 - 2) {
1688      break;
1689    }
1690    if (`${index}` !== key) {
1691      if (RegExpPrototypeExec(numberRegExp, key) === null) {
1692        break;
1693      }
1694      const emptyItems = tmp - index;
1695      const ending = emptyItems > 1 ? 's' : '';
1696      const message = `<${emptyItems} empty item${ending}>`;
1697      ArrayPrototypePush(output, ctx.stylize(message, 'undefined'));
1698      index = tmp;
1699      if (output.length === maxLength) {
1700        break;
1701      }
1702    }
1703    ArrayPrototypePush(output, formatProperty(ctx, value, recurseTimes, key, kArrayType));
1704    index++;
1705  }
1706  const remaining = value.length - index;
1707  if (output.length !== maxLength) {
1708    if (remaining > 0) {
1709      const ending = remaining > 1 ? 's' : '';
1710      const message = `<${remaining} empty item${ending}>`;
1711      ArrayPrototypePush(output, ctx.stylize(message, 'undefined'));
1712    }
1713  } else if (remaining > 0) {
1714    ArrayPrototypePush(output, `... ${remaining} more item${remaining > 1 ? 's' : ''}`);
1715  }
1716  return output;
1717}
1718
1719function formatArrayBuffer(ctx, value) {
1720  let buffer;
1721  try {
1722    buffer = new Uint8Array(value);
1723  } catch {
1724    return [ctx.stylize('(detached)', 'special')];
1725  }
1726  if (hexSlice === undefined)
1727    hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice);
1728  let str = StringPrototypeTrim(RegExpPrototypeSymbolReplace(
1729    /(.{2})/g,
1730    hexSlice(buffer, 0, MathMin(ctx.maxArrayLength, buffer.length)),
1731    '$1 ',
1732  ));
1733  const remaining = buffer.length - ctx.maxArrayLength;
1734  if (remaining > 0)
1735    str += ` ... ${remaining} more byte${remaining > 1 ? 's' : ''}`;
1736  return [`${ctx.stylize('[Uint8Contents]', 'special')}: <${str}>`];
1737}
1738
1739function formatArray(ctx, value, recurseTimes) {
1740  const valLen = value.length;
1741  const len = MathMin(MathMax(0, ctx.maxArrayLength), valLen);
1742
1743  const remaining = valLen - len;
1744  const output = [];
1745  for (let i = 0; i < len; i++) {
1746    // Special handle sparse arrays.
1747    if (!ObjectPrototypeHasOwnProperty(value, i)) {
1748      return formatSpecialArray(ctx, value, recurseTimes, len, output, i);
1749    }
1750    ArrayPrototypePush(output, formatProperty(ctx, value, recurseTimes, i, kArrayType));
1751  }
1752  if (remaining > 0) {
1753    ArrayPrototypePush(output, `... ${remaining} more item${remaining > 1 ? 's' : ''}`);
1754  }
1755  return output;
1756}
1757
1758function formatTypedArray(value, length, ctx, ignored, recurseTimes) {
1759  const maxLength = MathMin(MathMax(0, ctx.maxArrayLength), length);
1760  const remaining = value.length - maxLength;
1761  const output = new Array(maxLength);
1762  const elementFormatter = value.length > 0 && typeof value[0] === 'number' ?
1763    formatNumber :
1764    formatBigInt;
1765  for (let i = 0; i < maxLength; ++i) {
1766    output[i] = elementFormatter(ctx.stylize, value[i], ctx.numericSeparator);
1767  }
1768  if (remaining > 0) {
1769    output[maxLength] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
1770  }
1771  if (ctx.showHidden) {
1772    // .buffer goes last, it's not a primitive like the others.
1773    // All besides `BYTES_PER_ELEMENT` are actually getters.
1774    ctx.indentationLvl += 2;
1775    for (const key of [
1776      'BYTES_PER_ELEMENT',
1777      'length',
1778      'byteLength',
1779      'byteOffset',
1780      'buffer',
1781    ]) {
1782      const str = formatValue(ctx, value[key], recurseTimes, true);
1783      ArrayPrototypePush(output, `[${key}]: ${str}`);
1784    }
1785    ctx.indentationLvl -= 2;
1786  }
1787  return output;
1788}
1789
1790function formatSet(value, ctx, ignored, recurseTimes) {
1791  const output = [];
1792  ctx.indentationLvl += 2;
1793  for (const v of value) {
1794    ArrayPrototypePush(output, formatValue(ctx, v, recurseTimes));
1795  }
1796  ctx.indentationLvl -= 2;
1797  return output;
1798}
1799
1800function formatMap(value, ctx, ignored, recurseTimes) {
1801  const output = [];
1802  ctx.indentationLvl += 2;
1803  for (const { 0: k, 1: v } of value) {
1804    ArrayPrototypePush(
1805      output,
1806      `${formatValue(ctx, k, recurseTimes)} => ${formatValue(ctx, v, recurseTimes)}`,
1807    );
1808  }
1809  ctx.indentationLvl -= 2;
1810  return output;
1811}
1812
1813function formatSetIterInner(ctx, recurseTimes, entries, state) {
1814  const maxArrayLength = MathMax(ctx.maxArrayLength, 0);
1815  const maxLength = MathMin(maxArrayLength, entries.length);
1816  const output = new Array(maxLength);
1817  ctx.indentationLvl += 2;
1818  for (let i = 0; i < maxLength; i++) {
1819    output[i] = formatValue(ctx, entries[i], recurseTimes);
1820  }
1821  ctx.indentationLvl -= 2;
1822  if (state === kWeak && !ctx.sorted) {
1823    // Sort all entries to have a halfway reliable output (if more entries than
1824    // retrieved ones exist, we can not reliably return the same output) if the
1825    // output is not sorted anyway.
1826    ArrayPrototypeSort(output);
1827  }
1828  const remaining = entries.length - maxLength;
1829  if (remaining > 0) {
1830    ArrayPrototypePush(output,
1831                       `... ${remaining} more item${remaining > 1 ? 's' : ''}`);
1832  }
1833  return output;
1834}
1835
1836function formatMapIterInner(ctx, recurseTimes, entries, state) {
1837  const maxArrayLength = MathMax(ctx.maxArrayLength, 0);
1838  // Entries exist as [key1, val1, key2, val2, ...]
1839  const len = entries.length / 2;
1840  const remaining = len - maxArrayLength;
1841  const maxLength = MathMin(maxArrayLength, len);
1842  const output = new Array(maxLength);
1843  let i = 0;
1844  ctx.indentationLvl += 2;
1845  if (state === kWeak) {
1846    for (; i < maxLength; i++) {
1847      const pos = i * 2;
1848      output[i] =
1849        `${formatValue(ctx, entries[pos], recurseTimes)} => ${formatValue(ctx, entries[pos + 1], recurseTimes)}`;
1850    }
1851    // Sort all entries to have a halfway reliable output (if more entries than
1852    // retrieved ones exist, we can not reliably return the same output) if the
1853    // output is not sorted anyway.
1854    if (!ctx.sorted)
1855      ArrayPrototypeSort(output);
1856  } else {
1857    for (; i < maxLength; i++) {
1858      const pos = i * 2;
1859      const res = [
1860        formatValue(ctx, entries[pos], recurseTimes),
1861        formatValue(ctx, entries[pos + 1], recurseTimes),
1862      ];
1863      output[i] = reduceToSingleString(
1864        ctx, res, '', ['[', ']'], kArrayExtrasType, recurseTimes);
1865    }
1866  }
1867  ctx.indentationLvl -= 2;
1868  if (remaining > 0) {
1869    ArrayPrototypePush(output, `... ${remaining} more item${remaining > 1 ? 's' : ''}`);
1870  }
1871  return output;
1872}
1873
1874function formatWeakCollection(ctx) {
1875  return [ctx.stylize('<items unknown>', 'special')];
1876}
1877
1878function formatWeakSet(ctx, value, recurseTimes) {
1879  const entries = previewEntries(value);
1880  return formatSetIterInner(ctx, recurseTimes, entries, kWeak);
1881}
1882
1883function formatWeakMap(ctx, value, recurseTimes) {
1884  const entries = previewEntries(value);
1885  return formatMapIterInner(ctx, recurseTimes, entries, kWeak);
1886}
1887
1888function formatIterator(braces, ctx, value, recurseTimes) {
1889  const { 0: entries, 1: isKeyValue } = previewEntries(value, true);
1890  if (isKeyValue) {
1891    // Mark entry iterators as such.
1892    braces[0] = RegExpPrototypeSymbolReplace(/ Iterator] {$/, braces[0], ' Entries] {');
1893    return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries);
1894  }
1895
1896  return formatSetIterInner(ctx, recurseTimes, entries, kIterator);
1897}
1898
1899function formatPromise(ctx, value, recurseTimes) {
1900  let output;
1901  const { 0: state, 1: result } = getPromiseDetails(value);
1902  if (state === kPending) {
1903    output = [ctx.stylize('<pending>', 'special')];
1904  } else {
1905    ctx.indentationLvl += 2;
1906    const str = formatValue(ctx, result, recurseTimes);
1907    ctx.indentationLvl -= 2;
1908    output = [
1909      state === kRejected ?
1910        `${ctx.stylize('<rejected>', 'special')} ${str}` :
1911        str,
1912    ];
1913  }
1914  return output;
1915}
1916
1917function formatProperty(ctx, value, recurseTimes, key, type, desc,
1918                        original = value) {
1919  let name, str;
1920  let extra = ' ';
1921  desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
1922    { value: value[key], enumerable: true };
1923  if (desc.value !== undefined) {
1924    const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3;
1925    ctx.indentationLvl += diff;
1926    str = formatValue(ctx, desc.value, recurseTimes);
1927    if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) {
1928      extra = `\n${StringPrototypeRepeat(' ', ctx.indentationLvl)}`;
1929    }
1930    ctx.indentationLvl -= diff;
1931  } else if (desc.get !== undefined) {
1932    const label = desc.set !== undefined ? 'Getter/Setter' : 'Getter';
1933    const s = ctx.stylize;
1934    const sp = 'special';
1935    if (ctx.getters && (ctx.getters === true ||
1936          (ctx.getters === 'get' && desc.set === undefined) ||
1937          (ctx.getters === 'set' && desc.set !== undefined))) {
1938      try {
1939        const tmp = FunctionPrototypeCall(desc.get, original);
1940        ctx.indentationLvl += 2;
1941        if (tmp === null) {
1942          str = `${s(`[${label}:`, sp)} ${s('null', 'null')}${s(']', sp)}`;
1943        } else if (typeof tmp === 'object') {
1944          str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`;
1945        } else {
1946          const primitive = formatPrimitive(s, tmp, ctx);
1947          str = `${s(`[${label}:`, sp)} ${primitive}${s(']', sp)}`;
1948        }
1949        ctx.indentationLvl -= 2;
1950      } catch (err) {
1951        const message = `<Inspection threw (${err.message})>`;
1952        str = `${s(`[${label}:`, sp)} ${message}${s(']', sp)}`;
1953      }
1954    } else {
1955      str = ctx.stylize(`[${label}]`, sp);
1956    }
1957  } else if (desc.set !== undefined) {
1958    str = ctx.stylize('[Setter]', 'special');
1959  } else {
1960    str = ctx.stylize('undefined', 'undefined');
1961  }
1962  if (type === kArrayType) {
1963    return str;
1964  }
1965  if (typeof key === 'symbol') {
1966    const tmp = RegExpPrototypeSymbolReplace(
1967      strEscapeSequencesReplacer,
1968      SymbolPrototypeToString(key),
1969      escapeFn,
1970    );
1971    name = `[${ctx.stylize(tmp, 'symbol')}]`;
1972  } else if (key === '__proto__') {
1973    name = "['__proto__']";
1974  } else if (desc.enumerable === false) {
1975    const tmp = RegExpPrototypeSymbolReplace(
1976      strEscapeSequencesReplacer,
1977      key,
1978      escapeFn,
1979    );
1980    name = `[${tmp}]`;
1981  } else if (RegExpPrototypeExec(keyStrRegExp, key) !== null) {
1982    name = ctx.stylize(key, 'name');
1983  } else {
1984    name = ctx.stylize(strEscape(key), 'string');
1985  }
1986  return `${name}:${extra}${str}`;
1987}
1988
1989function isBelowBreakLength(ctx, output, start, base) {
1990  // Each entry is separated by at least a comma. Thus, we start with a total
1991  // length of at least `output.length`. In addition, some cases have a
1992  // whitespace in-between each other that is added to the total as well.
1993  // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth
1994  // function. Check the performance overhead and make it an opt-in in case it's
1995  // significant.
1996  let totalLength = output.length + start;
1997  if (totalLength + output.length > ctx.breakLength)
1998    return false;
1999  for (let i = 0; i < output.length; i++) {
2000    if (ctx.colors) {
2001      totalLength += removeColors(output[i]).length;
2002    } else {
2003      totalLength += output[i].length;
2004    }
2005    if (totalLength > ctx.breakLength) {
2006      return false;
2007    }
2008  }
2009  // Do not line up properties on the same line if `base` contains line breaks.
2010  return base === '' || !StringPrototypeIncludes(base, '\n');
2011}
2012
2013function reduceToSingleString(
2014  ctx, output, base, braces, extrasType, recurseTimes, value) {
2015  if (ctx.compact !== true) {
2016    if (typeof ctx.compact === 'number' && ctx.compact >= 1) {
2017      // Memorize the original output length. In case the output is grouped,
2018      // prevent lining up the entries on a single line.
2019      const entries = output.length;
2020      // Group array elements together if the array contains at least six
2021      // separate entries.
2022      if (extrasType === kArrayExtrasType && entries > 6) {
2023        output = groupArrayElements(ctx, output, value);
2024      }
2025      // `ctx.currentDepth` is set to the most inner depth of the currently
2026      // inspected object part while `recurseTimes` is the actual current depth
2027      // that is inspected.
2028      //
2029      // Example:
2030      //
2031      // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } }
2032      //
2033      // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max
2034      // depth of 1.
2035      //
2036      // Consolidate all entries of the local most inner depth up to
2037      // `ctx.compact`, as long as the properties are smaller than
2038      // `ctx.breakLength`.
2039      if (ctx.currentDepth - recurseTimes < ctx.compact &&
2040          entries === output.length) {
2041        // Line up all entries on a single line in case the entries do not
2042        // exceed `breakLength`. Add 10 as constant to start next to all other
2043        // factors that may reduce `breakLength`.
2044        const start = output.length + ctx.indentationLvl +
2045                      braces[0].length + base.length + 10;
2046        if (isBelowBreakLength(ctx, output, start, base)) {
2047          const joinedOutput = join(output, ', ');
2048          if (!StringPrototypeIncludes(joinedOutput, '\n')) {
2049            return `${base ? `${base} ` : ''}${braces[0]} ${joinedOutput}` +
2050              ` ${braces[1]}`;
2051          }
2052        }
2053      }
2054    }
2055    // Line up each entry on an individual line.
2056    const indentation = `\n${StringPrototypeRepeat(' ', ctx.indentationLvl)}`;
2057    return `${base ? `${base} ` : ''}${braces[0]}${indentation}  ` +
2058      `${join(output, `,${indentation}  `)}${indentation}${braces[1]}`;
2059  }
2060  // Line up all entries on a single line in case the entries do not exceed
2061  // `breakLength`.
2062  if (isBelowBreakLength(ctx, output, 0, base)) {
2063    return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` +
2064      braces[1];
2065  }
2066  const indentation = StringPrototypeRepeat(' ', ctx.indentationLvl);
2067  // If the opening "brace" is too large, like in the case of "Set {",
2068  // we need to force the first item to be on the next line or the
2069  // items will not line up correctly.
2070  const ln = base === '' && braces[0].length === 1 ?
2071    ' ' : `${base ? ` ${base}` : ''}\n${indentation}  `;
2072  // Line up each entry on an individual line.
2073  return `${braces[0]}${ln}${join(output, `,\n${indentation}  `)} ${braces[1]}`;
2074}
2075
2076function hasBuiltInToString(value) {
2077  // Prevent triggering proxy traps.
2078  const getFullProxy = false;
2079  const proxyTarget = getProxyDetails(value, getFullProxy);
2080  if (proxyTarget !== undefined) {
2081    if (proxyTarget === null) {
2082      return true;
2083    }
2084    value = proxyTarget;
2085  }
2086
2087  // Count objects that have no `toString` function as built-in.
2088  if (typeof value.toString !== 'function') {
2089    return true;
2090  }
2091
2092  // The object has a own `toString` property. Thus it's not not a built-in one.
2093  if (ObjectPrototypeHasOwnProperty(value, 'toString')) {
2094    return false;
2095  }
2096
2097  // Find the object that has the `toString` property as own property in the
2098  // prototype chain.
2099  let pointer = value;
2100  do {
2101    pointer = ObjectGetPrototypeOf(pointer);
2102  } while (!ObjectPrototypeHasOwnProperty(pointer, 'toString'));
2103
2104  // Check closer if the object is a built-in.
2105  const descriptor = ObjectGetOwnPropertyDescriptor(pointer, 'constructor');
2106  return descriptor !== undefined &&
2107    typeof descriptor.value === 'function' &&
2108    builtInObjects.has(descriptor.value.name);
2109}
2110
2111const firstErrorLine = (error) => StringPrototypeSplit(error.message, '\n', 1)[0];
2112let CIRCULAR_ERROR_MESSAGE;
2113function tryStringify(arg) {
2114  try {
2115    return JSONStringify(arg);
2116  } catch (err) {
2117    // Populate the circular error message lazily
2118    if (!CIRCULAR_ERROR_MESSAGE) {
2119      try {
2120        const a = {};
2121        a.a = a;
2122        JSONStringify(a);
2123      } catch (circularError) {
2124        CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);
2125      }
2126    }
2127    if (err.name === 'TypeError' &&
2128        firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE) {
2129      return '[Circular]';
2130    }
2131    throw err;
2132  }
2133}
2134
2135function format(...args) {
2136  return formatWithOptionsInternal(undefined, args);
2137}
2138
2139function formatWithOptions(inspectOptions, ...args) {
2140  validateObject(inspectOptions, 'inspectOptions', { allowArray: true });
2141  return formatWithOptionsInternal(inspectOptions, args);
2142}
2143
2144function formatNumberNoColor(number, options) {
2145  return formatNumber(
2146    stylizeNoColor,
2147    number,
2148    options?.numericSeparator ?? inspectDefaultOptions.numericSeparator,
2149  );
2150}
2151
2152function formatBigIntNoColor(bigint, options) {
2153  return formatBigInt(
2154    stylizeNoColor,
2155    bigint,
2156    options?.numericSeparator ?? inspectDefaultOptions.numericSeparator,
2157  );
2158}
2159
2160function formatWithOptionsInternal(inspectOptions, args) {
2161  const first = args[0];
2162  let a = 0;
2163  let str = '';
2164  let join = '';
2165
2166  if (typeof first === 'string') {
2167    if (args.length === 1) {
2168      return first;
2169    }
2170    let tempStr;
2171    let lastPos = 0;
2172
2173    for (let i = 0; i < first.length - 1; i++) {
2174      if (StringPrototypeCharCodeAt(first, i) === 37) { // '%'
2175        const nextChar = StringPrototypeCharCodeAt(first, ++i);
2176        if (a + 1 !== args.length) {
2177          switch (nextChar) {
2178            case 115: { // 's'
2179              const tempArg = args[++a];
2180              if (typeof tempArg === 'number') {
2181                tempStr = formatNumberNoColor(tempArg, inspectOptions);
2182              } else if (typeof tempArg === 'bigint') {
2183                tempStr = formatBigIntNoColor(tempArg, inspectOptions);
2184              } else if (typeof tempArg !== 'object' ||
2185                         tempArg === null ||
2186                         !hasBuiltInToString(tempArg)) {
2187                tempStr = String(tempArg);
2188              } else {
2189                tempStr = inspect(tempArg, {
2190                  ...inspectOptions,
2191                  compact: 3,
2192                  colors: false,
2193                  depth: 0,
2194                });
2195              }
2196              break;
2197            }
2198            case 106: // 'j'
2199              tempStr = tryStringify(args[++a]);
2200              break;
2201            case 100: { // 'd'
2202              const tempNum = args[++a];
2203              if (typeof tempNum === 'bigint') {
2204                tempStr = formatBigIntNoColor(tempNum, inspectOptions);
2205              } else if (typeof tempNum === 'symbol') {
2206                tempStr = 'NaN';
2207              } else {
2208                tempStr = formatNumberNoColor(Number(tempNum), inspectOptions);
2209              }
2210              break;
2211            }
2212            case 79: // 'O'
2213              tempStr = inspect(args[++a], inspectOptions);
2214              break;
2215            case 111: // 'o'
2216              tempStr = inspect(args[++a], {
2217                ...inspectOptions,
2218                showHidden: true,
2219                showProxy: true,
2220                depth: 4,
2221              });
2222              break;
2223            case 105: { // 'i'
2224              const tempInteger = args[++a];
2225              if (typeof tempInteger === 'bigint') {
2226                tempStr = formatBigIntNoColor(tempInteger, inspectOptions);
2227              } else if (typeof tempInteger === 'symbol') {
2228                tempStr = 'NaN';
2229              } else {
2230                tempStr = formatNumberNoColor(
2231                  NumberParseInt(tempInteger), inspectOptions);
2232              }
2233              break;
2234            }
2235            case 102: { // 'f'
2236              const tempFloat = args[++a];
2237              if (typeof tempFloat === 'symbol') {
2238                tempStr = 'NaN';
2239              } else {
2240                tempStr = formatNumberNoColor(
2241                  NumberParseFloat(tempFloat), inspectOptions);
2242              }
2243              break;
2244            }
2245            case 99: // 'c'
2246              a += 1;
2247              tempStr = '';
2248              break;
2249            case 37: // '%'
2250              str += StringPrototypeSlice(first, lastPos, i);
2251              lastPos = i + 1;
2252              continue;
2253            default: // Any other character is not a correct placeholder
2254              continue;
2255          }
2256          if (lastPos !== i - 1) {
2257            str += StringPrototypeSlice(first, lastPos, i - 1);
2258          }
2259          str += tempStr;
2260          lastPos = i + 1;
2261        } else if (nextChar === 37) {
2262          str += StringPrototypeSlice(first, lastPos, i);
2263          lastPos = i + 1;
2264        }
2265      }
2266    }
2267    if (lastPos !== 0) {
2268      a++;
2269      join = ' ';
2270      if (lastPos < first.length) {
2271        str += StringPrototypeSlice(first, lastPos);
2272      }
2273    }
2274  }
2275
2276  while (a < args.length) {
2277    const value = args[a];
2278    str += join;
2279    str += typeof value !== 'string' ? inspect(value, inspectOptions) : value;
2280    join = ' ';
2281    a++;
2282  }
2283  return str;
2284}
2285
2286function isZeroWidthCodePoint(code) {
2287  return code <= 0x1F || // C0 control codes
2288    (code >= 0x7F && code <= 0x9F) || // C1 control codes
2289    (code >= 0x300 && code <= 0x36F) || // Combining Diacritical Marks
2290    (code >= 0x200B && code <= 0x200F) || // Modifying Invisible Characters
2291    // Combining Diacritical Marks for Symbols
2292    (code >= 0x20D0 && code <= 0x20FF) ||
2293    (code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors
2294    (code >= 0xFE20 && code <= 0xFE2F) || // Combining Half Marks
2295    (code >= 0xE0100 && code <= 0xE01EF); // Variation Selectors
2296}
2297
2298if (internalBinding('config').hasIntl) {
2299  const icu = internalBinding('icu');
2300  // icu.getStringWidth(string, ambiguousAsFullWidth, expandEmojiSequence)
2301  // Defaults: ambiguousAsFullWidth = false; expandEmojiSequence = true;
2302  // TODO(BridgeAR): Expose the options to the user. That is probably the
2303  // best thing possible at the moment, since it's difficult to know what
2304  // the receiving end supports.
2305  getStringWidth = function getStringWidth(str, removeControlChars = true) {
2306    let width = 0;
2307
2308    if (removeControlChars) {
2309      str = stripVTControlCharacters(str);
2310    }
2311    for (let i = 0; i < str.length; i++) {
2312      // Try to avoid calling into C++ by first handling the ASCII portion of
2313      // the string. If it is fully ASCII, we skip the C++ part.
2314      const code = StringPrototypeCharCodeAt(str, i);
2315      if (code >= 127) {
2316        width += icu.getStringWidth(StringPrototypeNormalize(StringPrototypeSlice(str, i), 'NFC'));
2317        break;
2318      }
2319      width += code >= 32 ? 1 : 0;
2320    }
2321    return width;
2322  };
2323} else {
2324  /**
2325   * Returns the number of columns required to display the given string.
2326   */
2327  getStringWidth = function getStringWidth(str, removeControlChars = true) {
2328    let width = 0;
2329
2330    if (removeControlChars)
2331      str = stripVTControlCharacters(str);
2332    str = StringPrototypeNormalize(str, 'NFC');
2333    for (const char of new SafeStringIterator(str)) {
2334      const code = StringPrototypeCodePointAt(char, 0);
2335      if (isFullWidthCodePoint(code)) {
2336        width += 2;
2337      } else if (!isZeroWidthCodePoint(code)) {
2338        width++;
2339      }
2340    }
2341
2342    return width;
2343  };
2344
2345  /**
2346   * Returns true if the character represented by a given
2347   * Unicode code point is full-width. Otherwise returns false.
2348   */
2349  const isFullWidthCodePoint = (code) => {
2350    // Code points are partially derived from:
2351    // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
2352    return code >= 0x1100 && (
2353      code <= 0x115f ||  // Hangul Jamo
2354      code === 0x2329 || // LEFT-POINTING ANGLE BRACKET
2355      code === 0x232a || // RIGHT-POINTING ANGLE BRACKET
2356      // CJK Radicals Supplement .. Enclosed CJK Letters and Months
2357      (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
2358      // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
2359      (code >= 0x3250 && code <= 0x4dbf) ||
2360      // CJK Unified Ideographs .. Yi Radicals
2361      (code >= 0x4e00 && code <= 0xa4c6) ||
2362      // Hangul Jamo Extended-A
2363      (code >= 0xa960 && code <= 0xa97c) ||
2364      // Hangul Syllables
2365      (code >= 0xac00 && code <= 0xd7a3) ||
2366      // CJK Compatibility Ideographs
2367      (code >= 0xf900 && code <= 0xfaff) ||
2368      // Vertical Forms
2369      (code >= 0xfe10 && code <= 0xfe19) ||
2370      // CJK Compatibility Forms .. Small Form Variants
2371      (code >= 0xfe30 && code <= 0xfe6b) ||
2372      // Halfwidth and Fullwidth Forms
2373      (code >= 0xff01 && code <= 0xff60) ||
2374      (code >= 0xffe0 && code <= 0xffe6) ||
2375      // Kana Supplement
2376      (code >= 0x1b000 && code <= 0x1b001) ||
2377      // Enclosed Ideographic Supplement
2378      (code >= 0x1f200 && code <= 0x1f251) ||
2379      // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff
2380      // Emoticons 0x1f600 - 0x1f64f
2381      (code >= 0x1f300 && code <= 0x1f64f) ||
2382      // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
2383      (code >= 0x20000 && code <= 0x3fffd)
2384    );
2385  };
2386
2387}
2388
2389/**
2390 * Remove all VT control characters. Use to estimate displayed string width.
2391 */
2392function stripVTControlCharacters(str) {
2393  validateString(str, 'str');
2394
2395  return RegExpPrototypeSymbolReplace(ansi, str, '');
2396}
2397
2398module.exports = {
2399  identicalSequenceRange,
2400  inspect,
2401  inspectDefaultOptions,
2402  format,
2403  formatWithOptions,
2404  getStringWidth,
2405  stripVTControlCharacters,
2406  isZeroWidthCodePoint,
2407};
2408