diff options
-rw-r--r-- | cli/tests/unit/console_test.ts | 31 | ||||
-rw-r--r-- | ext/console/02_console.js | 98 |
2 files changed, 98 insertions, 31 deletions
diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts index 539c717be..af42a655b 100644 --- a/cli/tests/unit/console_test.ts +++ b/cli/tests/unit/console_test.ts @@ -225,7 +225,7 @@ Deno.test(function consoleTestStringifyCircular() { }; nestedObj.o = circularObj; - const nestedObjExpected = `{ + const nestedObjExpected = `<ref *1> { num: 1, bool: true, str: "a", @@ -245,9 +245,9 @@ Deno.test(function consoleTestStringifyCircular() { method: [Function: method], un: undefined, nu: null, - nested: [Circular], + nested: [Circular *1], emptyObj: {}, - arr: [ 1, "s", false, null, [Circular] ], + arr: [ 1, "s", false, null, [Circular *1] ], baseClass: Base { a: 1 } } }`; @@ -350,12 +350,23 @@ Deno.test(function consoleTestStringifyCircular() { return Deno.inspect(this); }, }), - "[Circular]", + "[Circular *1]", ); // test inspect is working the same assertEquals(stripColor(Deno.inspect(nestedObj)), nestedObjExpected); }); +Deno.test(function consoleTestStringifyMultipleCircular() { + const y = { a: { b: {} }, foo: { bar: {} } }; + y.a.b = y.a; + y.foo.bar = y.foo; + console.log(y); + assertEquals( + stringify(y), + "{ a: <ref *1> { b: [Circular *1] }, foo: <ref *2> { bar: [Circular *2] } }", + ); +}); + Deno.test(function consoleTestStringifyFunctionWithPrototypeRemoved() { const f = function f() {}; Reflect.setPrototypeOf(f, null); @@ -392,14 +403,14 @@ Deno.test(function consoleTestStringifyFunctionWithProperties() { assertEquals( stringify({ f }), `{ - f: [Function: f] { + f: <ref *1> [Function: f] { x: [Function], y: 3, z: [Function], b: [Function: bar], a: Map {}, - s: [Circular], - t: [Function: t] { x: [Circular] } + s: [Circular *1], + t: [Function: t] { x: [Circular *1] } } }`, ); @@ -1864,12 +1875,16 @@ Deno.test(function inspectErrorCircular() { ); assertStringIncludes( stripColor(Deno.inspect(error2)), - "Error: This is an error", + "<ref *1> Error: This is an error", ); assertStringIncludes( stripColor(Deno.inspect(error2)), "Caused by Error: This is a cause error", ); + assertStringIncludes( + stripColor(Deno.inspect(error2)), + "Caused by [Circular *1]", + ); }); Deno.test(function inspectColors() { diff --git a/ext/console/02_console.js b/ext/console/02_console.js index 4dab799b9..d3734b6a4 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -72,6 +72,7 @@ ArrayPrototypePop, ArrayPrototypeSort, ArrayPrototypeSlice, + ArrayPrototypeShift, ArrayPrototypeIncludes, ArrayPrototypeFill, ArrayPrototypeFilter, @@ -340,11 +341,17 @@ // If we didn't find any properties, we will just append an // empty suffix. let suffix = ``; + let refStr = ""; if ( ObjectKeys(value).length > 0 || ObjectGetOwnPropertySymbols(value).length > 0 ) { - const propString = inspectRawObject(value, level, inspectOptions); + const [propString, refIndex] = inspectRawObject( + value, + level, + inspectOptions, + ); + refStr = refIndex; // Filter out the empty string for the case we only have // non-enumerable symbols. if ( @@ -357,9 +364,9 @@ if (value.name && value.name !== "anonymous") { // from MDN spec - return cyan(`[${cstrName}: ${value.name}]`) + suffix; + return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; } - return cyan(`[${cstrName}]`) + suffix; + return cyan(`${refStr}[${cstrName}]`) + suffix; } function inspectIterable( @@ -589,6 +596,23 @@ return entries; } + let circular; + function handleCircular(value, cyan) { + let index = 1; + if (circular === undefined) { + circular = new Map(); + MapPrototypeSet(circular, value, index); + } else { + index = MapPrototypeGet(circular, value); + if (index === undefined) { + index = circular.size + 1; + MapPrototypeSet(circular, value, index); + } + } + // Circular string is cyan + return cyan(`[Circular *${index}]`); + } + function _inspectValue( value, level, @@ -623,7 +647,7 @@ case "function": // Function string is cyan if (ctxHas(value)) { // Circular string is cyan - return cyan("[Circular]"); + return handleCircular(value, cyan); } return inspectFunction(value, level, inspectOptions); @@ -633,8 +657,7 @@ } if (ctxHas(value)) { - // Circular string is cyan - return cyan("[Circular]"); + return handleCircular(value, cyan); } return inspectObject(value, level, inspectOptions); default: @@ -892,25 +915,41 @@ return red(RegExpPrototypeToString(value)); // RegExps are red } - function inspectError(value) { - const causes = []; + function inspectError(value, cyan) { + const causes = [value]; let err = value; - while ( - ObjectPrototypeIsPrototypeOf(ErrorPrototype, err.cause) && - err.cause !== value && - !ArrayPrototypeIncludes(causes, err.cause) // circular check - ) { - ArrayPrototypePush(causes, err.cause); - err = err.cause; + while (err.cause) { + if (ArrayPrototypeIncludes(causes, err.cause)) { + ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); + break; + } else { + ArrayPrototypePush(causes, err.cause); + err = err.cause; + } } - return `${value.stack}${ + const refMap = new Map(); + for (const cause of causes) { + if (circular !== undefined) { + const index = MapPrototypeGet(circular, cause); + if (index !== undefined) { + MapPrototypeSet(refMap, cause, cyan(`<ref *${index}> `)); + } + } + } + ArrayPrototypeShift(causes); + + return (MapPrototypeGet(refMap, value) ?? "") + value.stack + ArrayPrototypeJoin( - ArrayPrototypeMap(causes, (cause) => `\nCaused by ${cause.stack}`), + ArrayPrototypeMap( + causes, + (cause) => + "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + + (cause?.stack ?? cause), + ), "", - ) - }`; + ); } function inspectStringObject(value, inspectOptions) { @@ -1002,7 +1041,7 @@ const cyan = maybeColor(colors.cyan, inspectOptions); if (level >= inspectOptions.depth) { - return cyan("[Object]"); // wrappers are in cyan + return [cyan("[Object]"), ""]; // wrappers are in cyan } let baseString; @@ -1144,7 +1183,15 @@ baseString = `${displayName} ${baseString}`; } - return baseString; + let refIndex = ""; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, value); + if (index !== undefined) { + refIndex = `<ref *${index}> `; + } + } + + return [baseString, refIndex]; } function inspectObject( @@ -1171,7 +1218,7 @@ return String(value[privateCustomInspect](inspect)); } if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { - return inspectError(value); + return inspectError(value, maybeColor(colors.cyan, inspectOptions)); } else if (ArrayIsArray(value)) { return inspectArray(value, level, inspectOptions); } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) { @@ -1207,7 +1254,9 @@ ); } else { // Otherwise, default object formatting - return inspectRawObject(value, level, inspectOptions); + let [insp, refIndex] = inspectRawObject(value, level, inspectOptions); + insp = refIndex + insp; + return insp; } } @@ -1660,6 +1709,8 @@ } function inspectArgs(args, inspectOptions = {}) { + circular = undefined; + const noColor = colors.getNoColor(); const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; const first = args[0]; @@ -2076,6 +2127,7 @@ value, inspectOptions = {}, ) { + circular = undefined; return inspectValue(value, 0, { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions, |