From 1548792fb3530efb63432c0c704a3f0053410eb3 Mon Sep 17 00:00:00 2001 From: "Kevin (Kun) \"Kassimo\" Qian" Date: Mon, 10 Dec 2018 12:01:02 -0500 Subject: [PATCH] Add more console types formatting support (#1299) --- js/console.ts | 228 +++++++++++++++++++++++++++++++++++++++------ js/console_test.ts | 33 ++++++- 2 files changed, 225 insertions(+), 36 deletions(-) diff --git a/js/console.ts b/js/console.ts index ccfcf007b8..3d2d659096 100644 --- a/js/console.ts +++ b/js/console.ts @@ -1,4 +1,5 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. +import { isTypedArray } from "./util"; // tslint:disable-next-line:no-any type ConsoleContext = Set; @@ -9,7 +10,7 @@ type ConsoleOptions = Partial<{ }>; // Default depth of logging nested objects -const DEFAULT_MAX_DEPTH = 2; +const DEFAULT_MAX_DEPTH = 4; // tslint:disable-next-line:no-any function getClassInstanceName(instance: any): string { @@ -35,6 +36,42 @@ function createFunctionString(value: Function, ctx: ConsoleContext): string { return `[${cstrName}]`; } +interface IterablePrintConfig { + typeName: string; + displayName: string; + delims: [string, string]; + entryHandler: ( + // tslint:disable-next-line:no-any + entry: any, + ctx: ConsoleContext, + level: number, + maxLevel: number + ) => string; +} + +function createIterableString( + // tslint:disable-next-line:no-any + value: any, + ctx: ConsoleContext, + level: number, + maxLevel: number, + config: IterablePrintConfig +): string { + if (level >= maxLevel) { + return `[${config.typeName}]`; + } + ctx.add(value); + + const entries: string[] = []; + for (const el of value) { + entries.push(config.entryHandler(el, ctx, level + 1, maxLevel)); + } + ctx.delete(value); + const iPrefix = `${config.displayName ? config.displayName + " " : ""}`; + const iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `; + return `${iPrefix}${config.delims[0]}${iContent}${config.delims[1]}`; +} + function createArrayString( // tslint:disable-next-line:no-any value: any[], @@ -42,24 +79,122 @@ function createArrayString( level: number, maxLevel: number ): string { - const entries: string[] = []; - for (const el of value) { - entries.push(stringifyWithQuotes(ctx, el, level + 1, maxLevel)); - } - ctx.delete(value); - if (entries.length === 0) { - return "[]"; - } - return `[ ${entries.join(", ")} ]`; + const printConfig: IterablePrintConfig = { + typeName: "Array", + displayName: "", + delims: ["[", "]"], + entryHandler: (el, ctx, level, maxLevel) => + stringifyWithQuotes(el, ctx, level + 1, maxLevel) + }; + return createIterableString(value, ctx, level, maxLevel, printConfig); } -function createObjectString( +function createTypedArrayString( + typedArrayName: string, // tslint:disable-next-line:no-any value: any, ctx: ConsoleContext, level: number, maxLevel: number ): string { + const printConfig: IterablePrintConfig = { + typeName: typedArrayName, + displayName: typedArrayName, + delims: ["[", "]"], + entryHandler: (el, ctx, level, maxLevel) => + stringifyWithQuotes(el, ctx, level + 1, maxLevel) + }; + return createIterableString(value, ctx, level, maxLevel, printConfig); +} + +function createSetString( + // tslint:disable-next-line:no-any + value: Set, + ctx: ConsoleContext, + level: number, + maxLevel: number +): string { + const printConfig: IterablePrintConfig = { + typeName: "Set", + displayName: "Set", + delims: ["{", "}"], + entryHandler: (el, ctx, level, maxLevel) => + stringifyWithQuotes(el, ctx, level + 1, maxLevel) + }; + return createIterableString(value, ctx, level, maxLevel, printConfig); +} + +function createMapString( + // tslint:disable-next-line:no-any + value: Map, + ctx: ConsoleContext, + level: number, + maxLevel: number +): string { + const printConfig: IterablePrintConfig = { + typeName: "Map", + displayName: "Map", + delims: ["{", "}"], + entryHandler: (el, ctx, level, maxLevel) => { + const [key, val] = el; + return `${stringifyWithQuotes( + key, + ctx, + level + 1, + maxLevel + )} => ${stringifyWithQuotes(val, ctx, level + 1, maxLevel)}`; + } + }; + return createIterableString(value, ctx, level, maxLevel, printConfig); +} + +function createWeakSetString(): string { + return "WeakSet { [items unknown] }"; // as seen in Node +} + +function createWeakMapString(): string { + return "WeakMap { [items unknown] }"; // as seen in Node +} + +function createDateString(value: Date) { + // without quotes, ISO format + return value.toISOString(); +} + +function createRegExpString(value: RegExp) { + return value.toString(); +} + +// tslint:disable-next-line:ban-types +function createStringWrapperString(value: String) { + return `[String: "${value.toString()}"]`; +} + +// tslint:disable-next-line:ban-types +function createBooleanWrapperString(value: Boolean) { + return `[Boolean: ${value.toString()}]`; +} + +// tslint:disable-next-line:ban-types +function createNumberWrapperString(value: Number) { + return `[Number: ${value.toString()}]`; +} + +// TODO: Promise, requires v8 bindings to get info +// TODO: Proxy + +function createRawObjectString( + // tslint:disable-next-line:no-any + value: any, + ctx: ConsoleContext, + level: number, + maxLevel: number +): string { + if (level >= maxLevel) { + return "[Object]"; + } + ctx.add(value); + const entries: string[] = []; let baseString = ""; @@ -71,7 +206,7 @@ function createObjectString( for (const key of Object.keys(value)) { entries.push( - `${key}: ${stringifyWithQuotes(ctx, value[key], level + 1, maxLevel)}` + `${key}: ${stringifyWithQuotes(value[key], ctx, level + 1, maxLevel)}` ); } @@ -90,10 +225,54 @@ function createObjectString( return baseString; } -function stringify( - ctx: ConsoleContext, +function createObjectString( // tslint:disable-next-line:no-any value: any, + ...args: [ConsoleContext, number, number] +): string { + if (value instanceof Error) { + return value.stack! || ""; + } else if (Array.isArray(value)) { + return createArrayString(value, ...args); + } else if (value instanceof Number) { + // tslint:disable-next-line:ban-types + return createNumberWrapperString(value as Number); + } else if (value instanceof Boolean) { + // tslint:disable-next-line:ban-types + return createBooleanWrapperString(value as Boolean); + } else if (value instanceof String) { + // tslint:disable-next-line:ban-types + return createStringWrapperString(value as String); + } else if (value instanceof RegExp) { + return createRegExpString(value as RegExp); + } else if (value instanceof Date) { + return createDateString(value as Date); + } else if (value instanceof Set) { + // tslint:disable-next-line:no-any + return createSetString(value as Set, ...args); + } else if (value instanceof Map) { + // tslint:disable-next-line:no-any + return createMapString(value as Map, ...args); + } else if (value instanceof WeakSet) { + return createWeakSetString(); + } else if (value instanceof WeakMap) { + return createWeakMapString(); + } else if (isTypedArray(value)) { + return createTypedArrayString( + Object.getPrototypeOf(value).constructor.name, + value, + ...args + ); + } else { + // Otherwise, default object formatting + return createRawObjectString(value, ...args); + } +} + +function stringify( + // tslint:disable-next-line:no-any + value: any, + ctx: ConsoleContext, level: number, maxLevel: number ): string { @@ -118,20 +297,7 @@ function stringify( return "[Circular]"; } - if (level >= maxLevel) { - return `[object]`; - } - - ctx.add(value); - - if (value instanceof Error) { - return value.stack! || ""; - } else if (Array.isArray(value)) { - // tslint:disable-next-line:no-any - return createArrayString(value as any[], ctx, level, maxLevel); - } else { - return createObjectString(value, ctx, level, maxLevel); - } + return createObjectString(value, ctx, level, maxLevel); default: return "[Not Implemented]"; } @@ -139,9 +305,9 @@ function stringify( // Print strings when they are inside of arrays or objects with quotes function stringifyWithQuotes( - ctx: ConsoleContext, // tslint:disable-next-line:no-any value: any, + ctx: ConsoleContext, level: number, maxLevel: number ): string { @@ -149,7 +315,7 @@ function stringifyWithQuotes( case "string": return `"${value}"`; default: - return stringify(ctx, value, level, maxLevel); + return stringify(value, ctx, level, maxLevel); } } @@ -167,9 +333,9 @@ export function stringifyArgs( out.push( // use default maximum depth for null or undefined argument stringify( + a, // tslint:disable-next-line:no-any new Set(), - a, 0, // tslint:disable-next-line:triple-equals options.depth != undefined ? options.depth : DEFAULT_MAX_DEPTH diff --git a/js/console_test.ts b/js/console_test.ts index a46edf29e7..eb53ebb763 100644 --- a/js/console_test.ts +++ b/js/console_test.ts @@ -67,12 +67,30 @@ test(function consoleTestStringifyCircular() { nestedObj.o = circularObj; // tslint:disable-next-line:max-line-length - const nestedObjExpected = `{ num: 1, bool: true, str: "a", method: [Function: method], asyncMethod: [AsyncFunction: asyncMethod], generatorMethod: [GeneratorFunction: generatorMethod], un: undefined, nu: null, arrowFunc: [Function: arrowFunc], extendedClass: Extended { a: 1, b: 2 }, nFunc: [Function], extendedCstr: [Function: Extended], o: { num: 2, bool: false, str: "b", method: [Function: method], un: undefined, nu: null, nested: [Circular], emptyObj: [object], arr: [object], baseClass: [object] } }`; + const nestedObjExpected = `{ num: 1, bool: true, str: "a", method: [Function: method], asyncMethod: [AsyncFunction: asyncMethod], generatorMethod: [GeneratorFunction: generatorMethod], un: undefined, nu: null, arrowFunc: [Function: arrowFunc], extendedClass: Extended { a: 1, b: 2 }, nFunc: [Function], extendedCstr: [Function: Extended], o: { num: 2, bool: false, str: "b", method: [Function: method], un: undefined, nu: null, nested: [Circular], emptyObj: {}, arr: [ 1, "s", false, null, [Circular] ], baseClass: Base { a: 1 } } }`; assertEqual(stringify(1), "1"); assertEqual(stringify(1n), "1n"); assertEqual(stringify("s"), "s"); assertEqual(stringify(false), "false"); + // tslint:disable-next-line:no-construct + assertEqual(stringify(new Number(1)), "[Number: 1]"); + // tslint:disable-next-line:no-construct + assertEqual(stringify(new Boolean(true)), "[Boolean: true]"); + // tslint:disable-next-line:no-construct + assertEqual(stringify(new String("deno")), `[String: "deno"]`); + assertEqual(stringify(/[0-9]*/), "/[0-9]*/"); + assertEqual( + stringify(new Date("2018-12-10T02:26:59.002Z")), + "2018-12-10T02:26:59.002Z" + ); + assertEqual(stringify(new Set([1, 2, 3])), "Set { 1, 2, 3 }"); + assertEqual( + stringify(new Map([[1, "one"], [2, "two"]])), + `Map { 1 => "one", 2 => "two" }` + ); + assertEqual(stringify(new WeakSet()), "WeakSet { [items unknown] }"); + assertEqual(stringify(new WeakMap()), "WeakMap { [items unknown] }"); assertEqual(stringify(Symbol(1)), "Symbol(1)"); assertEqual(stringify(null), "null"); assertEqual(stringify(undefined), "undefined"); @@ -84,6 +102,11 @@ test(function consoleTestStringifyCircular() { stringify(async function* agf() {}), "[AsyncGeneratorFunction: agf]" ); + assertEqual(stringify(new Uint8Array([1, 2, 3])), "Uint8Array [ 1, 2, 3 ]"); + assertEqual( + stringify({ a: { b: { c: { d: new Set([1]) } } } }), + "{ a: { b: { c: { d: [Set] } } } }" + ); assertEqual(stringify(nestedObj), nestedObjExpected); assertEqual(stringify(JSON), "{}"); assertEqual( @@ -98,16 +121,16 @@ test(function consoleTestStringifyWithDepth() { const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } }; assertEqual( stringifyArgs([nestedObj], { depth: 3 }), - "{ a: { b: { c: [object] } } }" + "{ a: { b: { c: [Object] } } }" ); assertEqual( stringifyArgs([nestedObj], { depth: 4 }), - "{ a: { b: { c: { d: [object] } } } }" + "{ a: { b: { c: { d: [Object] } } } }" ); - assertEqual(stringifyArgs([nestedObj], { depth: 0 }), "[object]"); + assertEqual(stringifyArgs([nestedObj], { depth: 0 }), "[Object]"); assertEqual( stringifyArgs([nestedObj], { depth: null }), - "{ a: { b: [object] } }" + "{ a: { b: { c: { d: [Object] } } } }" ); });