Skip to content

Commit

Permalink
Add more console types formatting support
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinkassimo committed Dec 10, 2018
1 parent f2447f6 commit bcc1b14
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 34 deletions.
245 changes: 216 additions & 29 deletions js/console.ts
Expand Up @@ -9,7 +9,19 @@ type ConsoleOptions = Partial<{
}>;

// Default depth of logging nested objects
const DEFAULT_MAX_DEPTH = 2;
const DEFAULT_MAX_DEPTH = 4;

const TYPEDARRAY_TYPES = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];

// tslint:disable-next-line:no-any
function getClassInstanceName(instance: any): string {
Expand All @@ -35,31 +47,170 @@ function createFunctionString(value: Function, ctx: ConsoleContext): string {
return `[${cstrName}]`;
}

function createArrayString(
interface IterablePrintConfig {
typeName: string;
displayName: string;
leftDelim: string;
rightDelim: 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[],
value: any,
ctx: ConsoleContext,
level: number,
maxLevel: 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(stringifyWithQuotes(ctx, el, level + 1, maxLevel));
entries.push(config.entryHandler(el, ctx, level + 1, maxLevel));
}
ctx.delete(value);
if (entries.length === 0) {
return "[]";
}
return `[ ${entries.join(", ")} ]`;
const iPrefix = `${config.displayName ? config.displayName + " " : ""}`;
const iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
return `${iPrefix}${config.leftDelim}${iContent}${config.rightDelim}`;
}

function createObjectString(
function createArrayString(
// tslint:disable-next-line:no-any
value: any[],
ctx: ConsoleContext,
level: number,
maxLevel: number
): string {
const printConfig: IterablePrintConfig = {
typeName: "Array",
displayName: "",
leftDelim: "[",
rightDelim: "]",
entryHandler: (el, ctx, level, maxLevel) =>
stringifyWithQuotes(el, ctx, level + 1, maxLevel)
};
return createIterableString(value, ctx, level, maxLevel, printConfig);
}

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,
leftDelim: "[",
rightDelim: "]",
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<any>,
ctx: ConsoleContext,
level: number,
maxLevel: number
): string {
const printConfig: IterablePrintConfig = {
typeName: "Set",
displayName: "Set",
leftDelim: "{",
rightDelim: "}",
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<any, any>,
ctx: ConsoleContext,
level: number,
maxLevel: number
): string {
const printConfig: IterablePrintConfig = {
typeName: "Map",
displayName: "Map",
leftDelim: "{",
rightDelim: "}",
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 = "";

Expand All @@ -71,7 +222,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)}`
);
}

Expand All @@ -90,10 +241,59 @@ function createObjectString(
return baseString;
}

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<any>, ...args);
} else if (value instanceof Map) {
// tslint:disable-next-line:no-any
return createMapString(value as Map<any, any>, ...args);
} else if (value instanceof WeakSet) {
return createWeakSetString();
} else if (value instanceof WeakMap) {
return createWeakMapString();
} else {
// Check if it is a typed array
for (const cstr of TYPEDARRAY_TYPES) {
if (value instanceof cstr) {
return createTypedArrayString(
cstr.prototype.constructor.name,
value,
...args
);
}
}

// Otherwise, default object formatting
return createRawObjectString(value, ...args);
}
}

function stringify(
ctx: ConsoleContext,
// tslint:disable-next-line:no-any
value: any,
ctx: ConsoleContext,
level: number,
maxLevel: number
): string {
Expand All @@ -118,38 +318,25 @@ 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]";
}
}

// 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 {
switch (typeof value) {
case "string":
return `"${value}"`;
default:
return stringify(ctx, value, level, maxLevel);
return stringify(value, ctx, level, maxLevel);
}
}

Expand All @@ -167,9 +354,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<any>(),
a,
0,
// tslint:disable-next-line:triple-equals
options.depth != undefined ? options.depth : DEFAULT_MAX_DEPTH
Expand Down
33 changes: 28 additions & 5 deletions js/console_test.ts
Expand Up @@ -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");
Expand All @@ -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(
Expand All @@ -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] } } } }"
);
});

Expand Down

0 comments on commit bcc1b14

Please sign in to comment.