mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
util: use @@toStringTag
uses @@toStringTag when creating the "tag" for an inspected value PR-URL: https://github.com/nodejs/node/pull/16956 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Brian White <mscdex@mscdex.net> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
committed by
Anna Henningsen
parent
f6926d5d00
commit
31e0dbc0c7
@@ -350,6 +350,24 @@ changes:
|
||||
The `util.inspect()` method returns a string representation of `object` that is
|
||||
primarily useful for debugging. Additional `options` may be passed that alter
|
||||
certain aspects of the formatted string.
|
||||
`util.inspect()` will use the constructor's name and/or `@@toStringTag` to make an
|
||||
identifiable tag for an inspected value.
|
||||
|
||||
```js
|
||||
class Foo {
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
|
||||
class Bar {}
|
||||
|
||||
const baz = Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } });
|
||||
|
||||
util.inspect(new Foo()); // 'Foo [bar] {}'
|
||||
util.inspect(new Bar()); // 'Bar {}'
|
||||
util.inspect(baz); // '[foo] {}'
|
||||
```
|
||||
|
||||
The following example inspects all properties of the `util` object:
|
||||
|
||||
|
||||
@@ -220,6 +220,43 @@ function getConstructorOf(obj) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// getConstructorOf is wrapped into this to save iterations
|
||||
function getIdentificationOf(obj) {
|
||||
const original = obj;
|
||||
let constructor = undefined;
|
||||
let tag = undefined;
|
||||
|
||||
while (obj) {
|
||||
if (constructor === undefined) {
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, 'constructor');
|
||||
if (desc !== undefined &&
|
||||
typeof desc.value === 'function' &&
|
||||
desc.value.name !== '')
|
||||
constructor = desc.value.name;
|
||||
}
|
||||
|
||||
if (tag === undefined) {
|
||||
const desc = Object.getOwnPropertyDescriptor(obj, Symbol.toStringTag);
|
||||
if (desc !== undefined) {
|
||||
if (typeof desc.value === 'string') {
|
||||
tag = desc.value;
|
||||
} else if (desc.get !== undefined) {
|
||||
tag = desc.get.call(original);
|
||||
if (typeof tag !== 'string')
|
||||
tag = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor !== undefined && tag !== undefined)
|
||||
break;
|
||||
|
||||
obj = Object.getPrototypeOf(obj);
|
||||
}
|
||||
|
||||
return { constructor, tag };
|
||||
}
|
||||
|
||||
const kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
|
||||
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
|
||||
|
||||
@@ -310,6 +347,7 @@ module.exports = {
|
||||
emitExperimentalWarning,
|
||||
filterDuplicateStrings,
|
||||
getConstructorOf,
|
||||
getIdentificationOf,
|
||||
isError,
|
||||
join,
|
||||
normalizeEncoding,
|
||||
|
||||
52
lib/util.js
52
lib/util.js
@@ -56,7 +56,7 @@ const {
|
||||
const {
|
||||
customInspectSymbol,
|
||||
deprecate,
|
||||
getConstructorOf,
|
||||
getIdentificationOf,
|
||||
isError,
|
||||
promisify,
|
||||
join
|
||||
@@ -429,9 +429,15 @@ function formatValue(ctx, value, recurseTimes, ln) {
|
||||
}
|
||||
|
||||
const keyLength = keys.length + symbols.length;
|
||||
const constructor = getConstructorOf(value);
|
||||
const ctorName = constructor && constructor.name ?
|
||||
`${constructor.name} ` : '';
|
||||
|
||||
const { constructor, tag } = getIdentificationOf(value);
|
||||
var prefix = '';
|
||||
if (constructor && tag && constructor !== tag)
|
||||
prefix = `${constructor} [${tag}] `;
|
||||
else if (constructor)
|
||||
prefix = `${constructor} `;
|
||||
else if (tag)
|
||||
prefix = `[${tag}] `;
|
||||
|
||||
var base = '';
|
||||
var formatter = formatObject;
|
||||
@@ -444,28 +450,28 @@ function formatValue(ctx, value, recurseTimes, ln) {
|
||||
noIterator = false;
|
||||
if (Array.isArray(value)) {
|
||||
// Only set the constructor for non ordinary ("Array [...]") arrays.
|
||||
braces = [`${ctorName === 'Array ' ? '' : ctorName}[`, ']'];
|
||||
braces = [`${prefix === 'Array ' ? '' : prefix}[`, ']'];
|
||||
if (value.length === 0 && keyLength === 0)
|
||||
return `${braces[0]}]`;
|
||||
formatter = formatArray;
|
||||
} else if (isSet(value)) {
|
||||
if (value.size === 0 && keyLength === 0)
|
||||
return `${ctorName}{}`;
|
||||
braces = [`${ctorName}{`, '}'];
|
||||
return `${prefix}{}`;
|
||||
braces = [`${prefix}{`, '}'];
|
||||
formatter = formatSet;
|
||||
} else if (isMap(value)) {
|
||||
if (value.size === 0 && keyLength === 0)
|
||||
return `${ctorName}{}`;
|
||||
braces = [`${ctorName}{`, '}'];
|
||||
return `${prefix}{}`;
|
||||
braces = [`${prefix}{`, '}'];
|
||||
formatter = formatMap;
|
||||
} else if (isTypedArray(value)) {
|
||||
braces = [`${ctorName}[`, ']'];
|
||||
braces = [`${prefix}[`, ']'];
|
||||
formatter = formatTypedArray;
|
||||
} else if (isMapIterator(value)) {
|
||||
braces = ['MapIterator {', '}'];
|
||||
braces = [`[${tag}] {`, '}'];
|
||||
formatter = formatMapIterator;
|
||||
} else if (isSetIterator(value)) {
|
||||
braces = ['SetIterator {', '}'];
|
||||
braces = [`[${tag}] {`, '}'];
|
||||
formatter = formatSetIterator;
|
||||
} else {
|
||||
// Check for boxed strings with valueOf()
|
||||
@@ -491,12 +497,13 @@ function formatValue(ctx, value, recurseTimes, ln) {
|
||||
}
|
||||
if (noIterator) {
|
||||
braces = ['{', '}'];
|
||||
if (ctorName === 'Object ') {
|
||||
if (prefix === 'Object ') {
|
||||
// Object fast path
|
||||
if (keyLength === 0)
|
||||
return '{}';
|
||||
} else if (typeof value === 'function') {
|
||||
const name = `${constructor.name}${value.name ? `: ${value.name}` : ''}`;
|
||||
const name =
|
||||
`${constructor || tag}${value.name ? `: ${value.name}` : ''}`;
|
||||
if (keyLength === 0)
|
||||
return ctx.stylize(`[${name}]`, 'special');
|
||||
base = ` [${name}]`;
|
||||
@@ -523,16 +530,16 @@ function formatValue(ctx, value, recurseTimes, ln) {
|
||||
// Can't do the same for DataView because it has a non-primitive
|
||||
// .buffer property that we need to recurse for.
|
||||
if (keyLength === 0)
|
||||
return ctorName +
|
||||
return prefix +
|
||||
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
|
||||
braces[0] = `${ctorName}{`;
|
||||
braces[0] = `${prefix}{`;
|
||||
keys.unshift('byteLength');
|
||||
} else if (isDataView(value)) {
|
||||
braces[0] = `${ctorName}{`;
|
||||
braces[0] = `${prefix}{`;
|
||||
// .buffer goes last, it's not a primitive like the others.
|
||||
keys.unshift('byteLength', 'byteOffset', 'buffer');
|
||||
} else if (isPromise(value)) {
|
||||
braces[0] = `${ctorName}{`;
|
||||
braces[0] = `${prefix}{`;
|
||||
formatter = formatPromise;
|
||||
} else {
|
||||
// Check boxed primitives other than string with valueOf()
|
||||
@@ -560,22 +567,21 @@ function formatValue(ctx, value, recurseTimes, ln) {
|
||||
} else if (keyLength === 0) {
|
||||
if (isExternal(value))
|
||||
return ctx.stylize('[External]', 'special');
|
||||
return `${ctorName}{}`;
|
||||
return `${prefix}{}`;
|
||||
} else {
|
||||
braces[0] = `${ctorName}{`;
|
||||
braces[0] = `${prefix}{`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Using an array here is actually better for the average case than using
|
||||
// a Set. `seen` will only check for the depth and will never grow to large.
|
||||
// a Set. `seen` will only check for the depth and will never grow too large.
|
||||
if (ctx.seen.indexOf(value) !== -1)
|
||||
return ctx.stylize('[Circular]', 'special');
|
||||
|
||||
if (recurseTimes != null) {
|
||||
if (recurseTimes < 0)
|
||||
return ctx.stylize(`[${constructor ? constructor.name : 'Object'}]`,
|
||||
'special');
|
||||
return ctx.stylize(`[${constructor || tag || 'Object'}]`, 'special');
|
||||
recurseTimes -= 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -923,27 +923,27 @@ if (typeof Symbol !== 'undefined') {
|
||||
// Test Map iterators
|
||||
{
|
||||
const map = new Map([['foo', 'bar']]);
|
||||
assert.strictEqual(util.inspect(map.keys()), 'MapIterator { \'foo\' }');
|
||||
assert.strictEqual(util.inspect(map.values()), 'MapIterator { \'bar\' }');
|
||||
assert.strictEqual(util.inspect(map.keys()), '[Map Iterator] { \'foo\' }');
|
||||
assert.strictEqual(util.inspect(map.values()), '[Map Iterator] { \'bar\' }');
|
||||
assert.strictEqual(util.inspect(map.entries()),
|
||||
'MapIterator { [ \'foo\', \'bar\' ] }');
|
||||
'[Map Iterator] { [ \'foo\', \'bar\' ] }');
|
||||
// make sure the iterator doesn't get consumed
|
||||
const keys = map.keys();
|
||||
assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
|
||||
assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }');
|
||||
assert.strictEqual(util.inspect(keys), '[Map Iterator] { \'foo\' }');
|
||||
assert.strictEqual(util.inspect(keys), '[Map Iterator] { \'foo\' }');
|
||||
}
|
||||
|
||||
// Test Set iterators
|
||||
{
|
||||
const aSet = new Set([1, 3]);
|
||||
assert.strictEqual(util.inspect(aSet.keys()), 'SetIterator { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(aSet.values()), 'SetIterator { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(aSet.values()), '[Set Iterator] { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(aSet.entries()),
|
||||
'SetIterator { [ 1, 1 ], [ 3, 3 ] }');
|
||||
'[Set Iterator] { [ 1, 1 ], [ 3, 3 ] }');
|
||||
// make sure the iterator doesn't get consumed
|
||||
const keys = aSet.keys();
|
||||
assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');
|
||||
assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');
|
||||
}
|
||||
|
||||
// Test alignment of items in container
|
||||
@@ -996,11 +996,11 @@ if (typeof Symbol !== 'undefined') {
|
||||
assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)),
|
||||
'ArraySubclass [ 1, 2, 3 ]');
|
||||
assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])),
|
||||
'SetSubclass { 1, 2, 3 }');
|
||||
'SetSubclass [Set] { 1, 2, 3 }');
|
||||
assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])),
|
||||
'MapSubclass { \'foo\' => 42 }');
|
||||
'MapSubclass [Map] { \'foo\' => 42 }');
|
||||
assert.strictEqual(util.inspect(new PromiseSubclass(() => {})),
|
||||
'PromiseSubclass { <pending> }');
|
||||
'PromiseSubclass [Promise] { <pending> }');
|
||||
assert.strictEqual(
|
||||
util.inspect({ a: { b: new ArraySubclass([1, [2], 3]) } }, { depth: 1 }),
|
||||
'{ a: { b: [ArraySubclass] } }'
|
||||
@@ -1162,3 +1162,52 @@ assert.doesNotThrow(() => util.inspect(process));
|
||||
const obj = { inspect: 'fhqwhgads' };
|
||||
assert.strictEqual(util.inspect(obj), "{ inspect: 'fhqwhgads' }");
|
||||
}
|
||||
|
||||
{
|
||||
// @@toStringTag
|
||||
assert.strictEqual(util.inspect({ [Symbol.toStringTag]: 'a' }),
|
||||
'Object [a] { [Symbol(Symbol.toStringTag)]: \'a\' }');
|
||||
|
||||
class Foo {
|
||||
constructor() {
|
||||
this.foo = 'bar';
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return this.foo;
|
||||
}
|
||||
}
|
||||
|
||||
assert.strictEqual(util.inspect(
|
||||
Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } })),
|
||||
'[foo] {}');
|
||||
|
||||
assert.strictEqual(util.inspect(new Foo()), 'Foo [bar] { foo: \'bar\' }');
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(new (class extends Foo {})()),
|
||||
'Foo [bar] { foo: \'bar\' }');
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(Object.create(Object.create(Foo.prototype), {
|
||||
foo: { value: 'bar', enumerable: true }
|
||||
})),
|
||||
'Foo [bar] { foo: \'bar\' }');
|
||||
|
||||
class ThrowingClass {
|
||||
get [Symbol.toStringTag]() {
|
||||
throw new Error('toStringTag error');
|
||||
}
|
||||
}
|
||||
|
||||
assert.throws(() => util.inspect(new ThrowingClass()), /toStringTag error/);
|
||||
|
||||
class NotStringClass {
|
||||
get [Symbol.toStringTag]() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
assert.strictEqual(util.inspect(new NotStringClass()),
|
||||
'NotStringClass {}');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user