Skip to content

Commit 52d4f1f

Browse files
BridgeARtargos
authored andcommitted
util: improve function inspection
This commit contains the following changes: 1) Add null prototype support for functions. 2) Safely detect async and generator functions. 3) Mark anonymous functions as such instead of just leaving out the name. PR-URL: #27227 Backport-PR-URL: #27570 Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: John-David Dalton <[email protected]> Reviewed-By: Anto Aravinth <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent a758f9b commit 52d4f1f

7 files changed

+101
-30
lines changed

lib/internal/util/inspect.js

+30-7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const {
4949
} = require('internal/errors');
5050

5151
const {
52+
isAsyncFunction,
53+
isGeneratorFunction,
5254
isAnyArrayBuffer,
5355
isArrayBuffer,
5456
isArgumentsObject,
@@ -642,14 +644,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
642644
return `${braces[0]}}`;
643645
}
644646
} else if (typeof value === 'function') {
645-
const type = constructor || tag || 'Function';
646-
let name = `${type}`;
647-
if (value.name && typeof value.name === 'string') {
648-
name += `: ${value.name}`;
649-
}
647+
base = getFunctionBase(value, constructor, tag);
650648
if (keys.length === 0)
651-
return ctx.stylize(`[${name}]`, 'special');
652-
base = `[${name}]`;
649+
return ctx.stylize(base, 'special');
653650
} else if (isRegExp(value)) {
654651
// Make RegExps say that they are RegExps
655652
base = RegExpPrototype.toString(
@@ -834,6 +831,32 @@ function getBoxedBase(value, ctx, keys, constructor, tag) {
834831
return ctx.stylize(base, type.toLowerCase());
835832
}
836833

834+
function getFunctionBase(value, constructor, tag) {
835+
let type = 'Function';
836+
if (isAsyncFunction(value)) {
837+
type = 'AsyncFunction';
838+
} else if (isGeneratorFunction(value)) {
839+
type = 'GeneratorFunction';
840+
}
841+
let base = `[${type}`;
842+
if (constructor === null) {
843+
base += ' (null prototype)';
844+
}
845+
if (value.name === '') {
846+
base += ' (anonymous)';
847+
} else {
848+
base += `: ${value.name}`;
849+
}
850+
base += ']';
851+
if (constructor !== type && constructor !== null) {
852+
base += ` ${constructor}`;
853+
}
854+
if (tag !== '' && constructor !== tag) {
855+
base += ` [${tag}]`;
856+
}
857+
return base;
858+
}
859+
837860
function formatError(err, constructor, tag, ctx) {
838861
// TODO(BridgeAR): Always show the error code if present.
839862
let stack = err.stack || ErrorPrototype.toString(err);

test/parallel/test-assert.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ testAssertionMessage(undefined, 'undefined');
289289
testAssertionMessage(-Infinity, '-Infinity');
290290
testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]');
291291
testAssertionMessage(function f() {}, '[Function: f]');
292-
testAssertionMessage(function() {}, '[Function]');
292+
testAssertionMessage(function() {}, '[Function (anonymous)]');
293293
testAssertionMessage(circular, '{\n+ x: [Circular],\n+ y: 1\n+ }');
294294
testAssertionMessage({ a: undefined, b: null },
295295
'{\n+ a: undefined,\n+ b: null\n+ }');
@@ -597,7 +597,7 @@ assert.throws(
597597
'\n' +
598598
'+ {}\n' +
599599
'- {\n' +
600-
'- [Symbol(nodejs.util.inspect.custom)]: [Function],\n' +
600+
'- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' +
601601
"- loop: 'forever'\n" +
602602
'- }'
603603
});

test/parallel/test-console-table.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ test(undefined, 'undefined\n');
3232
test(false, 'false\n');
3333
test('hi', 'hi\n');
3434
test(Symbol(), 'Symbol()\n');
35-
test(function() {}, '[Function]\n');
35+
test(function() {}, '[Function (anonymous)]\n');
3636

3737
test([1, 2, 3], `
3838
┌─────────┬────────┐

test/parallel/test-repl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ const errorTests = [
308308
// Functions should not evaluate twice (#2773)
309309
{
310310
send: 'var I = [1,2,3,function() {}]; I.pop()',
311-
expect: '[Function]'
311+
expect: '[Function (anonymous)]'
312312
},
313313
// Multiline object
314314
{

test/parallel/test-util-format.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ assert.strictEqual(util.format('abc%', 1), 'abc% 1');
293293

294294
// Additional arguments after format specifiers
295295
assert.strictEqual(util.format('%i', 1, 'number'), '1 number');
296-
assert.strictEqual(util.format('%i', 1, () => {}), '1 [Function]');
296+
assert.strictEqual(util.format('%i', 1, () => {}), '1 [Function (anonymous)]');
297297

298298
{
299299
const o = {};
@@ -344,8 +344,8 @@ assert.strictEqual(util.format('1', '1'), '1 1');
344344
assert.strictEqual(util.format(1, '1'), '1 1');
345345
assert.strictEqual(util.format('1', 1), '1 1');
346346
assert.strictEqual(util.format(1, -0), '1 -0');
347-
assert.strictEqual(util.format('1', () => {}), '1 [Function]');
348-
assert.strictEqual(util.format(1, () => {}), '1 [Function]');
347+
assert.strictEqual(util.format('1', () => {}), '1 [Function (anonymous)]');
348+
assert.strictEqual(util.format(1, () => {}), '1 [Function (anonymous)]');
349349
assert.strictEqual(util.format('1', "'"), "1 '");
350350
assert.strictEqual(util.format(1, "'"), "1 '");
351351
assert.strictEqual(util.format('1', 'number'), '1 number');

test/parallel/test-util-inspect-proxy.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const proxy11 = new Proxy(() => {}, {
144144
return proxy11;
145145
}
146146
});
147-
const expected10 = '[Function]';
148-
const expected11 = '[Function]';
147+
const expected10 = '[Function (anonymous)]';
148+
const expected11 = '[Function (anonymous)]';
149149
assert.strictEqual(util.inspect(proxy10), expected10);
150150
assert.strictEqual(util.inspect(proxy11), expected11);

test/parallel/test-util-inspect.js

+62-14
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,51 @@ assert.strictEqual(util.inspect(1), '1');
3333
assert.strictEqual(util.inspect(false), 'false');
3434
assert.strictEqual(util.inspect(''), "''");
3535
assert.strictEqual(util.inspect('hello'), "'hello'");
36-
assert.strictEqual(util.inspect(function() {}), '[Function]');
37-
assert.strictEqual(util.inspect(() => {}), '[Function]');
38-
assert.strictEqual(util.inspect(async function() {}), '[AsyncFunction]');
39-
assert.strictEqual(util.inspect(async () => {}), '[AsyncFunction]');
40-
assert.strictEqual(util.inspect(function*() {}), '[GeneratorFunction]');
36+
assert.strictEqual(util.inspect(function abc() {}), '[Function: abc]');
37+
assert.strictEqual(util.inspect(() => {}), '[Function (anonymous)]');
38+
assert.strictEqual(
39+
util.inspect(async function() {}),
40+
'[AsyncFunction (anonymous)]'
41+
);
42+
assert.strictEqual(util.inspect(async () => {}), '[AsyncFunction (anonymous)]');
43+
44+
// Special function inspection.
45+
{
46+
const fn = (() => function*() {})();
47+
assert.strictEqual(
48+
util.inspect(fn),
49+
'[GeneratorFunction (anonymous)]'
50+
);
51+
Object.setPrototypeOf(fn, Object.getPrototypeOf(async () => {}));
52+
assert.strictEqual(
53+
util.inspect(fn),
54+
'[GeneratorFunction (anonymous)] AsyncFunction'
55+
);
56+
Object.defineProperty(fn, 'name', { value: 5, configurable: true });
57+
assert.strictEqual(
58+
util.inspect(fn),
59+
'[GeneratorFunction: 5] AsyncFunction'
60+
);
61+
Object.defineProperty(fn, Symbol.toStringTag, {
62+
value: 'Foobar',
63+
configurable: true
64+
});
65+
assert.strictEqual(
66+
util.inspect({ ['5']: fn }),
67+
"{ '5': [GeneratorFunction: 5] AsyncFunction [Foobar] }"
68+
);
69+
Object.defineProperty(fn, 'name', { value: '5', configurable: true });
70+
Object.setPrototypeOf(fn, null);
71+
assert.strictEqual(
72+
util.inspect(fn),
73+
'[GeneratorFunction (null prototype): 5] [Foobar]'
74+
);
75+
assert.strictEqual(
76+
util.inspect({ ['5']: fn }),
77+
"{ '5': [GeneratorFunction (null prototype): 5] [Foobar] }"
78+
);
79+
}
80+
4181
assert.strictEqual(util.inspect(undefined), 'undefined');
4282
assert.strictEqual(util.inspect(null), 'null');
4383
assert.strictEqual(util.inspect(/foo(bar\n)?/gi), '/foo(bar\\n)?/gi');
@@ -59,8 +99,9 @@ assert.strictEqual(util.inspect({}), '{}');
5999
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
60100
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
61101
assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }');
62-
assert.strictEqual(util.inspect({ a: async function() {} }),
63-
'{ a: [AsyncFunction: a] }');
102+
// eslint-disable-next-line func-name-matching
103+
assert.strictEqual(util.inspect({ a: async function abc() {} }),
104+
'{ a: [AsyncFunction: abc] }');
64105
assert.strictEqual(util.inspect({ a: async () => {} }),
65106
'{ a: [AsyncFunction: a] }');
66107
assert.strictEqual(util.inspect({ a: function*() {} }),
@@ -411,7 +452,10 @@ assert.strictEqual(
411452
{
412453
const value = (() => function() {})();
413454
value.aprop = 42;
414-
assert.strictEqual(util.inspect(value), '[Function] { aprop: 42 }');
455+
assert.strictEqual(
456+
util.inspect(value),
457+
'[Function (anonymous)] { aprop: 42 }'
458+
);
415459
}
416460

417461
// Regular expressions with properties.
@@ -1441,7 +1485,7 @@ util.inspect(process);
14411485
out = util.inspect(o, { compact: false, breakLength: 3 });
14421486
expect = [
14431487
'{',
1444-
' a: [Function],',
1488+
' a: [Function (anonymous)],',
14451489
' b: [Number: 3]',
14461490
'}'
14471491
].join('\n');
@@ -1450,7 +1494,7 @@ util.inspect(process);
14501494
out = util.inspect(o, { compact: false, breakLength: 3, showHidden: true });
14511495
expect = [
14521496
'{',
1453-
' a: [Function] {',
1497+
' a: [Function (anonymous)] {',
14541498
' [length]: 0,',
14551499
" [name]: ''",
14561500
' },',
@@ -1767,8 +1811,8 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
17671811
[new Number(55), '[Number: 55]'],
17681812
[Object(BigInt(55)), '[BigInt: 55n]'],
17691813
[Object(Symbol('foo')), '[Symbol: Symbol(foo)]'],
1770-
[function() {}, '[Function]'],
1771-
[() => {}, '[Function]'],
1814+
[function() {}, '[Function (anonymous)]'],
1815+
[() => {}, '[Function (anonymous)]'],
17721816
[[1, 2], '[ 1, 2 ]'],
17731817
[[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'],
17741818
[{ a: 5 }, '{ a: 5 }'],
@@ -1957,10 +2001,14 @@ assert.strictEqual(
19572001
let value = (function() { return function() {}; })();
19582002
Object.setPrototypeOf(value, null);
19592003
Object.setPrototypeOf(obj, value);
1960-
assert.strictEqual(util.inspect(obj), '<[Function]> { a: true }');
2004+
assert.strictEqual(
2005+
util.inspect(obj),
2006+
'<[Function (null prototype) (anonymous)]> { a: true }'
2007+
);
19612008
assert.strictEqual(
19622009
util.inspect(obj, { colors: true }),
1963-
'<\u001b[36m[Function]\u001b[39m> { a: \u001b[33mtrue\u001b[39m }'
2010+
'<\u001b[36m[Function (null prototype) (anonymous)]\u001b[39m> ' +
2011+
'{ a: \u001b[33mtrue\u001b[39m }'
19642012
);
19652013

19662014
obj = { a: true };

0 commit comments

Comments
 (0)