@@ -450,14 +450,20 @@ function getEmptyFormatArray() { |
450 | 450 | return []; |
451 | 451 | } |
452 | 452 |
|
453 | | -function getConstructorName(obj, ctx, recurseTimes) { |
| 453 | +function getConstructorName(obj, ctx, recurseTimes, protoProps) { |
454 | 454 | let firstProto; |
455 | 455 | const tmp = obj; |
456 | 456 | while (obj) { |
457 | 457 | const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor'); |
458 | 458 | if (descriptor !== undefined && |
459 | 459 | typeof descriptor.value === 'function' && |
460 | 460 | descriptor.value.name !== '') { |
| 461 | + if (protoProps !== undefined && |
| 462 | + !builtInObjects.has(descriptor.value.name)) { |
| 463 | + const isProto = firstProto !== undefined; |
| 464 | + addPrototypeProperties( |
| 465 | + ctx, tmp, obj, recurseTimes, isProto, protoProps); |
| 466 | + } |
461 | 467 | return descriptor.value.name; |
462 | 468 | } |
463 | 469 |
|
@@ -477,7 +483,8 @@ function getConstructorName(obj, ctx, recurseTimes) { |
477 | 483 | return `${res} <Complex prototype>`; |
478 | 484 | } |
479 | 485 |
|
480 | | - const protoConstr = getConstructorName(firstProto, ctx, recurseTimes + 1); |
| 486 | + const protoConstr = getConstructorName( |
| 487 | + firstProto, ctx, recurseTimes + 1, protoProps); |
481 | 488 |
|
482 | 489 | if (protoConstr === null) { |
483 | 490 | return `${res} <${inspect(firstProto, { |
@@ -490,6 +497,68 @@ function getConstructorName(obj, ctx, recurseTimes) { |
490 | 497 | return `${res} <${protoConstr}>`; |
491 | 498 | } |
492 | 499 |
|
| 500 | +// This function has the side effect of adding prototype properties to the |
| 501 | +// `output` argument (which is an array). This is intended to highlight user |
| 502 | +// defined prototype properties. |
| 503 | +function addPrototypeProperties(ctx, main, obj, recurseTimes, isProto, output) { |
| 504 | + let depth = 0; |
| 505 | + let keys; |
| 506 | + let keySet; |
| 507 | + do { |
| 508 | + if (!isProto) { |
| 509 | + obj = ObjectGetPrototypeOf(obj); |
| 510 | + // Stop as soon as a null prototype is encountered. |
| 511 | + if (obj === null) { |
| 512 | + return; |
| 513 | + } |
| 514 | + // Stop as soon as a built-in object type is detected. |
| 515 | + const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor'); |
| 516 | + if (descriptor !== undefined && |
| 517 | + typeof descriptor.value === 'function' && |
| 518 | + builtInObjects.has(descriptor.value.name)) { |
| 519 | + return; |
| 520 | + } |
| 521 | + } else { |
| 522 | + isProto = false; |
| 523 | + } |
| 524 | + |
| 525 | + if (depth === 0) { |
| 526 | + keySet = new Set(); |
| 527 | + } else { |
| 528 | + keys.forEach((key) => keySet.add(key)); |
| 529 | + } |
| 530 | + // Get all own property names and symbols. |
| 531 | + keys = ObjectGetOwnPropertyNames(obj); |
| 532 | + const symbols = ObjectGetOwnPropertySymbols(obj); |
| 533 | + if (symbols.length !== 0) { |
| 534 | + keys.push(...symbols); |
| 535 | + } |
| 536 | + for (const key of keys) { |
| 537 | + // Ignore the `constructor` property and keys that exist on layers above. |
| 538 | + if (key === 'constructor' || |
| 539 | + ObjectPrototypeHasOwnProperty(main, key) || |
| 540 | + (depth !== 0 && keySet.has(key))) { |
| 541 | + continue; |
| 542 | + } |
| 543 | + const desc = ObjectGetOwnPropertyDescriptor(obj, key); |
| 544 | + if (typeof desc.value === 'function') { |
| 545 | + continue; |
| 546 | + } |
| 547 | + const value = formatProperty( |
| 548 | + ctx, obj, recurseTimes, key, kObjectType, desc); |
| 549 | + if (ctx.colors) { |
| 550 | + // Faint! |
| 551 | + output.push(`\u001b[2m${value}\u001b[22m`); |
| 552 | + } else { |
| 553 | + output.push(value); |
| 554 | + } |
| 555 | + } |
| 556 | + // Limit the inspection to up to three prototype layers. Using `recurseTimes` |
| 557 | + // is not a good choice here, because it's as if the properties are declared |
| 558 | + // on the current object from the users perspective. |
| 559 | + } while (++depth !== 3); |
| 560 | +} |
| 561 | + |
493 | 562 | function getPrefix(constructor, tag, fallback) { |
494 | 563 | if (constructor === null) { |
495 | 564 | if (tag !== '') { |
@@ -693,8 +762,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) { |
693 | 762 |
|
694 | 763 | function formatRaw(ctx, value, recurseTimes, typedArray) { |
695 | 764 | let keys; |
| 765 | + let protoProps; |
| 766 | + if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) { |
| 767 | + protoProps = []; |
| 768 | + } |
| 769 | + |
| 770 | + const constructor = getConstructorName(value, ctx, recurseTimes, protoProps); |
| 771 | + // Reset the variable to check for this later on. |
| 772 | + if (protoProps !== undefined && protoProps.length === 0) { |
| 773 | + protoProps = undefined; |
| 774 | + } |
696 | 775 |
|
697 | | - const constructor = getConstructorName(value, ctx, recurseTimes); |
698 | 776 | let tag = value[SymbolToStringTag]; |
699 | 777 | // Only list the tag in case it's non-enumerable / not an own property. |
700 | 778 | // Otherwise we'd print this twice. |
@@ -724,21 +802,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
724 | 802 | // Only set the constructor for non ordinary ("Array [...]") arrays. |
725 | 803 | const prefix = getPrefix(constructor, tag, 'Array'); |
726 | 804 | braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']']; |
727 | | - if (value.length === 0 && keys.length === 0) |
| 805 | + if (value.length === 0 && keys.length === 0 && protoProps === undefined) |
728 | 806 | return `${braces[0]}]`; |
729 | 807 | extrasType = kArrayExtrasType; |
730 | 808 | formatter = formatArray; |
731 | 809 | } else if (isSet(value)) { |
732 | 810 | keys = getKeys(value, ctx.showHidden); |
733 | 811 | const prefix = getPrefix(constructor, tag, 'Set'); |
734 | | - if (value.size === 0 && keys.length === 0) |
| 812 | + if (value.size === 0 && keys.length === 0 && protoProps === undefined) |
735 | 813 | return `${prefix}{}`; |
736 | 814 | braces = [`${prefix}{`, '}']; |
737 | 815 | formatter = formatSet; |
738 | 816 | } else if (isMap(value)) { |
739 | 817 | keys = getKeys(value, ctx.showHidden); |
740 | 818 | const prefix = getPrefix(constructor, tag, 'Map'); |
741 | | - if (value.size === 0 && keys.length === 0) |
| 819 | + if (value.size === 0 && keys.length === 0 && protoProps === undefined) |
742 | 820 | return `${prefix}{}`; |
743 | 821 | braces = [`${prefix}{`, '}']; |
744 | 822 | formatter = formatMap; |
@@ -773,12 +851,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
773 | 851 | } else if (tag !== '') { |
774 | 852 | braces[0] = `${getPrefix(constructor, tag, 'Object')}{`; |
775 | 853 | } |
776 | | - if (keys.length === 0) { |
| 854 | + if (keys.length === 0 && protoProps === undefined) { |
777 | 855 | return `${braces[0]}}`; |
778 | 856 | } |
779 | 857 | } else if (typeof value === 'function') { |
780 | 858 | base = getFunctionBase(value, constructor, tag); |
781 | | - if (keys.length === 0) |
| 859 | + if (keys.length === 0 && protoProps === undefined) |
782 | 860 | return ctx.stylize(base, 'special'); |
783 | 861 | } else if (isRegExp(value)) { |
784 | 862 | // Make RegExps say that they are RegExps |
@@ -788,8 +866,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
788 | 866 | const prefix = getPrefix(constructor, tag, 'RegExp'); |
789 | 867 | if (prefix !== 'RegExp ') |
790 | 868 | base = `${prefix}${base}`; |
791 | | - if (keys.length === 0 || (recurseTimes > ctx.depth && ctx.depth !== null)) |
| 869 | + if ((keys.length === 0 && protoProps === undefined) || |
| 870 | + (recurseTimes > ctx.depth && ctx.depth !== null)) { |
792 | 871 | return ctx.stylize(base, 'regexp'); |
| 872 | + } |
793 | 873 | } else if (isDate(value)) { |
794 | 874 | // Make dates with properties first say the date |
795 | 875 | base = NumberIsNaN(DatePrototypeGetTime(value)) ? |
@@ -798,12 +878,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
798 | 878 | const prefix = getPrefix(constructor, tag, 'Date'); |
799 | 879 | if (prefix !== 'Date ') |
800 | 880 | base = `${prefix}${base}`; |
801 | | - if (keys.length === 0) { |
| 881 | + if (keys.length === 0 && protoProps === undefined) { |
802 | 882 | return ctx.stylize(base, 'date'); |
803 | 883 | } |
804 | 884 | } else if (isError(value)) { |
805 | 885 | base = formatError(value, constructor, tag, ctx); |
806 | | - if (keys.length === 0) |
| 886 | + if (keys.length === 0 && protoProps === undefined) |
807 | 887 | return base; |
808 | 888 | } else if (isAnyArrayBuffer(value)) { |
809 | 889 | // Fast path for ArrayBuffer and SharedArrayBuffer. |
@@ -814,7 +894,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
814 | 894 | const prefix = getPrefix(constructor, tag, arrayType); |
815 | 895 | if (typedArray === undefined) { |
816 | 896 | formatter = formatArrayBuffer; |
817 | | - } else if (keys.length === 0) { |
| 897 | + } else if (keys.length === 0 && protoProps === undefined) { |
818 | 898 | return prefix + |
819 | 899 | `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; |
820 | 900 | } |
@@ -838,7 +918,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
838 | 918 | formatter = formatNamespaceObject; |
839 | 919 | } else if (isBoxedPrimitive(value)) { |
840 | 920 | base = getBoxedBase(value, ctx, keys, constructor, tag); |
841 | | - if (keys.length === 0) { |
| 921 | + if (keys.length === 0 && protoProps === undefined) { |
842 | 922 | return base; |
843 | 923 | } |
844 | 924 | } else { |
@@ -858,7 +938,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
858 | 938 | formatter = formatIterator; |
859 | 939 | // Handle other regular objects again. |
860 | 940 | } else { |
861 | | - if (keys.length === 0) { |
| 941 | + if (keys.length === 0 && protoProps === undefined) { |
862 | 942 | if (isExternal(value)) |
863 | 943 | return ctx.stylize('[External]', 'special'); |
864 | 944 | return `${getCtxStyle(value, constructor, tag)}{}`; |
@@ -886,6 +966,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { |
886 | 966 | output.push( |
887 | 967 | formatProperty(ctx, value, recurseTimes, keys[i], extrasType)); |
888 | 968 | } |
| 969 | + if (protoProps !== undefined) { |
| 970 | + output.push(...protoProps); |
| 971 | + } |
889 | 972 | } catch (err) { |
890 | 973 | const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); |
891 | 974 | return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl); |
@@ -1349,6 +1432,7 @@ function formatTypedArray(ctx, value, recurseTimes) { |
1349 | 1432 | } |
1350 | 1433 | if (ctx.showHidden) { |
1351 | 1434 | // .buffer goes last, it's not a primitive like the others. |
| 1435 | + // All besides `BYTES_PER_ELEMENT` are actually getters. |
1352 | 1436 | ctx.indentationLvl += 2; |
1353 | 1437 | for (const key of [ |
1354 | 1438 | 'BYTES_PER_ELEMENT', |
@@ -1497,10 +1581,10 @@ function formatPromise(ctx, value, recurseTimes) { |
1497 | 1581 | return output; |
1498 | 1582 | } |
1499 | 1583 |
|
1500 | | -function formatProperty(ctx, value, recurseTimes, key, type) { |
| 1584 | +function formatProperty(ctx, value, recurseTimes, key, type, desc) { |
1501 | 1585 | let name, str; |
1502 | 1586 | let extra = ' '; |
1503 | | - const desc = ObjectGetOwnPropertyDescriptor(value, key) || |
| 1587 | + desc = desc || ObjectGetOwnPropertyDescriptor(value, key) || |
1504 | 1588 | { value: value[key], enumerable: true }; |
1505 | 1589 | if (desc.value !== undefined) { |
1506 | 1590 | const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3; |
|