From 1e390e69cd45aeb4570fe821ef0ea866b71433e6 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Wed, 26 Sep 2018 09:44:59 -0700 Subject: [PATCH] Error pretty print (print stack) --- js/console.ts | 112 ++++++++++++++++++++++++++------------------- js/console_test.ts | 14 ++++++ 2 files changed, 79 insertions(+), 47 deletions(-) diff --git a/js/console.ts b/js/console.ts index 00a24e83ce..d3bd5bb6bb 100644 --- a/js/console.ts +++ b/js/console.ts @@ -6,12 +6,68 @@ function getClassInstanceName(instance: any): string { if (typeof instance !== "object") { return ""; } - if (instance && instance.__proto__ && instance.__proto__.constructor) { - return instance.__proto__.constructor.name; // could be "Object" or "Array" + if (instance) { + const proto = Object.getPrototypeOf(instance); + if (proto && proto.constructor) { + return proto.constructor.name; // could be "Object" or "Array" + } } return ""; } +function createFunctionString(value: Function, ctx: ConsoleContext): string { + // Might be Function/AsyncFunction/GeneratorFunction + const cstrName = Object.getPrototypeOf(value).constructor.name; + if (value.name && value.name !== "anonymous") { + // from MDN spec + return `[${cstrName}: ${value.name}]`; + } + return `[${cstrName}]`; +} + +// tslint:disable-next-line:no-any +function createArrayString(value: any[], ctx: ConsoleContext): string { + const entries: string[] = []; + for (const el of value) { + entries.push(stringifyWithQuotes(ctx, el)); + } + ctx.delete(value); + if (entries.length === 0) { + return "[]"; + } + return `[ ${entries.join(", ")} ]`; +} + +// tslint:disable-next-line:no-any +function createObjectString(value: any, ctx: ConsoleContext): string { + const entries: string[] = []; + let baseString = ""; + + const className = getClassInstanceName(value); + let shouldShowClassName = false; + if (className && className !== "Object" && className !== "anonymous") { + shouldShowClassName = true; + } + + for (const key of Object.keys(value)) { + entries.push(`${key}: ${stringifyWithQuotes(ctx, value[key])}`); + } + + ctx.delete(value); + + if (entries.length === 0) { + baseString = "{}"; + } else { + baseString = `{ ${entries.join(", ")} }`; + } + + if (shouldShowClassName) { + baseString = `${className} ${baseString}`; + } + + return baseString; +} + // tslint:disable-next-line:no-any function stringify(ctx: ConsoleContext, value: any): string { switch (typeof value) { @@ -23,13 +79,7 @@ function stringify(ctx: ConsoleContext, value: any): string { case "symbol": return String(value); case "function": - // Might be Function/AsyncFunction/GeneratorFunction - const cstrName = value.__proto__.constructor.name; - if (value.name && value.name !== "anonymous") { - // from MDN spec - return `[${cstrName}: ${value.name}]`; - } - return `[${cstrName}]`; + return createFunctionString(value as Function, ctx); case "object": if (value === null) { return "null"; @@ -38,47 +88,15 @@ function stringify(ctx: ConsoleContext, value: any): string { if (ctx.has(value)) { return "[Circular]"; } - ctx.add(value); - const entries: string[] = []; - if (Array.isArray(value)) { - for (const el of value) { - entries.push(stringifyWithQuotes(ctx, el)); - } - - ctx.delete(value); - - if (entries.length === 0) { - return "[]"; - } - return `[ ${entries.join(", ")} ]`; + if (value instanceof Error) { + return value.stack! || ""; + } else if (Array.isArray(value)) { + // tslint:disable-next-line:no-any + return createArrayString(value as any[], ctx); } else { - let baseString = ""; - - const className = getClassInstanceName(value); - let shouldShowClassName = false; - if (className && className !== "Object" && className !== "anonymous") { - shouldShowClassName = true; - } - - for (const key of Object.keys(value)) { - entries.push(`${key}: ${stringifyWithQuotes(ctx, value[key])}`); - } - - ctx.delete(value); - - if (entries.length === 0) { - baseString = "{}"; - } else { - baseString = `{ ${entries.join(", ")} }`; - } - - if (shouldShowClassName) { - baseString = `${className} ${baseString}`; - } - - return baseString; + return createObjectString(value, ctx); } default: return "[Not Implemented]"; diff --git a/js/console_test.ts b/js/console_test.ts index cfe75dafe3..94a627a5ff 100644 --- a/js/console_test.ts +++ b/js/console_test.ts @@ -91,3 +91,17 @@ test(function consoleTestStringifyCircular() { "Console { printFunc: [Function], debug: [Function: log], info: [Function: log], error: [Function: warn] }" ); }); + +test(function consoleTestError() { + class MyError extends Error { + constructor(msg: string) { + super(msg); + this.name = "MyError"; + } + } + try { + throw new MyError("This is an error"); + } catch (e) { + assertEqual(stringify(e).split("\n")[0], "MyError: This is an error"); + } +});