| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0376e1c commit 7686865
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -398,6 +398,10 @@ stream.write('With ES6'); | |||
| 398 | 398 | <!-- YAML | |
| 399 | 399 | added: v0.3.0 | |
| 400 | 400 | changes: | |
| 401 | + - version: REPLACEME | ||
| 402 | + pr-url: https://github.com/nodejs/node/pull/30768 | ||
| 403 | + description: User defined prototype properties are inspected in case | ||
| 404 | + `showHidden` is `true`. | ||
| 401 | 405 | - version: v12.0.0 | |
| 402 | 406 | pr-url: https://github.com/nodejs/node/pull/27109 | |
| 403 | 407 | description: The `compact` options default is changed to `3` and the | |
@@ -458,7 +462,8 @@ changes: | |||
| 458 | 462 | * `options` {Object} | |
| 459 | 463 | * `showHidden` {boolean} If `true`, `object`'s non-enumerable symbols and | |
| 460 | 464 | properties are included in the formatted result. [`WeakMap`][] and | |
| 461 | - [`WeakSet`][] entries are also included. **Default:** `false`. | ||
| 465 | + [`WeakSet`][] entries are also included as well as user defined prototype | ||
| 466 | + properties (excluding method properties). **Default:** `false`. | ||
| 462 | 467 | * `depth` {number} Specifies the number of times to recurse while formatting | |
| 463 | 468 | `object`. This is useful for inspecting large objects. To recurse up to | |
| 464 | 469 | the maximum call stack size pass `Infinity` or `null`. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -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; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -391,8 +391,24 @@ assert.strictEqual( | |||
| 391 | 391 | { | |
| 392 | 392 | class CustomArray extends Array {} | |
| 393 | 393 | CustomArray.prototype[5] = 'foo'; | |
| 394 | + CustomArray.prototype[49] = 'bar'; | ||
| 395 | + CustomArray.prototype.foo = true; | ||
| 394 | 396 | const arr = new CustomArray(50); | |
| 395 | - assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]'); | ||
| 397 | + arr[49] = 'I win'; | ||
| 398 | + assert.strictEqual( | ||
| 399 | + util.inspect(arr), | ||
| 400 | + "CustomArray [ <49 empty items>, 'I win' ]" | ||
| 401 | + ); | ||
| 402 | + assert.strictEqual( | ||
| 403 | + util.inspect(arr, { showHidden: true }), | ||
| 404 | + 'CustomArray [\n' + | ||
| 405 | + ' <49 empty items>,\n' + | ||
| 406 | + " 'I win',\n" + | ||
| 407 | + ' [length]: 50,\n' + | ||
| 408 | + " '5': 'foo',\n" + | ||
| 409 | + ' foo: true\n' + | ||
| 410 | + ']' | ||
| 411 | + ); | ||
| 396 | 412 | } | |
| 397 | 413 | ||
| 398 | 414 | // Array with extra properties. | |
@@ -2585,3 +2601,65 @@ assert.strictEqual( | |||
| 2585 | 2601 | throw err; | |
| 2586 | 2602 | } | |
| 2587 | 2603 | } | |
| 2604 | + | ||
| 2605 | + // Inspect prototype properties. | ||
| 2606 | + { | ||
| 2607 | + class Foo extends Map { | ||
| 2608 | + prop = false; | ||
| 2609 | + prop2 = true; | ||
| 2610 | + get abc() { | ||
| 2611 | + return true; | ||
| 2612 | + } | ||
| 2613 | + get def() { | ||
| 2614 | + return false; | ||
| 2615 | + } | ||
| 2616 | + set def(v) {} | ||
| 2617 | + get xyz() { | ||
| 2618 | + return 'Should be ignored'; | ||
| 2619 | + } | ||
| 2620 | + func(a) {} | ||
| 2621 | + [util.inspect.custom]() { | ||
| 2622 | + return this; | ||
| 2623 | + } | ||
| 2624 | + } | ||
| 2625 | + | ||
| 2626 | + class Bar extends Foo { | ||
| 2627 | + abc = true; | ||
| 2628 | + prop = true; | ||
| 2629 | + get xyz() { | ||
| 2630 | + return 'YES!'; | ||
| 2631 | + } | ||
| 2632 | + [util.inspect.custom]() { | ||
| 2633 | + return this; | ||
| 2634 | + } | ||
| 2635 | + } | ||
| 2636 | + | ||
| 2637 | + const bar = new Bar(); | ||
| 2638 | + | ||
| 2639 | + assert.strictEqual( | ||
| 2640 | + inspect(bar), | ||
| 2641 | + 'Bar [Map] { prop: true, prop2: true, abc: true }' | ||
| 2642 | + ); | ||
| 2643 | + assert.strictEqual( | ||
| 2644 | + inspect(bar, { showHidden: true, getters: true, colors: false }), | ||
| 2645 | + 'Bar [Map] {\n' + | ||
| 2646 | + ' [size]: 0,\n' + | ||
| 2647 | + ' prop: true,\n' + | ||
| 2648 | + ' prop2: true,\n' + | ||
| 2649 | + ' abc: true,\n' + | ||
| 2650 | + " [xyz]: [Getter: 'YES!'],\n" + | ||
| 2651 | + ' [def]: [Getter/Setter: false]\n' + | ||
| 2652 | + '}' | ||
| 2653 | + ); | ||
| 2654 | + assert.strictEqual( | ||
| 2655 | + inspect(bar, { showHidden: true, getters: false, colors: true }), | ||
| 2656 | + 'Bar [Map] {\n' + | ||
| 2657 | + ' [size]: \x1B[33m0\x1B[39m,\n' + | ||
| 2658 | + ' prop: \x1B[33mtrue\x1B[39m,\n' + | ||
| 2659 | + ' prop2: \x1B[33mtrue\x1B[39m,\n' + | ||
| 2660 | + ' abc: \x1B[33mtrue\x1B[39m,\n' + | ||
| 2661 | + ' \x1B[2m[xyz]: \x1B[36m[Getter]\x1B[39m\x1B[22m,\n' + | ||
| 2662 | + ' \x1B[2m[def]: \x1B[36m[Getter/Setter]\x1B[39m\x1B[22m\n' + | ||
| 2663 | + '}' | ||
| 2664 | + ); | ||
| 2665 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -113,10 +113,19 @@ if (common.hasIntl) { | |||
| 113 | 113 | } else { | |
| 114 | 114 | assert.strictEqual( | |
| 115 | 115 | util.inspect(dec, { showHidden: true }), | |
| 116 | - "TextDecoder {\n encoding: 'utf-8',\n fatal: false,\n " + | ||
| 117 | - 'ignoreBOM: true,\n [Symbol(flags)]: 4,\n [Symbol(handle)]: ' + | ||
| 118 | - "StringDecoder {\n encoding: 'utf8',\n " + | ||
| 119 | - '[Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>\n }\n}' | ||
| 116 | + 'TextDecoder {\n' + | ||
| 117 | + " encoding: 'utf-8',\n" + | ||
| 118 | + ' fatal: false,\n' + | ||
| 119 | + ' ignoreBOM: true,\n' + | ||
| 120 | + ' [Symbol(flags)]: 4,\n' + | ||
| 121 | + ' [Symbol(handle)]: StringDecoder {\n' + | ||
| 122 | + " encoding: 'utf8',\n" + | ||
| 123 | + ' [Symbol(kNativeDecoder)]: <Buffer 00 00 00 00 00 00 01>,\n' + | ||
| 124 | + ' lastChar: [Getter],\n' + | ||
| 125 | + ' lastNeed: [Getter],\n' + | ||
| 126 | + ' lastTotal: [Getter]\n' + | ||
| 127 | + ' }\n' + | ||
| 128 | + '}' | ||
| 120 | 129 | ); | |
| 121 | 130 | } | |
| 122 | 131 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments