diff --git a/Makefile b/Makefile index 6fa145e226..d01d4bf132 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ TS_FILES = \ + console.ts \ deno.d.ts \ deno.ts \ dispatch.ts \ diff --git a/console.ts b/console.ts new file mode 100644 index 0000000000..0d1585bc89 --- /dev/null +++ b/console.ts @@ -0,0 +1,123 @@ +const print = V8Worker2.print; + +// tslint:disable-next-line:no-any +type ConsoleContext = Set; + +// tslint:disable-next-line:no-any +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" + } + return ""; +} + +// tslint:disable-next-line:no-any +function stringify(ctx: ConsoleContext, value: any): string { + switch (typeof value) { + case "string": + return value; + case "number": + case "boolean": + case "undefined": + case "symbol": + return String(value); + case "function": + if (value.name && value.name !== "anonymous") { // from MDN spec + return `[Function: ${value.name}]`; + } + return "[Function]"; + case "object": + if (value === null) { + return "null"; + } + + if (ctx.has(value)) { + return "[Circular]"; + } + + ctx.add(value); + const entries: string[] = []; + + if (Array.isArray(value)) { + for (const el of value) { + entries.push(stringify(ctx, el)); + } + + ctx.delete(value); + + if (entries.length === 0) { + return "[]"; + } + return `[ ${entries.join(", ")} ]`; + } 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}: ${stringify(ctx, value[key])}`); + } + + ctx.delete(value); + + if (entries.length === 0) { + baseString = "{}"; + } else { + baseString = `{ ${entries.join(", ")} }`; + } + + if (shouldShowClassName) { + baseString = `${className} ${baseString}`; + } + + return baseString; + } + default: + return "[Not Implemented]"; + } +} + +// tslint:disable-next-line:no-any +function stringifyArgs(args: any[]): string { + const out: string[] = []; + for (const a of args) { + if (typeof a === "string") { + out.push(a); + } else { + // tslint:disable-next-line:no-any + out.push(stringify(new Set(), a)); + } + } + return out.join(" "); +} + +export class Console { + // tslint:disable-next-line:no-any + log(...args: any[]): void { + print(stringifyArgs(args)); + } + + debug = this.log; + info = this.log; + + // tslint:disable-next-line:no-any + warn(...args: any[]): void { + print(`ERROR: ${stringifyArgs(args)}`); + } + + error = this.warn; + + // tslint:disable-next-line:no-any + assert(condition: boolean, ...args: any[]): void { + if (!condition) { + throw new Error(`Assertion failed: ${stringifyArgs(args)}`); + } + } +} diff --git a/globals.ts b/globals.ts index 896c2a0eb3..cca72d172d 100644 --- a/globals.ts +++ b/globals.ts @@ -21,39 +21,8 @@ _global["setInterval"] = timer.setInterval; _global["clearTimeout"] = timer.clearTimer; _global["clearInterval"] = timer.clearTimer; -const print = V8Worker2.print; - -_global["console"] = { - // tslint:disable-next-line:no-any - log(...args: any[]): void { - print(stringifyArgs(args)); - }, - - // tslint:disable-next-line:no-any - error(...args: any[]): void { - print(`ERROR: ${stringifyArgs(args)}`); - }, - - // tslint:disable-next-line:no-any - assert(condition: boolean, ...args: any[]): void { - if (!condition) { - throw new Error(`Assertion failed: ${stringifyArgs(args)}`); - } - } -}; - -// tslint:disable-next-line:no-any -function stringifyArgs(args: any[]): string { - const out: string[] = []; - for (const a of args) { - if (typeof a === "string") { - out.push(a); - } else { - out.push(JSON.stringify(a)); - } - } - return out.join(" "); -} +import { Console } from "./console"; +_global["console"] = new Console(); import { fetch } from "./fetch"; _global["fetch"] = fetch; diff --git a/tests.ts b/tests.ts index 406ac76e86..f1412e1527 100644 --- a/tests.ts +++ b/tests.ts @@ -55,3 +55,74 @@ test(async function tests_writeFileSync() { const actual = dec.decode(dataRead); assertEqual("Hello", actual); }); + +test(function tests_console_assert() { + console.assert(true); + + let hasThrown = false; + try { + console.assert(false); + } catch { + hasThrown = true; + } + assertEqual(hasThrown, true); +}); + +test(function tests_console_stringify_circular() { + class Base { + a = 1; + m1() {} + } + + class Extended extends Base { + b = 2; + m2() {} + } + + // tslint:disable-next-line:no-any + const nestedObj: any = { + num: 1, + bool: true, + str: "a", + method() {}, + un: undefined, + nu: null, + arrowFunc: () => {}, + extendedClass: new Extended(), + nFunc: new Function(), + extendedCstr: Extended, + }; + + const circularObj = { + num: 2, + bool: false, + str: "b", + method() {}, + un: undefined, + nu: null, + nested: nestedObj, + emptyObj: {}, + arr: [1, "s", false, null, nestedObj], + baseClass: new Base(), + }; + + nestedObj.o = circularObj; + + try { + console.log(1); + console.log("s"); + console.log(false); + console.log(Symbol(1)); + console.log(null); + console.log(undefined); + console.log(new Extended()); + console.log(function f() {}); + console.log(nestedObj); + console.log(JSON); + console.log(console); + } catch { + throw new Error( + "Expected no crash on circular object" + ); + } +});