├── .gitignore ├── .vscode ├── settings.json └── extensions.json ├── .github └── log-error-cause.png ├── prettier.config.mjs ├── src ├── error-capture-stack-trace.types.ts ├── internal-assert.ts ├── index.ts ├── hex-slice.ts ├── internal-util.ts ├── internal-errors.ts ├── internal-validators.ts ├── node-util.ts ├── internal-util-types.ts ├── primordials.ts └── inspect.ts ├── tsconfig.json ├── scripts ├── test-iterators.js └── test-error-cause.js ├── tsconfig.eslint.json ├── LICENSE.txt ├── package.json ├── LICENSE-node-inspect-extracted.txt ├── LICENSE-node-primordials.md ├── LICENSE-node.txt ├── eslint.config.mjs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /*.tgz 3 | node_modules 4 | dist -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "biome.enabled": false, 3 | "prettier.enable": true 4 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/log-error-cause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shirakaba/util-inspect-isomorphic/HEAD/.github/log-error-cause.png -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @satisfies {import("prettier").Config} 3 | * @see https://prettier.io/docs/en/configuration.html 4 | */ 5 | const config = {}; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /src/error-capture-stack-trace.types.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/microsoft/TypeScript/issues/3926#issuecomment-169096154 2 | interface ErrorConstructor { 3 | captureStackTrace(thisArg: any, func: any): void; 4 | } 5 | -------------------------------------------------------------------------------- /src/internal-assert.ts: -------------------------------------------------------------------------------- 1 | export function assert( 2 | condition: unknown, 3 | message?: string, 4 | ): asserts condition { 5 | if (!condition) { 6 | throw new Error(message ?? "Assertion failed"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "outDir": "dist", 5 | "declaration": true, 6 | "target": "ES6", 7 | // Adding DOM types purely for URL. 8 | "lib": ["ES2022", "DOM"], 9 | "strict": true 10 | }, 11 | "include": ["src"] 12 | } 13 | -------------------------------------------------------------------------------- /scripts/test-iterators.js: -------------------------------------------------------------------------------- 1 | // import { inspect } from "node:util"; 2 | import { inspect } from "../dist/inspect.js"; 3 | 4 | const set = new Set(["a", "b", "c"]); 5 | const map = new Map([ 6 | ["a", 0], 7 | ["b", 1], 8 | ["c", 2], 9 | ]); 10 | 11 | console.log(inspect(set.entries())); 12 | console.log(inspect(map.entries())); 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Re-export all the same things that `require("node:util")` does, as present. 2 | 3 | export { 4 | format, 5 | formatWithOptions, 6 | inspect, 7 | stripVTControlCharacters, 8 | } from "./inspect.js"; 9 | export { isRegExp, isDate } from "./internal-util-types.js"; 10 | export { isError } from "./internal-util.js"; 11 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | // https://github.com/typescript-eslint/typescript-eslint/blob/958fecaef10a26792dc00e936e98cb19fd26d05f/tsconfig.eslint.json 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "types": ["@types/node"], 6 | "noEmit": true, 7 | "allowJs": true 8 | }, 9 | "include": ["src", "eslint.config.mjs", "prettier.config.mjs"] 10 | } 11 | -------------------------------------------------------------------------------- /scripts/test-error-cause.js: -------------------------------------------------------------------------------- 1 | // import { inspect } from "node:util"; 2 | import { inspect } from "../dist/inspect.js"; 3 | 4 | try { 5 | case1(); 6 | } catch (error) { 7 | inspect(error); 8 | console.log(inspect(error)); 9 | } 10 | 11 | function case1() { 12 | try { 13 | errorful(); 14 | } catch (cause) { 15 | throw new Error("Errorful failed.", { cause }); 16 | } 17 | } 18 | 19 | function errorful() { 20 | try { 21 | nestedErrorful(); 22 | } catch (cause) { 23 | throw new Error("Nested errorful failed.", { cause }); 24 | } 25 | } 26 | 27 | function nestedErrorful() { 28 | throw new Error("Nested errorful!"); 29 | } 30 | -------------------------------------------------------------------------------- /src/hex-slice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A shim for `require("node:buffer").Buffer.prototype.hexSlice`. 3 | */ 4 | export function hexSlice( 5 | buffer: 6 | | Int8Array 7 | | Uint8Array 8 | | Uint8ClampedArray 9 | | Int16Array 10 | | Uint16Array 11 | | Int32Array 12 | | Uint32Array 13 | | Float32Array 14 | | Float64Array, 15 | start?: number, 16 | end?: number, 17 | ) { 18 | const sliced = buffer.slice(start, end); 19 | 20 | let hex = ""; 21 | for (const value of sliced) { 22 | hex += value.toString(16); 23 | } 24 | return hex; 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This project adapts source code from three other projects, attaching their 2 | licences as follows: 3 | 4 | - node: 5 | - https://github.com/nodejs/node 6 | - LICENSE-node.txt 7 | - node-primordials: 8 | - https://github.com/isaacs/node-primordials 9 | - LICENSE-node-primordials.md 10 | - node-inspect-extracted: 11 | - https://github.com/hildjj/node-inspect-extracted 12 | - LICENSE-node-inspect-extracted.txt 13 | 14 | The Node.js internals adapted in this project mostly consist of the file 15 | lib/internal/util/inspect.js, which does not have the Joyent copyright header. 16 | 17 | The maintainers of this package will not assert copyright over this code, but 18 | will assign ownership to the Node.js contributors, with the same license as 19 | specified in the Node.js codebase; the portion adapted here should all be plain 20 | MIT license. 21 | 22 | See README.md for a full summary of which source code was adapted from which 23 | projects. 24 | -------------------------------------------------------------------------------- /src/internal-util.ts: -------------------------------------------------------------------------------- 1 | import { primordials } from "./primordials.js"; 2 | 3 | const colorRegExp = /\u001b\[\d\d?m/g; 4 | 5 | export function removeColors(str: string) { 6 | return primordials.StringPrototypeReplace(str, colorRegExp, ""); 7 | } 8 | 9 | export function isError(e: unknown) { 10 | // JB: No way to implement the finer details in an engine-agnostic fashion. 11 | // 12 | // // An error could be an instance of Error while not being a native error 13 | // // or could be from a different realm and not be instance of Error but still 14 | // // be a native error. 15 | // // return isNativeError(e) || e instanceof Error; 16 | return e instanceof Error; 17 | } 18 | 19 | /** 20 | * "The built-in Array#join is slower in v8 6.0" 21 | * @see https://github.com/nodejs/node/blob/1093f38c437c44589c9962d3156540036d0717bd/lib/internal/util.js#L518C1-L530C2 22 | */ 23 | export function join(output: Array, separator: string) { 24 | let str = ""; 25 | if (output.length !== 0) { 26 | const lastIndex = output.length - 1; 27 | for (let i = 0; i < lastIndex; i++) { 28 | // It is faster not to use a template string here 29 | str += output[i]; 30 | str += separator; 31 | } 32 | str += output[lastIndex]; 33 | } 34 | return str; 35 | } 36 | 37 | export const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "util-inspect-isomorphic", 3 | "version": "1.0.0", 4 | "author": "Jamie Birch <14055146+shirakaba@users.noreply.github.com>", 5 | "description": "An isomorphic port of the Node.js API require('node:util').inspect().", 6 | "type": "module", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "LICENSE-node.txt", 12 | "LICENSE-node-primordials.md", 13 | "LICENSE-node-inspect-extracted.txt" 14 | ], 15 | "scripts": { 16 | "build": "tsc", 17 | "test": "node scripts/test-error-cause.js", 18 | "prepack": "node --run build" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/shirakaba/util-inspect-isomorphic.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/shirakaba/util-inspect-isomorphic/issues" 26 | }, 27 | "homepage": "https://github.com/shirakaba/util-inspect-isomorphic/tree/main", 28 | "keywords": [ 29 | "util", 30 | "inspect", 31 | "node" 32 | ], 33 | "devDependencies": { 34 | "@eslint/compat": "^1.2.9", 35 | "@eslint/js": "^9.28.0", 36 | "@typescript-eslint/eslint-plugin": "^8.33.1", 37 | "eslint": "^9.28.0", 38 | "eslint-config-prettier": "^10.1.5", 39 | "eslint-plugin-import": "^2.31.0", 40 | "globals": "^16.2.0", 41 | "prettier": "^3.5.3", 42 | "prettier-plugin-organize-attributes": "^1.0.0", 43 | "typescript": "^5.8.3", 44 | "typescript-eslint": "^8.33.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/internal-errors.ts: -------------------------------------------------------------------------------- 1 | import { primordials } from "./primordials.js"; 2 | 3 | /** 4 | * This function removes unnecessary frames from Node.js core errors. 5 | */ 6 | export function hideStackFrames) => any>( 7 | fn: T, 8 | ): T { 9 | function wrappedFn(...args: Array) { 10 | try { 11 | // @ts-ignore 12 | return primordials.ReflectApply(fn, this, args); 13 | } catch (error) { 14 | "stackTraceLimit" in Error && 15 | Error.stackTraceLimit && 16 | Error.captureStackTrace(error, wrappedFn); 17 | throw error; 18 | } 19 | } 20 | wrappedFn.withoutStackTrace = fn; 21 | return wrappedFn as unknown as T; 22 | } 23 | 24 | let maxStack_ErrorName: string; 25 | let maxStack_ErrorMessage: string; 26 | 27 | /** 28 | * Returns true if `err.name` and `err.message` are equal to engine-specific 29 | * values indicating max call stack size has been exceeded. 30 | * "Maximum call stack size exceeded" in V8. 31 | */ 32 | export function isStackOverflowError(err: Error) { 33 | if (maxStack_ErrorMessage === undefined) { 34 | try { 35 | function overflowStack() { 36 | overflowStack(); 37 | } 38 | overflowStack(); 39 | } catch (err) { 40 | maxStack_ErrorMessage = (err as Error).message; 41 | maxStack_ErrorName = (err as Error).name; 42 | } 43 | } 44 | 45 | return ( 46 | err && 47 | err.name === maxStack_ErrorName && 48 | err.message === maxStack_ErrorMessage 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE-node-inspect-extracted.txt: -------------------------------------------------------------------------------- 1 | This code is an adaptation of the Node.js internal implementation, mostly 2 | from the file lib/internal/util/inspect.js, which does not have the Joyent 3 | copyright header. The maintainers of this package will not assert copyright 4 | over this code, but will assign ownership to the Node.js contributors, with 5 | the same license as specified in the Node.js codebase; the portion adapted 6 | here should all be plain MIT license. 7 | 8 | Node.js is licensed for use as follows: 9 | 10 | """ 11 | Copyright Node.js contributors. All rights reserved. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to 15 | deal in the Software without restriction, including without limitation the 16 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | IN THE SOFTWARE. 30 | """ -------------------------------------------------------------------------------- /LICENSE-node-primordials.md: -------------------------------------------------------------------------------- 1 | # Blue Oak Model License 2 | 3 | Version 1.0.0 4 | 5 | ## Purpose 6 | 7 | This license gives everyone as much permission to work with 8 | this software as possible, while protecting contributors 9 | from liability. 10 | 11 | ## Acceptance 12 | 13 | In order to receive this license, you must agree to its 14 | rules. The rules of this license are both obligations 15 | under that agreement and conditions to your license. 16 | You must not do anything with this software that triggers 17 | a rule that you cannot or will not follow. 18 | 19 | ## Copyright 20 | 21 | Each contributor licenses you to do everything with this 22 | software that would otherwise infringe that contributor's 23 | copyright in it. 24 | 25 | ## Notices 26 | 27 | You must ensure that everyone who gets a copy of 28 | any part of this software from you, with or without 29 | changes, also gets the text of this license or a link to 30 | . 31 | 32 | ## Excuse 33 | 34 | If anyone notifies you in writing that you have not 35 | complied with [Notices](#notices), you can keep your 36 | license by taking all practical steps to comply within 30 37 | days after the notice. If you do not do so, your license 38 | ends immediately. 39 | 40 | ## Patent 41 | 42 | Each contributor licenses you to do everything with this 43 | software that would otherwise infringe any patent claims 44 | they can license or become able to license. 45 | 46 | ## Reliability 47 | 48 | No contributor can revoke this license. 49 | 50 | ## No Liability 51 | 52 | ***As far as the law allows, this software comes as is, 53 | without any warranty or condition, and no contributor 54 | will be liable to anyone for any damages related to this 55 | software or this license, under any kind of legal claim.*** -------------------------------------------------------------------------------- /LICENSE-node.txt: -------------------------------------------------------------------------------- 1 | Node.js is licensed for use as follows: 2 | 3 | """ 4 | Copyright Node.js contributors. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to 8 | deal in the Software without restriction, including without limitation the 9 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | IN THE SOFTWARE. 23 | """ 24 | 25 | This license applies to parts of Node.js originating from the 26 | https://github.com/joyent/node repository: 27 | 28 | """ 29 | Copyright Joyent, Inc. and other Node contributors. All rights reserved. 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to 32 | deal in the Software without restriction, including without limitation the 33 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 34 | sell copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | 37 | The above copyright notice and this permission notice shall be included in 38 | all copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 45 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 46 | IN THE SOFTWARE. 47 | """ 48 | 49 | The Node.js license applies to all parts of Node.js that are not externally 50 | maintained libraries. 51 | -------------------------------------------------------------------------------- /src/internal-validators.ts: -------------------------------------------------------------------------------- 1 | import { hideStackFrames } from "./internal-errors.js"; 2 | import { primordials } from "./primordials.js"; 3 | 4 | export const validateString: ( 5 | value: unknown, 6 | name: string, 7 | ) => asserts value is string = hideStackFrames( 8 | (value: unknown, name: string): asserts value is string => { 9 | if (typeof value !== "string") 10 | // JB: Implementing ERR_INVALID_ARG_TYPE is a deep rabbit hole. 11 | // // throw new ERR_INVALID_ARG_TYPE(name, "string", value); 12 | throw new TypeError( 13 | `Expected string for parameter "${name}", but got ${typeof value}.`, 14 | ); 15 | }, 16 | ); 17 | 18 | export const kValidateObjectNone = 0; 19 | export const kValidateObjectAllowNullable = 1 << 0; 20 | export const kValidateObjectAllowArray = 1 << 1; 21 | export const kValidateObjectAllowFunction = 1 << 2; 22 | 23 | export const validateObject = hideStackFrames( 24 | ( 25 | value: unknown, 26 | name: string, 27 | options: 28 | | typeof kValidateObjectNone 29 | | typeof kValidateObjectAllowNullable 30 | | typeof kValidateObjectAllowArray 31 | | typeof kValidateObjectAllowFunction = kValidateObjectNone, 32 | ): void => { 33 | // JB: Implementing ERR_INVALID_ARG_TYPE is a deep rabbit hole. 34 | 35 | if (options === kValidateObjectNone) { 36 | if (value === null || primordials.ArrayIsArray(value)) { 37 | // throw new ERR_INVALID_ARG_TYPE(name, "Object", value); 38 | throw new TypeError( 39 | `Expected Object for parameter "${name}", but got ${typeof value}.`, 40 | ); 41 | } 42 | 43 | if (typeof value !== "object") { 44 | // throw new ERR_INVALID_ARG_TYPE(name, "Object", value); 45 | throw new TypeError( 46 | `Expected Object for parameter "${name}", but got ${typeof value}.`, 47 | ); 48 | } 49 | } else { 50 | const throwOnNullable = (kValidateObjectAllowNullable & options) === 0; 51 | 52 | if (throwOnNullable && value === null) { 53 | // throw new ERR_INVALID_ARG_TYPE(name, "Object", value); 54 | throw new TypeError( 55 | `Expected Object for parameter "${name}", but got ${typeof value}.`, 56 | ); 57 | } 58 | 59 | const throwOnArray = (kValidateObjectAllowArray & options) === 0; 60 | 61 | if (throwOnArray && primordials.ArrayIsArray(value)) { 62 | // throw new ERR_INVALID_ARG_TYPE(name, "Object", value); 63 | throw new TypeError( 64 | `Expected Object for parameter "${name}", but got ${typeof value}.`, 65 | ); 66 | } 67 | 68 | const throwOnFunction = (kValidateObjectAllowFunction & options) === 0; 69 | const typeofValue = typeof value; 70 | 71 | if ( 72 | typeofValue !== "object" && 73 | (throwOnFunction || typeofValue !== "function") 74 | ) { 75 | // throw new ERR_INVALID_ARG_TYPE(name, "Object", value); 76 | throw new TypeError( 77 | `Expected Object for parameter "${name}", but got ${typeof value}.`, 78 | ); 79 | } 80 | } 81 | }, 82 | ); 83 | -------------------------------------------------------------------------------- /src/node-util.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { isSetIterator, isMapIterator } from "./internal-util-types.js"; 3 | 4 | /** 5 | * From `node-inspect-extracted`: 6 | * @see https://github.com/hildjj/node-inspect-extracted/blob/7ea8149fbda1a81322e2d99484fe1cb7873a5f1e/src/util.js#L21 7 | * 8 | * Original native code here: 9 | * @see https://github.com/nodejs/node/blob/919ef7cae89ea9821db041cde892697cc8030b7c/src/node_util.cc#L52 10 | * @see https://source.chromium.org/chromium/chromium/src/+/main:v8/src/api/api.cc;l=4732?q=GetPropertyNames&sq=&ss=chromium%2Fchromium%2Fsrc:v8%2F 11 | */ 12 | export function getOwnNonIndexProperties( 13 | value: unknown, 14 | filter = PropertyFilter.ONLY_ENUMERABLE, 15 | ) { 16 | const desc = Object.getOwnPropertyDescriptors(value); 17 | const ret = new Array(); 18 | 19 | for (const [entryKey, entryValue] of Object.entries(desc)) { 20 | if ( 21 | !/^(0|[1-9][0-9]*)$/.test(entryKey) || 22 | Number.parseInt(entryKey, 10) >= 2 ** 32 - 1 23 | ) { 24 | // Arrays are limited in size 25 | if (filter === PropertyFilter.ONLY_ENUMERABLE && !entryValue.enumerable) { 26 | continue; 27 | } 28 | ret.push(entryKey); 29 | } 30 | } 31 | 32 | for (const sym of Object.getOwnPropertySymbols(value)) { 33 | const descriptor = Object.getOwnPropertyDescriptor(value, sym); 34 | if (filter === PropertyFilter.ONLY_ENUMERABLE && !descriptor?.enumerable) { 35 | continue; 36 | } 37 | ret.push(sym); 38 | } 39 | 40 | return ret; 41 | } 42 | 43 | export function getPromiseState(promise: Promise) { 44 | const pendingState = { status: "pending" }; 45 | 46 | return Promise.race([promise, pendingState]).then( 47 | (value) => 48 | value === pendingState ? value : { status: "fulfilled", value }, 49 | (reason) => ({ status: "rejected", reason }), 50 | ); 51 | } 52 | 53 | /** 54 | * @see https://github.com/nodejs/node/blob/919ef7cae89ea9821db041cde892697cc8030b7c/src/node_util.cc#L166 55 | * @see https://source.chromium.org/chromium/chromium/src/+/main:v8/src/api/api.cc;l=11202?q=PreviewEntries&ss=chromium%2Fchromium%2Fsrc:v8%2F 56 | */ 57 | export function previewEntries( 58 | value: unknown, 59 | isKeyValue?: boolean, 60 | ): [entries: Array, isKeyValue: boolean | undefined] { 61 | if (typeof value !== "object" || value === null) { 62 | throw new TypeError("First argument must be an object"); 63 | } 64 | 65 | if (value instanceof Map || value instanceof Set) { 66 | return [Array.from(value), true]; 67 | } 68 | 69 | if (isSetIterator(value) || isMapIterator(value)) { 70 | const elements = new Array<[unknown, unknown]>(); 71 | for (const element of value) { 72 | elements.push(element); 73 | } 74 | 75 | // JB: We can't determine whether the iterator is key-value in an 76 | // engine-agnostic manner, so report `isKeyValue` as `false`. This is 77 | // because it's safe downstream to call `formatSetIterInner()` on either 78 | // type, but unsafe to call `formatMapIterInner()` on anything but a 79 | // key-value iterator. 80 | // 81 | return [elements, false]; 82 | } 83 | 84 | return [[], isKeyValue]; 85 | } 86 | 87 | /** 88 | * Just the ones we need out of the original: 89 | * @see https://chromium.googlesource.com/v8/v8/+/981aafaf977d4671763c341102a4ee5ef172f7f6/src/objects/property-details.h#35 90 | */ 91 | enum PropertyFilter { 92 | ALL_PROPERTIES = 0, 93 | ONLY_ENUMERABLE = 2, 94 | } 95 | 96 | export const constants = { 97 | ...PropertyFilter, 98 | }; 99 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url'; 2 | 3 | import { includeIgnoreFile } from '@eslint/compat'; 4 | import pluginJs from '@eslint/js'; 5 | import tsEslintPlugin from '@typescript-eslint/eslint-plugin'; 6 | import eslintConfigPrettier from 'eslint-config-prettier/flat'; 7 | import importPlugin from 'eslint-plugin-import'; 8 | import pluginReact from 'eslint-plugin-react'; 9 | import globals from 'globals'; 10 | import tseslint from 'typescript-eslint'; 11 | 12 | const { configs: tsEslintConfigs } = tsEslintPlugin; 13 | const { 'disable-type-checked': disableTypeChecked } = tsEslintConfigs; 14 | 15 | const gitignorePath = fileURLToPath(new URL('.gitignore', import.meta.url)); 16 | 17 | /** @type {import('eslint').Linter.Config[]} */ 18 | export default [ 19 | includeIgnoreFile(gitignorePath), 20 | { 21 | files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], 22 | languageOptions: { globals: globals.browser, ecmaVersion: 'latest' }, 23 | }, 24 | pluginJs.configs.recommended, 25 | 26 | // Untyped linting of TypeScript. We override this later with typed linting. 27 | ...tseslint.configs.recommended, 28 | 29 | pluginReact.configs.flat.recommended, 30 | pluginReact.configs.flat['jsx-runtime'], 31 | { settings: { react: { version: 'detect' } } }, 32 | 33 | importPlugin.flatConfigs.recommended, 34 | eslintConfigPrettier, 35 | 36 | // Typed linting of TypeScript. 37 | // https://typescript-eslint.io/getting-started/typed-linting/ 38 | // https://typescript-eslint.io/troubleshooting/typed-linting/#traditional-project-issues 39 | ...tseslint.configs.recommendedTypeChecked, 40 | { 41 | languageOptions: { 42 | // Based on: https://github.com/typescript-eslint/typescript-eslint/blob/958fecaef10a26792dc00e936e98cb19fd26d05f/.eslintrc.js 43 | parserOptions: { 44 | tsconfigRootDir: import.meta.dirname, 45 | project: ['tsconfig.eslint.json'], 46 | }, 47 | }, 48 | }, 49 | { 50 | files: ['**/*.js', '**/*.mjs', '**/*.cjs'], 51 | 52 | languageOptions: { 53 | parserOptions: disableTypeChecked.parserOptions ?? {}, 54 | }, 55 | 56 | rules: disableTypeChecked.rules ?? {}, 57 | }, 58 | { 59 | rules: { 60 | // https://github.com/iamturns/eslint-config-airbnb-typescript/issues/345#issuecomment-2269783683 61 | 'import/no-unresolved': 'off', 62 | 63 | 'import/first': 'error', 64 | 'import/newline-after-import': 'error', 65 | 'import/no-duplicates': 'error', 66 | 'import/order': [ 67 | 'warn', 68 | { 69 | alphabetize: { order: 'asc' }, 70 | pathGroups: [ 71 | { 72 | pattern: '../../../../**', 73 | group: 'parent', 74 | position: 'before', 75 | }, 76 | { 77 | pattern: '../../../**', 78 | group: 'parent', 79 | position: 'before', 80 | }, 81 | { 82 | pattern: '../../**', 83 | group: 'parent', 84 | position: 'before', 85 | }, 86 | { 87 | pattern: '../**', 88 | group: 'parent', 89 | position: 'before', 90 | }, 91 | ], 92 | 'newlines-between': 'always', 93 | }, 94 | ], 95 | 96 | 'linebreak-style': ['error', 'unix'], 97 | 98 | 'no-constant-condition': [ 99 | 'error', 100 | { 101 | checkLoops: false, 102 | }, 103 | ], 104 | 105 | 'no-prototype-builtins': 'off', 106 | 107 | 'prefer-const': [ 108 | 'error', 109 | { 110 | destructuring: 'all', 111 | }, 112 | ], 113 | 114 | quotes: [ 115 | 'error', 116 | 'single', 117 | { 118 | avoidEscape: true, 119 | }, 120 | ], 121 | 122 | 'react/react-in-jsx-scope': 'off', 123 | 'react/prop-types': 'off', 124 | 'react/no-unknown-property': 'off', 125 | semi: ['error', 'always'], 126 | '@typescript-eslint/no-empty-function': 'off', 127 | '@typescript-eslint/no-explicit-any': 'off', 128 | 129 | '@typescript-eslint/no-non-null-assertion': 'off', 130 | 131 | '@typescript-eslint/no-unused-vars': [ 132 | 'error', 133 | { 134 | args: 'all', 135 | argsIgnorePattern: '^_', 136 | caughtErrors: 'all', 137 | caughtErrorsIgnorePattern: '^_', 138 | destructuredArrayIgnorePattern: '^_', 139 | varsIgnorePattern: '^_', 140 | ignoreRestSiblings: true, 141 | }, 142 | ], 143 | 144 | // Never sure what they mean by an "error typed value". 145 | '@typescript-eslint/no-unsafe-call': 'off', 146 | '@typescript-eslint/no-unsafe-assignment': 'off', 147 | '@typescript-eslint/no-unsafe-member-access': 'off', 148 | }, 149 | }, 150 | ]; 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

util-inspect-isomorphic 🔍

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