0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

feat(ext/console): better circular information in object inspection (#13555)

This commit is contained in:
Leo Kettmeir 2022-02-04 21:10:47 +01:00 committed by GitHub
parent 3ec07f4e06
commit 2f438f4106
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 31 deletions

View file

@ -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() {

View file

@ -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,