Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: improve inspect #20802

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 60 additions & 43 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ function getPrefix(constructor, tag) {
return '';
}

function formatValue(ctx, value, recurseTimes, ln) {
function formatValue(ctx, value, recurseTimes) {
// Primitive types cannot have properties
if (typeof value !== 'object' && typeof value !== 'function') {
return formatPrimitive(ctx.stylize, value, ctx);
Expand Down Expand Up @@ -582,12 +582,27 @@ function formatValue(ctx, value, recurseTimes, ln) {
return ctx.stylize(dateToISOString.call(value), 'date');
}
// Make dates with properties first say the date
base = `${dateToISOString.call(value)}`;
base = dateToISOString.call(value);
} else if (isError(value)) {
// Make error with message first say the error
base = formatError(value);
// Wrap the error in brackets in case it has no stack trace.
const stackStart = base.indexOf('\n at');
if (stackStart === -1) {
base = `[${base}]`;
}
// The message and the stack have to be indented as well!
if (ctx.indentationLvl !== 0) {
const indentation = ' '.repeat(ctx.indentationLvl);
base = formatError(value).replace(/\n/g, `\n${indentation}`);
}
if (keyLength === 0)
return formatError(value);
base = `${formatError(value)}`;
return base;

if (ctx.compact === false && stackStart !== -1) {
braces[0] += `${base.slice(stackStart)}`;
base = `[${base.slice(0, stackStart)}]`;
}
} else if (isAnyArrayBuffer(value)) {
// Fast path for ArrayBuffer and SharedArrayBuffer.
// Can't do the same for DataView because it has a non-primitive
Expand Down Expand Up @@ -679,7 +694,7 @@ function formatValue(ctx, value, recurseTimes, ln) {
}
ctx.seen.pop();

return reduceToSingleString(ctx, output, base, braces, ln);
return reduceToSingleString(ctx, output, base, braces);
}

function formatNumber(fn, value) {
Expand Down Expand Up @@ -739,7 +754,7 @@ function formatPrimitive(fn, value, ctx) {
}

function formatError(value) {
return value.stack || `[${errorToString.call(value)}]`;
return value.stack || errorToString.call(value);
}

function formatObject(ctx, value, recurseTimes, keys) {
Expand All @@ -754,7 +769,23 @@ function formatNamespaceObject(ctx, value, recurseTimes, keys) {
const len = keys.length;
const output = new Array(len);
for (var i = 0; i < len; i++) {
output[i] = formatNamespaceProperty(ctx, value, recurseTimes, keys[i]);
try {
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0);
} catch (err) {
if (!(err instanceof ReferenceError)) {
throw err;
}
// Use the existing functionality. This makes sure the indentation and
// line breaks are always correct. Otherwise it is very difficult to keep
// this aligned, even though this is a hacky way of dealing with this.
const tmp = { [keys[i]]: '' };
output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], 0);
const pos = output[i].lastIndexOf(' ');
// We have to find the last whitespace and have to replace that value as
// it will be visualized as a regular string.
output[i] = output[i].slice(0, pos + 1) +
ctx.stylize('<uninitialized>', 'special');
}
}
return output;
}
Expand Down Expand Up @@ -982,42 +1013,21 @@ function formatPromise(ctx, value, recurseTimes, keys) {
return output;
}

function formatKey(ctx, key, enumerable) {
if (typeof key === 'symbol') {
return `[${ctx.stylize(key.toString(), 'symbol')}]`;
}
if (enumerable === false) {
return `[${key}]`;
}
if (keyStrRegExp.test(key)) {
return ctx.stylize(key, 'name');
}
return ctx.stylize(strEscape(key), 'string');
}

function formatNamespaceProperty(ctx, ns, recurseTimes, key) {
let value;
try {
value = formatValue(ctx, ns[key], recurseTimes, true);
} catch (err) {
if (err instanceof ReferenceError) {
value = ctx.stylize('<uninitialized>', 'special');
} else {
throw err;
}
}

return `${formatKey(ctx, key)}: ${value}`;
}

function formatProperty(ctx, value, recurseTimes, key, array) {
let str;
let name, str;
let extra = ' ';
const desc = Object.getOwnPropertyDescriptor(value, key) ||
{ value: value[key], enumerable: true };
if (desc.value !== undefined) {
const diff = array !== 0 || ctx.compact === false ? 2 : 3;
ctx.indentationLvl += diff;
str = formatValue(ctx, desc.value, recurseTimes, array === 0);
str = formatValue(ctx, desc.value, recurseTimes);
if (diff === 3) {
const len = ctx.colors ? removeColors(str).length : str.length;
if (ctx.breakLength < len) {
extra = `\n${' '.repeat(ctx.indentationLvl)}`;
}
}
ctx.indentationLvl -= diff;
} else if (desc.get !== undefined) {
if (desc.set !== undefined) {
Expand All @@ -1033,11 +1043,19 @@ function formatProperty(ctx, value, recurseTimes, key, array) {
if (array === 1) {
return str;
}

return `${formatKey(ctx, key, desc.enumerable)}: ${str}`;
if (typeof key === 'symbol') {
name = `[${ctx.stylize(key.toString(), 'symbol')}]`;
} else if (desc.enumerable === false) {
name = `[${key}]`;
} else if (keyStrRegExp.test(key)) {
name = ctx.stylize(key, 'name');
} else {
name = ctx.stylize(strEscape(key), 'string');
}
return `${name}:${extra}${str}`;
}

function reduceToSingleString(ctx, output, base, braces, addLn) {
function reduceToSingleString(ctx, output, base, braces) {
const breakLength = ctx.breakLength;
let i = 0;
if (ctx.compact === false) {
Expand Down Expand Up @@ -1066,11 +1084,10 @@ function reduceToSingleString(ctx, output, base, braces, addLn) {
// we need to force the first item to be on the next line or the
// items will not line up correctly.
const indentation = ' '.repeat(ctx.indentationLvl);
const extraLn = addLn === true ? `\n${indentation}` : '';
const ln = base === '' && braces[0].length === 1 ?
' ' : `${base ? ` ${base}` : base}\n${indentation} `;
' ' : `${base ? ` ${base}` : ''}\n${indentation} `;
const str = join(output, `,\n${indentation} `);
return `${extraLn}${braces[0]}${ln}${str} ${braces[1]}`;
return `${braces[0]}${ln}${str} ${braces[1]}`;
}

function isBoolean(arg) {
Expand Down
12 changes: 12 additions & 0 deletions test/message/util_inspect_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

require('../common');
const util = require('util');

const err = new Error('foo\nbar');

console.log(util.inspect({ err, nested: { err } }, { compact: true }));
console.log(util.inspect({ err, nested: { err } }, { compact: false }));

err.foo = 'bar';
console.log(util.inspect(err, { compact: true, breakLength: 5 }));
63 changes: 63 additions & 0 deletions test/message/util_inspect_error.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{ err:
Error: foo
bar
at *util_inspect_error*
at *
at *
at *
at *
at *
at *
at *
at *
nested:
{ err:
Error: foo
bar
at *util_inspect_error*
at *
at *
at *
at *
at *
at *
at *
at * } }
{
err: Error: foo
bar
at *util_inspect_error*
at *
at *
at *
at *
at *
at *
at *
at *,
nested: {
err: Error: foo
bar
at *util_inspect_error*
at *
at *
at *
at *
at *
at *
at *
at *
}
}
{ Error: foo
bar
at *util_inspect_error*
at *
at *
at *
at *
at *
at *
at *
at *
foo: 'bar' }
2 changes: 1 addition & 1 deletion test/parallel/test-repl-underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ function testError() {

// The error, both from the original throw and the `_error` echo.
'Error: foo',
'Error: foo',
'[Error: foo]',

// The sync error, with individual property echoes
/Error: ENOENT: no such file or directory, scandir '.*nonexistent.*'/,
Expand Down
16 changes: 8 additions & 8 deletions test/parallel/test-util-format.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ assert.strictEqual(
util.format('%o', obj),
'{ foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' func:\n' +
' { [Function: func]\n' +
' [length]: 0,\n' +
' [name]: \'func\',\n' +
Expand All @@ -135,8 +135,8 @@ assert.strictEqual(
util.format('%o', nestedObj2),
'{ foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' [ { a: \n' +
' func:\n' +
' [ { a:\n' +
' { [Function: a]\n' +
' [length]: 0,\n' +
' [name]: \'a\',\n' +
Expand All @@ -145,9 +145,9 @@ assert.strictEqual(
assert.strictEqual(
util.format('%o', nestedObj),
'{ foo: \'bar\',\n' +
' foobar: \n' +
' foobar:\n' +
' { foo: \'bar\',\n' +
' func: \n' +
' func:\n' +
' { [Function: func]\n' +
' [length]: 0,\n' +
' [name]: \'func\',\n' +
Expand All @@ -156,14 +156,14 @@ assert.strictEqual(
util.format('%o %o', obj, obj),
'{ foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' func:\n' +
' { [Function: func]\n' +
' [length]: 0,\n' +
' [name]: \'func\',\n' +
' [prototype]: func { [constructor]: [Circular] } } }' +
' { foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' func:\n' +
' { [Function: func]\n' +
' [length]: 0,\n' +
' [name]: \'func\',\n' +
Expand All @@ -172,7 +172,7 @@ assert.strictEqual(
util.format('%o %o', obj),
'{ foo: \'bar\',\n' +
' foobar: 1,\n' +
' func: \n' +
' func:\n' +
' { [Function: func]\n' +
' [length]: 0,\n' +
' [name]: \'func\',\n' +
Expand Down
Loading