Skip to content

Commit 7f3838b

Browse files
BridgeARjasnell
authored andcommitted
util: improve inspect performance
This improves a slow code part in `util.inspect` by directly retrieving the `Symbol.toStringTag` and by optimizing some code paths. PR-URL: #20009 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yuta Hiroto <hello@hiroppy.me> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 8a5b7b2 commit 7f3838b

File tree

2 files changed

+53
-59
lines changed

2 files changed

+53
-59
lines changed

lib/internal/util.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -252,43 +252,6 @@ function getSystemErrorName(err) {
252252
return entry ? entry[0] : `Unknown system error ${err}`;
253253
}
254254

255-
// getConstructorOf is wrapped into this to save iterations
256-
function getIdentificationOf(obj) {
257-
const original = obj;
258-
let constructor;
259-
let tag;
260-
261-
while (obj) {
262-
if (constructor === undefined) {
263-
const desc = Object.getOwnPropertyDescriptor(obj, 'constructor');
264-
if (desc !== undefined &&
265-
typeof desc.value === 'function' &&
266-
desc.value.name !== '')
267-
constructor = desc.value.name;
268-
}
269-
270-
if (tag === undefined) {
271-
const desc = Object.getOwnPropertyDescriptor(obj, Symbol.toStringTag);
272-
if (desc !== undefined) {
273-
if (typeof desc.value === 'string') {
274-
tag = desc.value;
275-
} else if (desc.get !== undefined) {
276-
tag = desc.get.call(original);
277-
if (typeof tag !== 'string')
278-
tag = undefined;
279-
}
280-
}
281-
}
282-
283-
if (constructor !== undefined && tag !== undefined)
284-
break;
285-
286-
obj = Object.getPrototypeOf(obj);
287-
}
288-
289-
return { constructor, tag };
290-
}
291-
292255
const kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
293256
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
294257

