Browse Source

Match Node 9 util.inspect()

inspect
Renée Kooi 7 years ago
parent
commit
5a271dde55
No known key found for this signature in database GPG Key ID: 8CDD5F0BC448F040
  1. 25
      internal/util.js
  2. 3
      package.json
  3. 6
      test/node/format.js
  4. 472
      test/node/inspect.js
  5. 675
      util.js

25
internal/util.js

@ -0,0 +1,25 @@
var colorRegExp = /\u001b\[\d\d?m/g;
function removeColors(str) {
return str.replace(colorRegExp, '');
}
function getConstructorOf(obj) {
while (obj) {
var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined &&
typeof descriptor.value === 'function' &&
descriptor.value.name !== '') {
return descriptor.value;
}
obj = Object.getPrototypeOf(obj);
}
return null;
}
module.exports = {
removeColors: removeColors,
getConstructorOf: getConstructorOf
};

3
package.json

@ -24,7 +24,8 @@
"test:browsers": "airtap test/browser/index.js" "test:browsers": "airtap test/browser/index.js"
}, },
"dependencies": { "dependencies": {
"inherits": "2.0.3" "inherits": "2.0.3",
"is-typed-array": "1.0.4"
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

6
test/node/format.js

@ -67,11 +67,13 @@ assert.equal(util.format('%%%s%%%%', 'hi'), '%hi%%');
})(); })();
// Errors // Errors
assert.equal(util.format(new Error('foo')), '[Error: foo]'); var foo = new Error('foo');
assert.equal(util.format(foo), foo.stack);
function CustomError(msg) { function CustomError(msg) {
Error.call(this); Error.call(this);
Object.defineProperty(this, 'message', { value: msg, enumerable: false }); Object.defineProperty(this, 'message', { value: msg, enumerable: false });
Object.defineProperty(this, 'name', { value: 'CustomError', enumerable: false }); Object.defineProperty(this, 'name', { value: 'CustomError', enumerable: false });
} }
util.inherits(CustomError, Error); util.inherits(CustomError, Error);
assert.equal(util.format(new CustomError('bar')), '[CustomError: bar]'); var custom = new CustomError('bar');
assert.equal(util.format(custom), '[CustomError: bar]');

472
test/node/inspect.js

