util: refactor inspect for performance and more
The main optimizations are - Removed visibleKeys - Removed proxy cache - Removed Object.assign - No key concatenating anymore - No key recalculating anymore - Improved indentation logic - Improved string escape logic - Added many fast paths - Optimized code branches a lot - Optimized (boxed) primitive handling - Inline code if possible - Only check extra keys if necessary - Guard against unnecessary more expensive calls This also fixes a bug with special array number keys as e.g. "00". Besides that there were lots of smaller optimizations, the code got a bit cleaned up and a few more tests got in. PR-URL: https://github.com/nodejs/node/pull/14881 Fixes: https://github.com/nodejs/node/issues/15288 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
parent
01652ccc68
commit
f9ad23dc91
923
lib/util.js
923
lib/util.js
File diff suppressed because it is too large
Load Diff
@ -114,6 +114,11 @@ const nestedObj = {
|
|||||||
func: function() {}
|
func: function() {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const nestedObj2 = {
|
||||||
|
foo: 'bar',
|
||||||
|
foobar: 1,
|
||||||
|
func: [{ a: function() {} }]
|
||||||
|
};
|
||||||
assert.strictEqual(util.format('%o'), '%o');
|
assert.strictEqual(util.format('%o'), '%o');
|
||||||
assert.strictEqual(util.format('%o', 42), '42');
|
assert.strictEqual(util.format('%o', 42), '42');
|
||||||
assert.strictEqual(util.format('%o', 'foo'), '\'foo\'');
|
assert.strictEqual(util.format('%o', 'foo'), '\'foo\'');
|
||||||
@ -126,6 +131,17 @@ assert.strictEqual(
|
|||||||
' [length]: 0,\n' +
|
' [length]: 0,\n' +
|
||||||
' [name]: \'func\',\n' +
|
' [name]: \'func\',\n' +
|
||||||
' [prototype]: func { [constructor]: [Circular] } } }');
|
' [prototype]: func { [constructor]: [Circular] } } }');
|
||||||
|
assert.strictEqual(
|
||||||
|
util.format('%o', nestedObj2),
|
||||||
|
'{ foo: \'bar\',\n' +
|
||||||
|
' foobar: 1,\n' +
|
||||||
|
' func: \n' +
|
||||||
|
' [ { a: \n' +
|
||||||
|
' { [Function: a]\n' +
|
||||||
|
' [length]: 0,\n' +
|
||||||
|
' [name]: \'a\',\n' +
|
||||||
|
' [prototype]: a { [constructor]: [Circular] } } },\n' +
|
||||||
|
' [length]: 1 ] }');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
util.format('%o', nestedObj),
|
util.format('%o', nestedObj),
|
||||||
'{ foo: \'bar\',\n' +
|
'{ foo: \'bar\',\n' +
|
||||||
|
@ -55,7 +55,9 @@ const expected6 = 'Proxy [ Proxy [ Proxy [ Proxy [Array], Proxy [Array]' +
|
|||||||
' ],\n Proxy [ Proxy [Array], Proxy [Array] ] ],\n' +
|
' ],\n Proxy [ Proxy [Array], Proxy [Array] ] ],\n' +
|
||||||
' Proxy [ Proxy [ Proxy [Array], Proxy [Array] ],\n' +
|
' Proxy [ Proxy [ Proxy [Array], Proxy [Array] ],\n' +
|
||||||
' Proxy [ Proxy [Array], Proxy [Array] ] ] ]';
|
' Proxy [ Proxy [Array], Proxy [Array] ] ] ]';
|
||||||
assert.strictEqual(util.inspect(proxy1, opts), expected1);
|
assert.strictEqual(
|
||||||
|
util.inspect(proxy1, { showProxy: true, depth: null }),
|
||||||
|
expected1);
|
||||||
assert.strictEqual(util.inspect(proxy2, opts), expected2);
|
assert.strictEqual(util.inspect(proxy2, opts), expected2);
|
||||||
assert.strictEqual(util.inspect(proxy3, opts), expected3);
|
assert.strictEqual(util.inspect(proxy3, opts), expected3);
|
||||||
assert.strictEqual(util.inspect(proxy4, opts), expected4);
|
assert.strictEqual(util.inspect(proxy4, opts), expected4);
|
||||||
|
@ -43,14 +43,15 @@ assert.strictEqual(
|
|||||||
new Date('2010-02-14T12:48:40+01:00').toISOString()
|
new Date('2010-02-14T12:48:40+01:00').toISOString()
|
||||||
);
|
);
|
||||||
assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString());
|
assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString());
|
||||||
|
|
||||||
assert.strictEqual(util.inspect('\n\u0001'), "'\\n\\u0001'");
|
assert.strictEqual(util.inspect('\n\u0001'), "'\\n\\u0001'");
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(`${Array(75).fill(1)}'\n\u001d\n\u0003`),
|
||||||
|
`'${Array(75).fill(1)}\\'\\n\\u001d\\n\\u0003'`
|
||||||
|
);
|
||||||
assert.strictEqual(util.inspect([]), '[]');
|
assert.strictEqual(util.inspect([]), '[]');
|
||||||
assert.strictEqual(util.inspect(Object.create([])), 'Array {}');
|
assert.strictEqual(util.inspect(Object.create([])), 'Array {}');
|
||||||
assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');
|
assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');
|
||||||
assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');
|
assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');
|
||||||
|
|
||||||
assert.strictEqual(util.inspect({}), '{}');
|
assert.strictEqual(util.inspect({}), '{}');
|
||||||
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
|
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
|
||||||
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
|
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
|
||||||
@ -76,6 +77,7 @@ assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1),
|
|||||||
'{ a: { b: [Object] } }');
|
'{ a: { b: [Object] } }');
|
||||||
assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1),
|
assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1),
|
||||||
'{ a: { b: [Array] } }');
|
'{ a: { b: [Array] } }');
|
||||||
|
assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array [ ]');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
util.inspect(
|
util.inspect(
|
||||||
Object.create(
|
Object.create(
|
||||||
@ -327,6 +329,38 @@ assert.strictEqual(
|
|||||||
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
|
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Array with extra properties
|
||||||
|
{
|
||||||
|
const arr = [1, 2, 3, , ];
|
||||||
|
arr.foo = 'bar';
|
||||||
|
assert.strictEqual(util.inspect(arr),
|
||||||
|
"[ 1, 2, 3, <1 empty item>, foo: 'bar' ]");
|
||||||
|
|
||||||
|
const arr2 = [];
|
||||||
|
assert.strictEqual(util.inspect([], { showHidden: true }), '[ [length]: 0 ]');
|
||||||
|
arr2['00'] = 1;
|
||||||
|
assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]");
|
||||||
|
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
|
||||||
|
"[ [length]: 0, '00': 1 ]");
|
||||||
|
arr2[1] = 0;
|
||||||
|
assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]");
|
||||||
|
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
|
||||||
|
"[ <1 empty item>, 0, [length]: 2, '00': 1 ]");
|
||||||
|
delete arr2[1];
|
||||||
|
assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]");
|
||||||
|
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
|
||||||
|
"[ <2 empty items>, [length]: 2, '00': 1 ]");
|
||||||
|
arr2['01'] = 2;
|
||||||
|
assert.strictEqual(util.inspect(arr2),
|
||||||
|
"[ <2 empty items>, '00': 1, '01': 2 ]");
|
||||||
|
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
|
||||||
|
"[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]");
|
||||||
|
|
||||||
|
const arr3 = [];
|
||||||
|
arr3[-1] = -1;
|
||||||
|
assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]");
|
||||||
|
}
|
||||||
|
|
||||||
// Function with properties
|
// Function with properties
|
||||||
{
|
{
|
||||||
const value = () => {};
|
const value = () => {};
|
||||||
@ -387,6 +421,11 @@ assert.strictEqual(util.inspect(-0), '-0');
|
|||||||
util.inspect(a, { breakLength: Infinity }),
|
util.inspect(a, { breakLength: Infinity }),
|
||||||
'[ \'foo\', <1 empty item>, \'baz\', \'bar\', <96 empty items>, \'qux\' ]'
|
'[ \'foo\', <1 empty item>, \'baz\', \'bar\', <96 empty items>, \'qux\' ]'
|
||||||
);
|
);
|
||||||
|
delete a[3];
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(a, { maxArrayLength: 4 }),
|
||||||
|
'[ \'foo\', <1 empty item>, \'baz\', <97 empty items>, ... 1 more item ]'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test for Array constructor in different context
|
// test for Array constructor in different context
|
||||||
@ -604,6 +643,10 @@ assert.doesNotThrow(() => {
|
|||||||
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
|
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'),
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -714,6 +757,7 @@ assert.doesNotThrow(() => {
|
|||||||
testLines([1, 2, 3, 4, 5, 6, 7]);
|
testLines([1, 2, 3, 4, 5, 6, 7]);
|
||||||
testLines(bigArray);
|
testLines(bigArray);
|
||||||
testLines({ foo: 'bar', baz: 35, b: { a: 35 } });
|
testLines({ foo: 'bar', baz: 35, b: { a: 35 } });
|
||||||
|
testLines({ a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 }, b: 1 });
|
||||||
testLines({
|
testLines({
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
baz: 35,
|
baz: 35,
|
||||||
@ -941,6 +985,20 @@ if (typeof Symbol !== 'undefined') {
|
|||||||
'PromiseSubclass { <pending> }');
|
'PromiseSubclass { <pending> }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty and circular before depth
|
||||||
|
{
|
||||||
|
const arr = [[[[]]]];
|
||||||
|
assert.strictEqual(util.inspect(arr), '[ [ [ [] ] ] ]');
|
||||||
|
arr[0][0][0][0] = [];
|
||||||
|
assert.strictEqual(util.inspect(arr), '[ [ [ [Array] ] ] ]');
|
||||||
|
arr[0][0][0] = {};
|
||||||
|
assert.strictEqual(util.inspect(arr), '[ [ [ {} ] ] ]');
|
||||||
|
arr[0][0][0] = { a: 2 };
|
||||||
|
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
|
||||||
|
arr[0][0][0] = arr;
|
||||||
|
assert.strictEqual(util.inspect(arr), '[ [ [ [Circular] ] ] ]');
|
||||||
|
}
|
||||||
|
|
||||||
// Corner cases.
|
// Corner cases.
|
||||||
{
|
{
|
||||||
const x = { constructor: 42 };
|
const x = { constructor: 42 };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user