@@ -431,7 +394,6 @@ module.exports = {
431394
filterDuplicateStrings,
432395
getConstructorOf,
433396
getSystemErrorName,
434-
getIdentificationOf,
435397
isError,
436398
isInsideNodeModules,
437399
join,

lib/util.js

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ const {
7272
customInspectSymbol,
7373
deprecate,
7474
getSystemErrorName: internalErrorName,
75-
getIdentificationOf,
7675
isError,
7776
promisify,
7877
join,
@@ -397,6 +396,35 @@ function stylizeNoColor(str, styleType) {
397396
return str;
398397
}
399398

399+
function getConstructorName(obj) {
400+
while (obj) {
401+
const descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
402+
if (descriptor !== undefined &&
403+
typeof descriptor.value === 'function' &&
404+
descriptor.value.name !== '') {
405+
return descriptor.value.name;
406+
}
407+
408+
obj = Object.getPrototypeOf(obj);
409+
}
410+
411+
return '';
412+
}
413+
414+
function getPrefix(constructor, tag) {
415+
if (constructor !== '') {
416+
if (tag !== '' && constructor !== tag) {
417+
return `${constructor} [${tag}] `;
418+
}
419+
return `${constructor} `;
420+
}
421+
422+
if (tag !== '')
423+
return `[${tag}] `;
424+
425+
return '';
426+
}
427+
400428
function formatValue(ctx, value, recurseTimes, ln) {
401429
// Primitive types cannot have properties
402430
if (typeof value !== 'object' && typeof value !== 'function') {
@@ -476,15 +504,10 @@ function formatValue(ctx, value, recurseTimes, ln) {
476504

477505
const keyLength = keys.length + symbols.length;
478506

479-
const { constructor, tag } = getIdentificationOf(value);
480-
let prefix = '';
481-
if (constructor && tag && constructor !== tag)
482-
prefix = `${constructor} [${tag}] `;
483-
else if (constructor)
484-
prefix = `${constructor} `;
485-
else if (tag)
486-
prefix = `[${tag}] `;
487-
507+
const constructor = getConstructorName(value);
508+
let tag = value[Symbol.toStringTag];
509+
if (typeof tag !== 'string')
510+
tag = '';
488511
let base = '';
489512
let formatter = formatObject;
490513
let braces;
@@ -497,22 +520,25 @@ function formatValue(ctx, value, recurseTimes, ln) {
497520
noIterator = false;
498521
if (Array.isArray(value)) {
499522
// Only set the constructor for non ordinary ("Array [...]") arrays.
523+
const prefix = getPrefix(constructor, tag);
500524
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
501525
if (value.length === 0 && keyLength === 0)
502526
return `${braces[0]}]`;
503527
formatter = formatArray;
504528
} else if (isSet(value)) {
529+
const prefix = getPrefix(constructor, tag);
505530
if (value.size === 0 && keyLength === 0)
506531
return `${prefix}{}`;
507532
braces = [`${prefix}{`, '}'];
508533
formatter = formatSet;
509534
} else if (isMap(value)) {
535+
const prefix = getPrefix(constructor, tag);
510536
if (value.size === 0 && keyLength === 0)
511537
return `${prefix}{}`;
512538
braces = [`${prefix}{`, '}'];
513539
formatter = formatMap;
514540
} else if (isTypedArray(value)) {
515-
braces = [`${prefix}[`, ']'];
541+
braces = [`${getPrefix(constructor, tag)}[`, ']'];
516542
formatter = formatTypedArray;
517543
} else if (isMapIterator(value)) {
518544
braces = [`[${tag}] {`, '}'];
@@ -544,11 +570,16 @@ function formatValue(ctx, value, recurseTimes, ln) {
544570
}
545571
if (noIterator) {
546572
braces = ['{', '}'];
547-
if (prefix === 'Object ') {
573+
if (constructor === 'Object') {
548574
if (isArgumentsObject(value)) {
549575
braces[0] = '[Arguments] {';
550576
if (keyLength === 0)
551577
return '[Arguments] {}';
578+
} else if (tag !== '') {
579+
braces[0] = `${getPrefix(constructor, tag)}{`;
580+
if (keyLength === 0) {
581+
return `${braces[0]}}`;
582+
}
552583
} else if (keyLength === 0) {
553584
return '{}';
554585
}
@@ -580,27 +611,28 @@ function formatValue(ctx, value, recurseTimes, ln) {
580611
// Fast path for ArrayBuffer and SharedArrayBuffer.
581612
// Can't do the same for DataView because it has a non-primitive
582613
// .buffer property that we need to recurse for.
614+
const prefix = getPrefix(constructor, tag);
583615
if (keyLength === 0)
584616
return prefix +
585617
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
586618
braces[0] = `${prefix}{`;
587619
keys.unshift('byteLength');
588620
} else if (isDataView(value)) {
589-
braces[0] = `${prefix}{`;
621+
braces[0] = `${getPrefix(constructor, tag)}{`;
590622
// .buffer goes last, it's not a primitive like the others.
591623
keys.unshift('byteLength', 'byteOffset', 'buffer');
592624
} else if (isPromise(value)) {
593-
braces[0] = `${prefix}{`;
625+
braces[0] = `${getPrefix(constructor, tag)}{`;
594626
formatter = formatPromise;
595627
} else if (isWeakSet(value)) {
596-
braces[0] = `${prefix}{`;
628+
braces[0] = `${getPrefix(constructor, tag)}{`;
597629
if (ctx.showHidden) {
598630
formatter = formatWeakSet;
599631
} else {
600632
extra = '[items unknown]';
601633
}
602634
} else if (isWeakMap(value)) {
603-
braces[0] = `${prefix}{`;
635+
braces[0] = `${getPrefix(constructor, tag)}{`;
604636
if (ctx.showHidden) {
605637
formatter = formatWeakMap;
606638
} else {
@@ -639,9 +671,9 @@ function formatValue(ctx, value, recurseTimes, ln) {
639671
} else if (keyLength === 0) {
640672
if (isExternal(value))
641673
return ctx.stylize('[External]', 'special');
642-
return `${prefix}{}`;
674+
return `${getPrefix(constructor, tag)}{}`;
643675
} else {
644-
braces[0] = `${prefix}{`;
676+
braces[0] = `${getPrefix(constructor, tag)}{`;
645677
}
646678
}
647679
}
@@ -676,8 +708,8 @@ function formatNumber(fn, value) {
676708
function formatPrimitive(fn, value, ctx) {
677709
if (typeof value === 'string') {
678710
if (ctx.compact === false &&
679-
value.length > MIN_LINE_LENGTH &&
680-
ctx.indentationLvl + value.length > ctx.breakLength) {
711+
ctx.indentationLvl + value.length > ctx.breakLength &&
712+
value.length > MIN_LINE_LENGTH) {
681713
// eslint-disable-next-line max-len
682714
const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH);
683715
// eslint-disable-next-line max-len
@@ -696,9 +728,9 @@ function formatPrimitive(fn, value, ctx) {
696728
// eslint-disable-next-line max-len, node-core/no-unescaped-regexp-dot
697729
readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm');
698730
}
699-
const indent = ' '.repeat(ctx.indentationLvl);
700731
const matches = value.match(readableRegExps[divisor]);
701732
if (matches.length > 1) {
733+
const indent = ' '.repeat(ctx.indentationLvl);
702734
res += `${fn(strEscape(matches[0]), 'string')} +\n`;
703735
for (var i = 1; i < matches.length - 1; i++) {
704736
res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`;

0 commit comments

Comments
 (0)