@ -23,8 +23,380 @@
var assert = require('assert'); var assert = require('assert');
var vm = require('vm');
var util = require('../../'); var util = require('../../');
assert.strictEqual(util.inspect(1), '1');
assert.strictEqual(util.inspect(false), 'false');
assert.strictEqual(util.inspect(''), "''");
assert.strictEqual(util.inspect('hello'), "'hello'");
assert.strictEqual(util.inspect(function() {}), '[Function]');
assert.strictEqual(util.inspect(() => {}), '[Function]');
assert.strictEqual(util.inspect(async function() {}), '[AsyncFunction]');
assert.strictEqual(util.inspect(async () => {}), '[AsyncFunction]');
assert.strictEqual(util.inspect(function*() {}), '[GeneratorFunction]');
assert.strictEqual(util.inspect(undefined), 'undefined');
assert.strictEqual(util.inspect(null), 'null');
assert.strictEqual(util.inspect(/foo(bar\n)?/gi), '/foo(bar\\n)?/gi');
assert.strictEqual(
util.inspect(new Date('Sun, 14 Feb 2010 11:48:40 GMT')),
new Date('2010-02-14T12:48:40+01:00').toISOString()
);
assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString());
assert.strictEqual(util.inspect('\n\u0001'), "'\\n\\u0001'");
assert.strictEqual(
util.inspect(`${Array(75).fill(1)}'\n\u001d\n\u0003`),
`'${Array(75).fill(1)}\\'\\n\\u001d\\n\\u0003'`
);
assert.strictEqual(util.inspect([]), '[]');
assert.strictEqual(util.inspect(Object.create([])), 'Array {}');
assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');
assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');
assert.strictEqual(util.inspect({}), '{}');
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }');
assert.strictEqual(util.inspect({ a: async function() {} }),
'{ a: [AsyncFunction: a] }');
assert.strictEqual(util.inspect({ a: async () => {} }),
'{ a: [AsyncFunction: a] }');
assert.strictEqual(util.inspect({ a: function*() {} }),
'{ a: [GeneratorFunction: a] }');
assert.strictEqual(util.inspect({ a: 1, b: 2 }), '{ a: 1, b: 2 }');
assert.strictEqual(util.inspect({ 'a': {} }), '{ a: {} }');
assert.strictEqual(util.inspect({ 'a': { 'b': 2 } }), '{ a: { b: 2 } }');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }),
'{ a: { b: { c: [Object] } } }');
assert.strictEqual(
util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }, false, null),
'{ a: { b: { c: { d: 2 } } } }');
assert.strictEqual(util.inspect([1, 2, 3], true), '[ 1, 2, 3, [length]: 3 ]');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 0),
'{ a: [Object] }');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1),
'{ a: { b: [Object] } }');
assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1),
'{ a: { b: [Array] } }');
assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array [ ]');
assert.strictEqual(
util.inspect(
Object.create(
{},
{ visible: { value: 1, enumerable: true }, hidden: { value: 2 } }
)
),
'{ visible: 1 }'
);
assert.strictEqual(
util.inspect(
Object.assign(new String('hello'), { [Symbol('foo')]: 123 }),
{ showHidden: true }
),
'{ [String: \'hello\'] [length]: 5, [Symbol(foo)]: 123 }'
);
{
const regexp = /regexp/;
regexp.aprop = 42;
assert.strictEqual(util.inspect({ a: regexp }, false, 0), '{ a: /regexp/ }');
}
assert(/Object/.test(
util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true)
));
assert(!/Object/.test(
util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true)
));
for (const showHidden of [true, false]) {
const ab = new ArrayBuffer(4);
const dv = new DataView(ab, 1, 2);
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
' y: 1337 }');
}
// Now do the same checks but from a different context
for (const showHidden of [true, false]) {
const ab = vm.runInNewContext('new ArrayBuffer(4)');
const dv = vm.runInNewContext('new DataView(ab, 1, 2)', { ab });
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4 } }');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { byteLength: 4, x: 42 },\n' +
' y: 1337 }');
}
[ Float32Array,
Float64Array,
Int16Array,
Int32Array,
Int8Array,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray ].forEach((constructor) => {
const length = 2;
const byteLength = length * constructor.BYTES_PER_ELEMENT;
const array = new constructor(new ArrayBuffer(byteLength), 0, length);
array[0] = 65;
array[1] = 97;
assert.strictEqual(
util.inspect(array, true),
`${constructor.name} [\n` +
' 65,\n' +
' 97,\n' +
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
` [length]: ${length},\n` +
` [byteLength]: ${byteLength},\n` +
' [byteOffset]: 0,\n' +
` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`);
assert.strictEqual(
util.inspect(array, false),
`${constructor.name} [ 65, 97 ]`
);
});
// Now check that declaring a TypedArray in a different context works the same
[ Float32Array,
Float64Array,
Int16Array,
Int32Array,
Int8Array,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray ].forEach((constructor) => {
const length = 2;
const byteLength = length * constructor.BYTES_PER_ELEMENT;
const array = vm.runInNewContext(
'new constructor(new ArrayBuffer(byteLength), 0, length)',
{ constructor, byteLength, length }
);
array[0] = 65;
array[1] = 97;
assert.strictEqual(
util.inspect(array, true),
`${constructor.name} [\n` +
' 65,\n' +
' 97,\n' +
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
` [length]: ${length},\n` +
` [byteLength]: ${byteLength},\n` +
' [byteOffset]: 0,\n' +
` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`);
assert.strictEqual(
util.inspect(array, false),
`${constructor.name} [ 65, 97 ]`
);
});
assert.strictEqual(
util.inspect(Object.create({}, {
visible: { value: 1, enumerable: true },
hidden: { value: 2 }
}), { showHidden: true }),
'{ visible: 1, [hidden]: 2 }'
);
// Objects without prototype
assert.strictEqual(
util.inspect(Object.create(null, {
name: { value: 'Tim', enumerable: true },
hidden: { value: 'secret' }
}), { showHidden: true }),
"{ name: 'Tim', [hidden]: 'secret' }"
);
assert.strictEqual(
util.inspect(Object.create(null, {
name: { value: 'Tim', enumerable: true },
hidden: { value: 'secret' }
})),
'{ name: \'Tim\' }'
);
// Dynamic properties
{
assert.strictEqual(
util.inspect({ get readonly() {} }),
'{ readonly: [Getter] }');
assert.strictEqual(
util.inspect({ get readwrite() {}, set readwrite(val) {} }),
'{ readwrite: [Getter/Setter] }');
assert.strictEqual(
util.inspect({ set writeonly(val) {} }),
'{ writeonly: [Setter] }');
const value = {};
value.a = value;
assert.strictEqual(util.inspect(value), '{ a: [Circular] }');
}
// Array with dynamic properties
{
const value = [1, 2, 3];
Object.defineProperty(
value,
'growingLength',
{
enumerable: true,
get: function() { this.push(true); return this.length; }
}
);
Object.defineProperty(
value,
'-1',
{
enumerable: true,
value: -1
}
);
assert.strictEqual(util.inspect(value),
'[ 1, 2, 3, growingLength: [Getter], \'-1\': -1 ]');
}
// Array with inherited number properties
{
class CustomArray extends Array {}
CustomArray.prototype[5] = 'foo';
const arr = new CustomArray(50);
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
}
// Array with extra properties
{
const arr = [1, 2, 3, , ];
arr.foo = 'bar';
assert.strictEqual(util.inspect(arr),
"[ 1, 2, 3, <1 empty item>, foo: 'bar' ]");
const arr2 = [];
assert.strictEqual(util.inspect([], { showHidden: true }), '[ [length]: 0 ]');
arr2['00'] = 1;
assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ [length]: 0, '00': 1 ]");
arr2[1] = 0;
assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <1 empty item>, 0, [length]: 2, '00': 1 ]");
delete arr2[1];
assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1 ]");
arr2['01'] = 2;
assert.strictEqual(util.inspect(arr2),
"[ <2 empty items>, '00': 1, '01': 2 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]");
const arr3 = [];
arr3[-1] = -1;
assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]");
}
// Indices out of bounds
{
const arr = [];
arr[2 ** 32] = true; // not a valid array index
assert.strictEqual(util.inspect(arr), "[ '4294967296': true ]");
arr[0] = true;
arr[10] = true;
assert.strictEqual(util.inspect(arr),
"[ true, <9 empty items>, true, '4294967296': true ]");
arr[2 ** 32 - 2] = true;
arr[2 ** 32 - 1] = true;
arr[2 ** 32 + 1] = true;
delete arr[0];
delete arr[10];
assert.strictEqual(util.inspect(arr),
['[ <4294967294 empty items>,',
'true,',
"'4294967296': true,",
"'4294967295': true,",
"'4294967297': true ]"
].join('\n '));
}
// Function with properties
{
const value = () => {};
value.aprop = 42;
assert.strictEqual(util.inspect(value), '{ [Function: value] aprop: 42 }');
}
// Anonymous function with properties
{
const value = (() => function() {})();
value.aprop = 42;
assert.strictEqual(util.inspect(value), '{ [Function] aprop: 42 }');
}
// Regular expressions with properties
{
const value = /123/ig;
value.aprop = 42;
assert.strictEqual(util.inspect(value), '{ /123/gi aprop: 42 }');
}
// Dates with properties
{
const value = new Date('Sun, 14 Feb 2010 11:48:40 GMT');
value.aprop = 42;
assert.strictEqual(util.inspect(value),
'{ 2010-02-14T11:48:40.000Z aprop: 42 }');
}
// test the internal isDate implementation // test the internal isDate implementation
var Date2 = require('vm').runInNewContext('Date'); var Date2 = require('vm').runInNewContext('Date');
var d = new Date2(); var d = new Date2();
@ -34,12 +406,40 @@ var after = util.inspect(d);
assert.equal(orig, after); assert.equal(orig, after);
// test for sparse array // test for sparse array
var a = ['foo', 'bar', 'baz']; {
assert.equal(util.inspect(a), '[ \'foo\', \'bar\', \'baz\' ]'); const a = ['foo', 'bar', 'baz'];
delete a[1]; assert.strictEqual(util.inspect(a), '[ \'foo\', \'bar\', \'baz\' ]');
assert.equal(util.inspect(a), '[ \'foo\', , \'baz\' ]'); delete a[1];
assert.equal(util.inspect(a, true), '[ \'foo\', , \'baz\', [length]: 3 ]'); assert.strictEqual(util.inspect(a), '[ \'foo\', <1 empty item>, \'baz\' ]');
assert.equal(util.inspect(new Array(5)), '[ , , , , ]'); assert.strictEqual(
util.inspect(a, true),
'[ \'foo\', <1 empty item>, \'baz\', [length]: 3 ]'
);
assert.strictEqual(util.inspect(new Array(5)), '[ <5 empty items> ]');
a[3] = 'bar';
a[100] = 'qux';
assert.strictEqual(
util.inspect(a, { breakLength: Infinity }),
'[ \'foo\', <1 empty item>, \'baz\', \'bar\', <96 empty items>, \'qux\' ]'
);
delete a[3];
assert.strictEqual(
util.inspect(a, { maxArrayLength: 4 }),
'[ \'foo\', <1 empty item>, \'baz\', <97 empty items>, ... 1 more item ]'
);
}
// test for other constructors in different context
{
let obj = vm.runInNewContext('(function(){return {}})()', {});
assert.strictEqual(util.inspect(obj), '{}');
obj = vm.runInNewContext('var m=new Map();m.set(1,2);m', {});
assert.strictEqual(util.inspect(obj), 'Map { 1 => 2 }');
obj = vm.runInNewContext('var s=new Set();s.add(1);s.add(2);s', {});
assert.strictEqual(util.inspect(obj), 'Set { 1, 2 }');
obj = vm.runInNewContext('fn=function(){};new Promise(fn,fn)', {});
assert.strictEqual(util.inspect(obj), 'Promise { }');
}
// test for property descriptors // test for property descriptors
var getter = Object.create(null, { var getter = Object.create(null, {
@ -63,25 +463,29 @@ assert.equal(util.inspect(setter, true), '{ [b]: [Setter] }');
assert.equal(util.inspect(getterAndSetter, true), '{ [c]: [Getter/Setter] }'); assert.equal(util.inspect(getterAndSetter, true), '{ [c]: [Getter/Setter] }');
// exceptions should print the error message, not '{}' // exceptions should print the error message, not '{}'
assert.equal(util.inspect(new Error()), '[Error]'); {
assert.equal(util.inspect(new Error('FAIL')), '[Error: FAIL]'); var errors = [];
assert.equal(util.inspect(new TypeError('FAIL')), '[TypeError: FAIL]'); errors.push(new Error());
assert.equal(util.inspect(new SyntaxError('FAIL')), '[SyntaxError: FAIL]'); errors.push(new Error('FAIL'));
try { errors.push(new TypeError('FAIL'));
undef(); errors.push(new SyntaxError('FAIL'));
} catch (e) { errors.forEach((err) => {
assert.equal(util.inspect(e), '[ReferenceError: undef is not defined]'); assert.strictEqual(util.inspect(err), err.stack);
} });
var ex = util.inspect(new Error('FAIL'), true); try {
assert.ok(ex.indexOf('[Error: FAIL]') != -1); undef(); // eslint-disable-line no-undef
/* Unsure why these don't work } catch (e) {
assert.ok(ex.indexOf('[stack]') != -1); assert.strictEqual(util.inspect(e), e.stack);
assert.ok(ex.indexOf('[message]') != -1); }
*/ var ex = util.inspect(new Error('FAIL'), true);
assert(ex.indexOf('Error: FAIL') !== -1);
assert(ex.indexOf('[stack]') !== -1);
assert(ex.indexOf('[message]') !== -1);
}
// GH-1941 // GH-1941
// should not throw: // should not throw:
assert.equal(util.inspect(Object.create(Date.prototype)), '{}'); assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}');
// GH-1944 // GH-1944
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
@ -90,6 +494,12 @@ assert.doesNotThrow(function() {
util.inspect(d); util.inspect(d);
}); });
assert.doesNotThrow(function() {
var d = new Date();
d.toISOString = null;
util.inspect(d);
});
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
var r = /regexp/; var r = /regexp/;
r.toString = null; r.toString = null;
@ -108,7 +518,7 @@ var x = { inspect: util.inspect };
assert.ok(util.inspect(x).indexOf('inspect') != -1); assert.ok(util.inspect(x).indexOf('inspect') != -1);
// util.inspect.styles and util.inspect.colors // util.inspect.styles and util.inspect.colors
function test_color_style(style, input, implicit) { function testColorStyle(style, input, implicit) {
var color_name = util.inspect.styles[style]; var color_name = util.inspect.styles[style];
var color = ['', '']; var color = ['', ''];
if(util.inspect.colors[color_name]) if(util.inspect.colors[color_name])
@ -121,14 +531,14 @@ function test_color_style(style, input, implicit) {
assert.equal(with_color, expect, 'util.inspect color for style '+style); assert.equal(with_color, expect, 'util.inspect color for style '+style);
} }
test_color_style('special', function(){}); testColorStyle('special', function(){});
test_color_style('number', 123.456); testColorStyle('number', 123.456);
test_color_style('boolean', true); testColorStyle('boolean', true);
test_color_style('undefined', undefined); testColorStyle('undefined', undefined);
test_color_style('null', null); testColorStyle('null', null);
test_color_style('string', 'test string'); testColorStyle('string', 'test string');
test_color_style('date', new Date); testColorStyle('date', new Date);
test_color_style('regexp', /regexp/); testColorStyle('regexp', /regexp/);
// an object with "hasOwnProperty" overwritten should not throw // an object with "hasOwnProperty" overwritten should not throw
assert.doesNotThrow(function() { assert.doesNotThrow(function() {

675
util.js

@ -29,6 +29,94 @@ var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors ||
return descriptors; return descriptors;
}; };
var internalUtil = require('./internal/util');
var removeColors = internalUtil.removeColors;
var getConstructorOf = internalUtil.getConstructorOf;
var isTypedArray = require('is-typed-array');
var propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
var regExpToString = RegExp.prototype.toString;
var dateToISOString = Date.prototype.toISOString;
var errorToString = Error.prototype.toString;
var strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/;
var strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g;
var keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
var numberRegExp = /^(0|[1-9][0-9]*)$/;
function isSet(obj) {
return objectToString(obj) === '[object Set]';
}
function isMap(obj) {
return objectToString(obj) === '[object Map]';
}
function isSetIterator(obj) {
return objectToString(obj) === '[object Set Iterator]';
}
function isMapIterator(obj) {
return objectToString(obj) === '[object Map Iterator]';
}
function isAnyArrayBuffer(obj) {
return objectToString(obj) === '[object ArrayBuffer]' ||
objectToString(obj) === '[object SharedArrayBuffer]';
}
function isDataView(obj) {
return objectToString(obj) === '[object DataView]';
}
function isPromise(obj) {
return objectToString(obj) === '[object Promise]';
}
// Escaped special characters. Use empty strings to fill up unused entries.
var meta = [
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
'\\u0005', '\\u0006', '\\u0007', '\\b', '\\t',
'\\n', '\\u000b', '\\f', '\\r', '\\u000e',
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
'\\u001e', '\\u001f', '', '', '',
'', '', '', '', "\\'", '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '\\\\'
];
function escapeFn (str) {
return meta[str.charCodeAt(0)];
}
// Escape control characters, single quotes and the backslash.
// This is similar to JSON stringify escaping.
function strEscape(str) {
// Some magic numbers that worked out fine while benchmarking with v8 6.0
if (str.length < 5000 && !strEscapeSequencesRegExp.test(str))
return '\'' + str + '\'';
if (str.length > 100)
return '\'' + str.replace(strEscapeSequencesReplacer, escapeFn) + '\'';
var result = '';
var last = 0;
for (var i = 0; i < str.length; i++) {
var point = str.charCodeAt(i);
if (point === 39 || point === 92 || point < 32) {
if (last === i) {
result += meta[point];
} else {
result += str.slice(last, i) + meta[point];
}
last = i + 1;
}
}
if (last === 0) {
result = str;
} else if (last !== i) {
result += str.slice(last);
}
return '\'' + result + '\'';
}
var formatRegExp = /%[sdj%]/g; var formatRegExp = /%[sdj%]/g;
exports.format = function(f) { exports.format = function(f) {
if (!isString(f)) { if (!isString(f)) {
@ -123,6 +211,7 @@ exports.debuglog = function(set) {
return debugs[set]; return debugs[set];
}; };
var customInspectSymbol = typeof Symbol !== 'undefined' ? Symbol('util.inspect.custom') : undefined;
/** /**
* Echos the value of a value. Trys to print the value out * Echos the value of a value. Trys to print the value out
@ -135,6 +224,8 @@ exports.debuglog = function(set) {
function inspect(obj, opts) { function inspect(obj, opts) {
// default options // default options
var ctx = { var ctx = {
maxArrayLength: 100,
breakLength: 60,
seen: [], seen: [],
stylize: stylizeNoColor stylize: stylizeNoColor
}; };
@ -157,7 +248,7 @@ function inspect(obj, opts) {
return formatValue(ctx, obj, ctx.depth); return formatValue(ctx, obj, ctx.depth);
} }
exports.inspect = inspect; exports.inspect = inspect;
inspect.custom = customInspectSymbol;
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = { inspect.colors = {
@ -217,244 +308,464 @@ function arrayToHash(array) {
return hash; return hash;
} }
function formatValue(ctx, value, recurseTimes, ln) {
// Primitive types cannot have properties
if (typeof value !== 'object' && typeof value !== 'function') {
return formatPrimitive(ctx.stylize, value);
}
if (value === null) {
return ctx.stylize('null', 'null');
}
function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions. // Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it // Check that value is an object with an inspect function on it
if (ctx.customInspect && if (ctx.customInspect && customInspectSymbol) {
value && var maybeCustomInspect = value[customInspectSymbol] || value.inspect;
isFunction(value.inspect) &&
// Filter out the util module, it's inspect function is special if (typeof maybeCustomInspect === 'function' &&
value.inspect !== exports.inspect && // Filter out the util module, its inspect function is special
// Also filter out any prototype objects using the circular check. maybeCustomInspect !== exports.inspect &&
!(value.constructor && value.constructor.prototype === value)) { // Also filter out any prototype objects using the circular check.
var ret = value.inspect(recurseTimes, ctx); !(value.constructor && value.constructor.prototype === value)) {
if (!isString(ret)) { var ret = maybeCustomInspect.call(value, recurseTimes, ctx);
ret = formatValue(ctx, ret, recurseTimes);
// If the custom inspection method returned `this`, don't go into
// infinite recursion.
if (ret !== value) {
if (typeof ret !== 'string') {
return formatValue(ctx, ret, recurseTimes);
}
return ret;
}
} }
return ret;
} }
// Primitive types cannot have properties var keys;
var primitive = formatPrimitive(ctx, value); var symbols = typeof Symbol !== 'undefined' ? Object.getOwnPropertySymbols(value) : [];
if (primitive) {
return primitive;
}
// Look up the keys of the object. // Look up the keys of the object.
var keys = Object.keys(value);
var visibleKeys = arrayToHash(keys);
if (ctx.showHidden) { if (ctx.showHidden) {
keys = Object.getOwnPropertyNames(value); keys = Object.getOwnPropertyNames(value);
} } else {
keys = Object.keys(value);
// IE doesn't make error fields non-enumerable if (symbols.length !== 0)
// http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx symbols = symbols.filter((key) => propertyIsEnumerable.call(value, key));
if (isError(value) }
&& (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
return formatError(value); var keyLength = keys.length + symbols.length;
} var constructor = getConstructorOf(value);
var ctorName = constructor && constructor.name ?
// Some type of object without properties can be shortcutted. constructor.name + ' ' : '';
if (keys.length === 0) {
if (isFunction(value)) { var base = '';
var name = value.name ? ': ' + value.name : ''; var formatter = formatObject;
return ctx.stylize('[Function' + name + ']', 'special'); var braces;
} var noIterator = true;
if (isRegExp(value)) { var raw;
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
} // Iterators and the rest are split to reduce checks
if (isDate(value)) { if (value[Symbol.iterator]) {
return ctx.stylize(Date.prototype.toString.call(value), 'date'); noIterator = false;
if (Array.isArray(value)) {
// Only set the constructor for non ordinary ("Array [...]") arrays.
braces = [(ctorName === 'Array ' ? '' : ctorName) + '[', ']'];
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 + '{', '}'];
formatter = formatSet;
} else if (isMap(value)) {
if (value.size === 0 && keyLength === 0)
return ctorName + '{}';
braces = [ctorName + '{', '}'];
formatter = formatMap;
} else if (isTypedArray(value)) {
braces = [ctorName + '[', ']'];
formatter = formatTypedArray;
} else if (isMapIterator(value)) {
braces = ['MapIterator {', '}'];
formatter = formatCollectionIterator;
} else if (isSetIterator(value)) {
braces = ['SetIterator {', '}'];
formatter = formatCollectionIterator;
} else {
// Check for boxed strings with valueOf()
// The .valueOf() call can fail for a multitude of reasons
try {
raw = value.valueOf();
} catch (e) { /* ignore */ }
if (typeof raw === 'string') {
var formatted = formatPrimitive(stylizeNoColor, raw);
if (keyLength === raw.length)
return ctx.stylize('[String: ' + formatted + ']', 'string');
base = ' [String: ' + formatted + ']';
// For boxed Strings, we have to remove the 0-n indexed entries,
// since they just noisy up the output and are redundant
// Make boxed primitive Strings look like such
keys = keys.slice(value.length);
braces = ['{', '}'];
} else {
noIterator = true;
}
} }
if (isError(value)) { }
return formatError(value); if (noIterator) {
braces = ['{', '}'];
if (ctorName === 'Object ') {
// Object fast path
if (keyLength === 0)
return '{}';
} else if (typeof value === 'function') {
var name = constructor.name + (value.name ? ': ' + value.name : '');
if (keyLength === 0)
return ctx.stylize('[' + name + ']', 'special');
base = ' [' + name + ']';
} else if (isRegExp(value)) {
// Make RegExps say that they are RegExps
if (keyLength === 0 || recurseTimes < 0)
return ctx.stylize(regExpToString.call(value), 'regexp');
base = ' ' + regExpToString.call(value);
} else if (isDate(value)) {
if (keyLength === 0) {
if (Number.isNaN(value.getTime()))
return ctx.stylize(value.toString(), 'date');
return ctx.stylize(dateToISOString.call(value), 'date');
}
// Make dates with properties first say the date
base = ' ' + dateToISOString.call(value);
} else if (isError(value)) {
// Make error with message first say the error
if (keyLength === 0)
return formatError(value);
base = ' ' + formatError(value);
} else if (isAnyArrayBuffer(value)) {
// Fast path for ArrayBuffer and SharedArrayBuffer.
// 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 +
'{ byteLength: ' + formatNumber(ctx.stylize, value.byteLength) + ' }';
braces[0] = ctorName + '{';
keys.unshift('byteLength');
} else if (isDataView(value)) {
braces[0] = ctorName + '{';
// .buffer goes last, it's not a primitive like the others.
keys.unshift('byteLength', 'byteOffset', 'buffer');
} else if (isPromise(value)) {
braces[0] = ctorName + '{';
formatter = formatPromise;
} else {
// Check boxed primitives other than string with valueOf()
// NOTE: `Date` has to be checked first!
// The .valueOf() call can fail for a multitude of reasons
try {
raw = value.valueOf();
} catch (e) { /* ignore */ }
if (typeof raw === 'number') {
// Make boxed primitive Numbers look like such
var formatted = formatPrimitive(stylizeNoColor, raw);
if (keyLength === 0)
return ctx.stylize(`[Number: ${formatted}]`, 'number');
base = ` [Number: ${formatted}]`;
} else if (typeof raw === 'boolean') {
// Make boxed primitive Booleans look like such
var formatted = formatPrimitive(stylizeNoColor, raw);
if (keyLength === 0)
return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
base = ' [Boolean: ' + formatted + ']';
} else if (typeof raw === 'symbol') {
var formatted = formatPrimitive(stylizeNoColor, raw);
return ctx.stylize('[Symbol: ' + formatted + ']', 'symbol');
} else if (keyLength === 0) {
return ctorName + '{}';
} else {
braces[0] = ctorName + '{';
}
} }
} }
var base = '', array = false, braces = ['{', '}']; // 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.
if (ctx.seen.indexOf(value) !== -1)
return ctx.stylize('[Circular]', 'special');
// Make Array say that they are Array if (recurseTimes != null) {
if (isArray(value)) { if (recurseTimes < 0)
array = true; return ctx.stylize('[' + (constructor ? constructor.name : 'Object') + ']',
braces = ['[', ']']; 'special');
recurseTimes -= 1;
} }
// Make functions say that they are functions ctx.seen.push(value);
if (isFunction(value)) { var output = formatter(ctx, value, recurseTimes, keys);
var n = value.name ? ': ' + value.name : '';
base = ' [Function' + n + ']';
}
// Make RegExps say that they are RegExps
if (isRegExp(value)) {
base = ' ' + RegExp.prototype.toString.call(value);
}
// Make dates with properties first say the date for (var i = 0; i < symbols.length; i++) {
if (isDate(value)) { output.push(formatProperty(ctx, value, recurseTimes, symbols[i], 0));
base = ' ' + Date.prototype.toUTCString.call(value);
} }
ctx.seen.pop();
// Make error with message first say the error return reduceToSingleString(ctx, output, base, braces, ln);
if (isError(value)) { }
base = ' ' + formatError(value);
}
if (keys.length === 0 && (!array || value.length == 0)) {
return braces[0] + base + braces[1];
}
if (recurseTimes < 0) {
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
} else {
return ctx.stylize('[Object]', 'special');
}
}
ctx.seen.push(value); function formatNumber(fn, value) {
// Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0.
if (value === 0 && (1 / value) === -Infinity)
return fn('-0', 'number');
return fn(String(value), 'number');
}
var output; function formatPrimitive(fn, value) {
if (array) { if (typeof value === 'string')
output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); return fn(strEscape(value), 'string');
} else { if (typeof value === 'number')
output = keys.map(function(key) { return formatNumber(fn, value);
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); if (typeof value === 'boolean')
}); return fn(String(value), 'boolean');
} if (typeof value === 'undefined')
return fn('undefined', 'undefined');
// es6 symbol primitive
return fn(value.toString(), 'symbol');
}
ctx.seen.pop();
return reduceToSingleString(output, base, braces); function formatError(value) {
return value.stack || '[' + errorToString.call(value) + ']';
} }
function formatObject(ctx, value, recurseTimes, keys) {
var len = keys.length;
var output = new Array(len);
for (var i = 0; i < len; i++)
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 0);
return output;
}
function formatPrimitive(ctx, value) { // The array is sparse and/or has extra keys
if (isUndefined(value)) function formatSpecialArray(ctx, value, recurseTimes, keys, maxLength, valLen) {
return ctx.stylize('undefined', 'undefined'); var output = [];
if (isString(value)) { var keyLen = keys.length;
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') var visibleLength = 0;
.replace(/'/g, "\\'") var i = 0;
.replace(/\\"/g, '"') + '\''; if (keyLen !== 0 && numberRegExp.test(keys[0])) {
return ctx.stylize(simple, 'string'); for (var key of keys) {
if (visibleLength === maxLength)
break;
var index = +key;
// Arrays can only have up to 2^32 - 1 entries
if (index > 2 ** 32 - 2)
break;
if (i !== index) {
if (!numberRegExp.test(key))
break;
var emptyItems = index - i;
var ending = emptyItems > 1 ? 's' : '';
var message = '<' + emptyItems + ' empty item' + ending + '>';
output.push(ctx.stylize(message, 'undefined'));
i = index;
if (++visibleLength === maxLength)
break;
}
output.push(formatProperty(ctx, value, recurseTimes, key, 1));
visibleLength++;
i++;
}
} }
if (isNumber(value)) if (i < valLen && visibleLength !== maxLength) {
return ctx.stylize('' + value, 'number'); var len = valLen - i;
if (isBoolean(value)) var ending = len > 1 ? 's' : '';
return ctx.stylize('' + value, 'boolean'); var message = '<' + len + ' empty item' + ending + '>';
// For some reason typeof null is "object", so special case here. output.push(ctx.stylize(message, 'undefined'));
if (isNull(value)) i = valLen;
return ctx.stylize('null', 'null'); if (keyLen === 0)
return output;
}
var remaining = valLen - i;
if (remaining > 0) {
output.push('... ' + remaining + ' more item' + (remaining > 1 ? 's' : ''));
}
if (ctx.showHidden && keys[keyLen - 1] === 'length') {
// No extra keys
output.push(formatProperty(ctx, value, recurseTimes, 'length', 2));
} else if (valLen === 0 ||
keyLen > valLen && keys[valLen - 1] === String(valLen - 1)) {
// The array is not sparse
for (i = valLen; i < keyLen; i++)
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2));
} else if (keys[keyLen - 1] !== String(valLen - 1)) {
var extra = [];
// Only handle special keys
var key;
for (i = keys.length - 1; i >= 0; i--) {
key = keys[i];
if (numberRegExp.test(key) && +key < Math.pow(2, 32) - 1)
break;
extra.push(formatProperty(ctx, value, recurseTimes, key, 2));
}
for (i = extra.length - 1; i >= 0; i--)
output.push(extra[i]);
}
return output;
} }
function formatArray(ctx, value, recurseTimes, keys) {
var len = Math.min(Math.max(0, ctx.maxArrayLength), value.length);
var hidden = ctx.showHidden ? 1 : 0;
var valLen = value.length;
var keyLen = keys.length - hidden;
if (keyLen !== valLen || keys[keyLen - 1] !== String(valLen - 1))
return formatSpecialArray(ctx, value, recurseTimes, keys, len, valLen);
var remaining = valLen - len;
var output = new Array(len + (remaining > 0 ? 1 : 0) + hidden);
for (var i = 0; i < len; i++)
output[i] = formatProperty(ctx, value, recurseTimes, keys[i], 1);
if (remaining > 0)
output[i++] = '... ' + remaining + ' more item' + (remaining > 1 ? 's' : '');
if (ctx.showHidden === true)
output[i] = formatProperty(ctx, value, recurseTimes, 'length', 2);
return output;
}
function formatError(value) { function formatTypedArray(ctx, value, recurseTimes, keys) {
return '[' + Error.prototype.toString.call(value) + ']'; var maxLength = Math.min(Math.max(0, ctx.maxArrayLength), value.length);
var remaining = value.length - maxLength;
var output = new Array(maxLength + (remaining > 0 ? 1 : 0));
for (var i = 0; i < maxLength; ++i)
output[i] = formatNumber(ctx.stylize, value[i]);
if (remaining > 0)
output[i] = '... ' + remaining + ' more item' + (remaining > 1 ? 's' : '');
if (ctx.showHidden) {
// .buffer goes last, it's not a primitive like the others.
var extraKeys = [
'BYTES_PER_ELEMENT',
'length',
'byteLength',
'byteOffset',
'buffer'
];
for (i = 0; i < extraKeys.length; i++) {
var str = formatValue(ctx, value[extraKeys[i]], recurseTimes);
output.push('[' + extraKeys[i] + ']: ' + str);
}
}
// TypedArrays cannot have holes. Therefore it is safe to assume that all
// extra keys are indexed after value.length.
for (i = value.length; i < keys.length; i++) {
output.push(formatProperty(ctx, value, recurseTimes, keys[i], 2));
}
return output;
} }
function formatSet(ctx, value, recurseTimes, keys) {
var output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0));
var i = 0;
for (var v of value)
output[i++] = formatValue(ctx, v, recurseTimes);
// With `showHidden`, `length` will display as a hidden property for
// arrays. For consistency's sake, do the same for `size`, even though this
// property isn't selected by Object.getOwnPropertyNames().
if (ctx.showHidden)
output[i++] = '[size]: ' + ctx.stylize(String(value.size), 'number');
for (var n = 0; n < keys.length; n++) {
output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0);
}
return output;
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { function formatMap(ctx, value, recurseTimes, keys) {
var output = []; var output = new Array(value.size + keys.length + (ctx.showHidden ? 1 : 0));
for (var i = 0, l = value.length; i < l; ++i) { var i = 0;
if (hasOwnProperty(value, String(i))) { for (var [k, v] of value)
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, output[i++] = formatValue(ctx, k, recurseTimes) + ' => ' +
String(i), true)); formatValue(ctx, v, recurseTimes);
} else { // See comment in formatSet
output.push(''); if (ctx.showHidden)
} output[i++] = '[size]: ' + ctx.stylize(String(value.size), 'number');
for (var n = 0; n < keys.length; n++) {
output[i++] = formatProperty(ctx, value, recurseTimes, keys[n], 0);
} }
keys.forEach(function(key) {
if (!key.match(/^\d+$/)) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, true));
}
});
return output; return output;
} }
function formatPromise(ctx, value, recurseTimes, keys) {
var output = [];
for (var n = 0; n < keys.length; n++) {
output.push(formatProperty(ctx, value, recurseTimes, keys[n], 0));
}
return output;
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { function formatProperty(ctx, value, recurseTimes, key, array) {
var name, str, desc; var name, str;
desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; var desc = Object.getOwnPropertyDescriptor(value, key) ||
if (desc.get) { { value: value[key], enumerable: true };
if (desc.set) { if (desc.value !== undefined) {
var diff = array === 0 ? 3 : 2;
ctx.indentationLvl += diff;
str = formatValue(ctx, desc.value, recurseTimes, array === 0);
ctx.indentationLvl -= diff;
} else if (desc.get !== undefined) {
if (desc.set !== undefined) {
str = ctx.stylize('[Getter/Setter]', 'special'); str = ctx.stylize('[Getter/Setter]', 'special');
} else { } else {
str = ctx.stylize('[Getter]', 'special'); str = ctx.stylize('[Getter]', 'special');
} }
} else if (desc.set !== undefined) {
str = ctx.stylize('[Setter]', 'special');
} else { } else {
if (desc.set) { str = ctx.stylize('undefined', 'undefined');
str = ctx.stylize('[Setter]', 'special');
}
} }
if (!hasOwnProperty(visibleKeys, key)) { if (array === 1) {
name = '[' + key + ']'; return str;
}
if (!str) {
if (ctx.seen.indexOf(desc.value) < 0) {
if (isNull(recurseTimes)) {
str = formatValue(ctx, desc.value, null);
} else {
str = formatValue(ctx, desc.value, recurseTimes - 1);
}
if (str.indexOf('\n') > -1) {
if (array) {
str = str.split('\n').map(function(line) {
return ' ' + line;
}).join('\n').substr(2);
} else {
str = '\n' + str.split('\n').map(function(line) {
return ' ' + line;
}).join('\n');
}
}
} else {
str = ctx.stylize('[Circular]', 'special');
}
} }
if (isUndefined(name)) { if (typeof key === 'symbol') {
if (array && key.match(/^\d+$/)) { name = '[' + ctx.stylize(key.toString(), 'symbol') + ']';
return str; } else if (desc.enumerable === false) {
} name = '[' + key + ']';
name = JSON.stringify('' + key); } else if (keyStrRegExp.test(key)) {
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = ctx.stylize(key, 'name');
name = name.substr(1, name.length - 2); } else {
name = ctx.stylize(name, 'name'); name = ctx.stylize(strEscape(key), 'string');
} else {
name = name.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'");
name = ctx.stylize(name, 'string');
}
} }
return name + ': ' + str; return name + ': ' + str;
} }
function reduceToSingleString(output, base, braces) { function reduceToSingleString(ctx, output, base, braces, addLn) {
var numLinesEst = 0; var breakLength = ctx.breakLength;
var length = output.reduce(function(prev, cur) { if (output.length * 2 <= breakLength) {
numLinesEst++; var length = 0;
if (cur.indexOf('\n') >= 0) numLinesEst++; for (var i = 0; i < output.length && length <= breakLength; i++) {
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; if (ctx.colors) {
}, 0); length += removeColors(output[i]).length + 1;
} else {
if (length > 60) { length += output[i].length + 1;
return braces[0] + }
(base === '' ? '' : base + '\n ') + }
' ' + if (length <= breakLength)
output.join(',\n ') + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
' ' + }
braces[1]; // If the opening "brace" is too large, like in the case of "Set {",
} // we need to force the first item to be on the next line or the
// items will not line up correctly.
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; var indentation = ' '.repeat(ctx.indentationLvl);
var extraLn = addLn === true ? '\n' + indentation : '';
var ln = base === '' && braces[0].length === 1 ?
' ' : base + '\n' + indentation + ' ';
var str = output.join(',\n' + indentation + ' ');
return extraLn + braces[0] + ln + str + ' ' + braces[1];
} }
// NOTE: These type checking functions intentionally don't use `instanceof` // NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`. // because it is fragile and can be easily faked with `Object.create()`.
function isArray(ar) { function isArray(ar) {

Loading…
Cancel
Save