summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/unit/console_test.ts31
-rw-r--r--ext/console/02_console.js98
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,