diff --git a/js/console.ts b/js/console.ts index 002be6e2fd..ed2543a9d1 100644 --- a/js/console.ts +++ b/js/console.ts @@ -326,7 +326,9 @@ function createObjectString( value: {}, ...args: [ConsoleContext, number, number] ): string { - if (value instanceof Error) { + if (customInspect in value && typeof value[customInspect] === "function") { + return String(value[customInspect]!()); + } else if (value instanceof Error) { return String(value.stack); } else if (Array.isArray(value)) { return createArrayString(value, ...args); @@ -752,6 +754,11 @@ export class Console { } } +/** A symbol which can be used as a key for a custom method which will be called + * when `Deno.inspect()` is called, or when the object is logged to the console. + */ +export const customInspect = Symbol.for("Deno.customInspect"); + /** * `inspect()` converts input into string that has the same format * as printed by `console.log(...)`; diff --git a/js/console_test.ts b/js/console_test.ts index 8070ee0f0b..a2f9280a14 100644 --- a/js/console_test.ts +++ b/js/console_test.ts @@ -3,8 +3,15 @@ import { assert, assertEquals, test } from "./test_util.ts"; // Some of these APIs aren't exposed in the types and so we have to cast to any // in order to "trick" TypeScript. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const { Console, stringifyArgs, inspect, write, stdout } = Deno as any; +const { + Console, + customInspect, + stringifyArgs, + inspect, + write, + stdout + // eslint-disable-next-line @typescript-eslint/no-explicit-any +} = Deno as any; function stringify(...args: unknown[]): string { return stringifyArgs(args).replace(/\n$/, ""); @@ -173,6 +180,16 @@ test(function consoleTestStringifyWithDepth(): void { ); }); +test(function consoleTestWithCustomInspector(): void { + class A { + [customInspect](): string { + return "b"; + } + } + + assertEquals(stringify(new A()), "b"); +}); + test(function consoleTestWithIntegerFormatSpecifier(): void { assertEquals(stringify("%i"), "%i"); assertEquals(stringify("%i", 42.0), "42"); diff --git a/js/deno.ts b/js/deno.ts index 8dc4fd791d..65a93c467a 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -79,7 +79,7 @@ export { ProcessStatus, Signal } from "./process"; -export { inspect } from "./console"; +export { inspect, customInspect } from "./console"; export { build, platform, OperatingSystem, Arch } from "./build"; export { version } from "./version"; export const args: string[] = []; diff --git a/js/globals.ts b/js/globals.ts index 92e4662aa7..93495ff188 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -1,6 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // This is a "special" module, in that it define the global runtime scope of -// Deno, and therefore it defines a lot of the runtime environemnt that code +// Deno, and therefore it defines a lot of the runtime environment that code // is evaluated in. We use this file to automatically build the runtime type // library. @@ -12,7 +12,7 @@ import * as blob from "./blob"; import * as consoleTypes from "./console"; import * as csprng from "./get_random_values"; import * as customEvent from "./custom_event"; -import * as deno from "./deno"; +import * as Deno from "./deno"; import * as domTypes from "./dom_types"; import * as domFile from "./dom_file"; import * as event from "./event"; @@ -62,6 +62,10 @@ declare global { interface ErrorConstructor { prepareStackTrace(error: Error, structuredStackTrace: CallSite[]): string; } + + interface Object { + [consoleTypes.customInspect]?(): string; + } } // A self reference to the global object. @@ -70,7 +74,7 @@ window.window = window; // This is the Deno namespace, it is handled differently from other window // properties when building the runtime type library, as the whole module // is flattened into a single namespace. -window.Deno = deno; +window.Deno = Deno; Object.freeze(window.Deno); // Globally available functions and object instances.