GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — util: inspect (user defined) prototype properties · nodejs/node@b0e3aec

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • doc/api
    • util.md
  • lib/internal/util
    • inspect.js
  • test/parallel
    • test-util-inspect.js
    • test-whatwg-encoding-custom-textdecoder.js
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎doc/api/util.md‎

Copy file name to clipboardExpand all lines: doc/api/util.md
+6-1Lines changed: 6 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ stream.write('With ES6');
398398
<!-- YAML
399399
added: v0.3.0
400400
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`.
401405
- version: v12.0.0
402406
pr-url: https://github.com/nodejs/node/pull/27109
403407
description: The `compact` options default is changed to `3` and the
@@ -458,7 +462,8 @@ changes:
458462
* `options` {Object}
459463
* `showHidden` {boolean} If `true`, `object`'s non-enumerable symbols and
460464
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`.
462467
* `depth` {number} Specifies the number of times to recurse while formatting
463468
`object`. This is useful for inspecting large objects. To recurse up to
464469
the maximum call stack size pass `Infinity` or `null`.
Collapse file

‎lib/internal/util/inspect.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/inspect.js
+100-16Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,20 @@ function getEmptyFormatArray() {
450450
return [];
451451
}
452452

453-
function getConstructorName(obj, ctx, recurseTimes) {
453+
function getConstructorName(obj, ctx, recurseTimes, protoProps) {
454454
let firstProto;
455455
const tmp = obj;
456456
while (obj) {
457457
const descriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
458458
if (descriptor !== undefined &&
459459
typeof descriptor.value === 'function' &&
460460
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+
}
461467
return descriptor.value.name;
462468
}
463469

@@ -477,7 +483,8 @@ function getConstructorName(obj, ctx, recurseTimes) {
477483
return `${res} <Complex prototype>`;
478484
}
479485

480-
const protoConstr = getConstructorName(firstProto, ctx, recurseTimes + 1);
486+
const protoConstr = getConstructorName(
487+
firstProto, ctx, recurseTimes + 1, protoProps);
481488

482489
if (protoConstr === null) {
483490
return `${res} <${inspect(firstProto, {
@@ -490,6 +497,68 @@ function getConstructorName(obj, ctx, recurseTimes) {
490497
return `${res} <${protoConstr}>`;
491498
}
492499

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+
493562
function getPrefix(constructor, tag, fallback) {
494563
if (constructor === null) {
495564
if (tag !== '') {
@@ -693,8 +762,17 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
693762

694763
function formatRaw(ctx, value, recurseTimes, typedArray) {
695764
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+
}
696775

697-
const constructor = getConstructorName(value, ctx, recurseTimes);
698776
let tag = value[SymbolToStringTag];
699777
// Only list the tag in case it's non-enumerable / not an own property.
700778
// Otherwise we'd print this twice.
@@ -724,21 +802,21 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
724802
// Only set the constructor for non ordinary ("Array [...]") arrays.
725803
const prefix = getPrefix(constructor, tag, 'Array');
726804
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
727-
if (value.length === 0 && keys.length === 0)
805+
if (value.length === 0 && keys.length === 0 && protoProps === undefined)
728806
return `${braces[0]}]`;
729807
extrasType = kArrayExtrasType;
730808
formatter = formatArray;
731809
} else if (isSet(value)) {
732810
keys = getKeys(value, ctx.showHidden);
733811
const prefix = getPrefix(constructor, tag, 'Set');
734-
if (value.size === 0 && keys.length === 0)
812+
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
735813
return `${prefix}{}`;
736814
braces = [`${prefix}{`, '}'];
737815
formatter = formatSet;
738816
} else if (isMap(value)) {
739817
keys = getKeys(value, ctx.showHidden);
740818
const prefix = getPrefix(constructor, tag, 'Map');
741-
if (value.size === 0 && keys.length === 0)
819+
if (value.size === 0 && keys.length === 0 && protoProps === undefined)
742820
return `${prefix}{}`;
743821
braces = [`${prefix}{`, '}'];
744822
formatter = formatMap;
@@ -773,12 +851,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
773851
} else if (tag !== '') {
774852
braces[0] = `${getPrefix(constructor, tag, 'Object')}{`;
775853
}
776-
if (keys.length === 0) {
854+
if (keys.length === 0 && protoProps === undefined) {
777855
return `${braces[0]}}`;
778856
}
779857
} else if (typeof value === 'function') {
780858
base = getFunctionBase(value, constructor, tag);
781-
if (keys.length === 0)
859+
if (keys.length === 0 && protoProps === undefined)
782860
return ctx.stylize(base, 'special');
783861
} else if (isRegExp(value)) {
784862
// Make RegExps say that they are RegExps
@@ -788,8 +866,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
788866
const prefix = getPrefix(constructor, tag, 'RegExp');
789867
if (prefix !== 'RegExp ')
790868
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)) {
792871
return ctx.stylize(base, 'regexp');
872+
}
793873
} else if (isDate(value)) {
794874
// Make dates with properties first say the date
795875
base = NumberIsNaN(DatePrototypeGetTime(value)) ?
@@ -798,12 +878,12 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
798878
const prefix = getPrefix(constructor, tag, 'Date');
799879
if (prefix !== 'Date ')
800880
base = `${prefix}${base}`;
801-
if (keys.length === 0) {
881+
if (keys.length === 0 && protoProps === undefined) {
802882
return ctx.stylize(base, 'date');
803883
}
804884
} else if (isError(value)) {
805885
base = formatError(value, constructor, tag, ctx);
806-
if (keys.length === 0)
886+
if (keys.length === 0 && protoProps === undefined)
807887
return base;
808888
} else if (isAnyArrayBuffer(value)) {
809889
// Fast path for ArrayBuffer and SharedArrayBuffer.
@@ -814,7 +894,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
814894
const prefix = getPrefix(constructor, tag, arrayType);
815895
if (typedArray === undefined) {
816896
formatter = formatArrayBuffer;
817-
} else if (keys.length === 0) {
897+
} else if (keys.length === 0 && protoProps === undefined) {
818898
return prefix +
819899
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
820900
}
@@ -838,7 +918,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
838918
formatter = formatNamespaceObject;
839919
} else if (isBoxedPrimitive(value)) {
840920
base = getBoxedBase(value, ctx, keys, constructor, tag);
841-
if (keys.length === 0) {
921+
if (keys.length === 0 && protoProps === undefined) {
842922
return base;
843923
}
844924
} else {
@@ -858,7 +938,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
858938
formatter = formatIterator;
859939
// Handle other regular objects again.
860940
} else {
861-
if (keys.length === 0) {
941+
if (keys.length === 0 && protoProps === undefined) {
862942
if (isExternal(value))
863943
return ctx.stylize('[External]', 'special');
864944
return `${getCtxStyle(value, constructor, tag)}{}`;
@@ -886,6 +966,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
886966
output.push(
887967
formatProperty(ctx, value, recurseTimes, keys[i], extrasType));
888968
}
969+
if (protoProps !== undefined) {
970+
output.push(...protoProps);
971+
}
889972
} catch (err) {
890973
const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1);
891974
return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl);
@@ -1349,6 +1432,7 @@ function formatTypedArray(ctx, value, recurseTimes) {
13491432
}
13501433
if (ctx.showHidden) {
13511434
// .buffer goes last, it's not a primitive like the others.
1435+
// All besides `BYTES_PER_ELEMENT` are actually getters.
13521436
ctx.indentationLvl += 2;
13531437
for (const key of [
13541438
'BYTES_PER_ELEMENT',
@@ -1497,10 +1581,10 @@ function formatPromise(ctx, value, recurseTimes) {
14971581
return output;
14981582
}
14991583

1500-
function formatProperty(ctx, value, recurseTimes, key, type) {
1584+
function formatProperty(ctx, value, recurseTimes, key, type, desc) {
15011585
let name, str;
15021586
let extra = ' ';
1503-
const desc = ObjectGetOwnPropertyDescriptor(value, key) ||
1587+
desc = desc || ObjectGetOwnPropertyDescriptor(value, key) ||
15041588
{ value: value[key], enumerable: true };
15051589
if (desc.value !== undefined) {
15061590
const diff = (type !== kObjectType || ctx.compact !== true) ? 2 : 3;
Collapse file

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

Copy file name to clipboardExpand all lines: test/parallel/test-util-inspect.js
+79-1Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,24 @@ assert.strictEqual(
391391
{
392392
class CustomArray extends Array {}
393393
CustomArray.prototype[5] = 'foo';
394+
CustomArray.prototype[49] = 'bar';
395+
CustomArray.prototype.foo = true;
394396
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+
);
396412
}
397413

398414
// Array with extra properties.
@@ -2585,3 +2601,65 @@ assert.strictEqual(
25852601
throw err;
25862602
}
25872603
}
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+
}
Collapse file

‎test/parallel/test-whatwg-encoding-custom-textdecoder.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-whatwg-encoding-custom-textdecoder.js
+13-4Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,19 @@ if (common.hasIntl) {
113113
} else {
114114
assert.strictEqual(
115115
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+
'}'
120129
);
121130
}
122131
}

0 commit comments

Comments
0 (0)
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.