node/lib/util.js
Evan Lucas 24012a879d util: make inspect more reliable
34a35919e1 added pretty printing for
TypedArray, ArrayBuffer, and DataView. This change allows inspecting
those across different contexts.

Since instanceof does not work across contexts, we can use
v8::Value::IsTypedArray, v8::Value::IsArrayBuffer, and
v8::Value::IsDataView

PR-URL: https://github.com/nodejs/node/pull/4098
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
2015-12-02 06:11:45 -06:00

889 lines
24 KiB
JavaScript

'use strict';
const uv = process.binding('uv');
const Buffer = require('buffer').Buffer;
const internalUtil = require('internal/util');
const binding = process.binding('util');
const isError = internalUtil.isError;
const objectToString = internalUtil.objectToString;
var Debug;
const formatRegExp = /%[sdj%]/g;
exports.format = function(f) {
if (typeof f !== 'string') {
var objects = [];
for (var i = 0; i < arguments.length; i++) {
objects.push(inspect(arguments[i]));
}
return objects.join(' ');
}
if (arguments.length === 1) return f;
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(formatRegExp, function(x) {
if (x === '%%') return '%';
if (i >= len) return x;
switch (x) {
case '%s': return String(args[i++]);
case '%d': return Number(args[i++]);
case '%j':
try {
return JSON.stringify(args[i++]);
} catch (_) {
return '[Circular]';
}
// falls through
default:
return x;
}
});
for (var x = args[i]; i < len; x = args[++i]) {
if (x === null || (typeof x !== 'object' && typeof x !== 'symbol')) {
str += ' ' + x;
} else {
str += ' ' + inspect(x);
}
}
return str;
};
exports.deprecate = internalUtil._deprecate;
var debugs = {};
var debugEnviron;
exports.debuglog = function(set) {
if (debugEnviron === undefined)
debugEnviron = process.env.NODE_DEBUG || '';
set = set.toUpperCase();
if (!debugs[set]) {
if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
var pid = process.pid;
debugs[set] = function() {
var msg = exports.format.apply(exports, arguments);
console.error('%s %d: %s', set, pid, msg);
};
} else {
debugs[set] = function() {};
}
}
return debugs[set];
};
/**
* Echos the value of a value. Trys to print the value out
* in the best way possible given the different types.
*
* @param {Object} obj The object to print out.
* @param {Object} opts Optional options object that alters the output.
*/
/* legacy: obj, showHidden, depth, colors*/
function inspect(obj, opts) {
// default options
var ctx = {
seen: [],
stylize: stylizeNoColor
};
// legacy...
if (arguments.length >= 3) ctx.depth = arguments[2];
if (arguments.length >= 4) ctx.colors = arguments[3];
if (typeof opts === 'boolean') {
// legacy...
ctx.showHidden = opts;
} else if (opts) {
// got an "options" object
exports._extend(ctx, opts);
}
// set default options
if (ctx.showHidden === undefined) ctx.showHidden = false;
if (ctx.depth === undefined) ctx.depth = 2;
if (ctx.colors === undefined) ctx.colors = false;
if (ctx.customInspect === undefined) ctx.customInspect = true;
if (ctx.colors) ctx.stylize = stylizeWithColor;
return formatValue(ctx, obj, ctx.depth);
}
exports.inspect = inspect;
// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = {
'bold' : [1, 22],
'italic' : [3, 23],
'underline' : [4, 24],
'inverse' : [7, 27],
'white' : [37, 39],
'grey' : [90, 39],
'black' : [30, 39],
'blue' : [34, 39],
'cyan' : [36, 39],
'green' : [32, 39],
'magenta' : [35, 39],
'red' : [31, 39],
'yellow' : [33, 39]
};
// Don't use 'blue' not visible on cmd.exe
inspect.styles = {
'special': 'cyan',
'number': 'yellow',
'boolean': 'yellow',
'undefined': 'grey',
'null': 'bold',
'string': 'green',
'symbol': 'green',
'date': 'magenta',
// "name": intentionally not styling
'regexp': 'red'
};
function stylizeWithColor(str, styleType) {
var style = inspect.styles[styleType];
if (style) {
return '\u001b[' + inspect.colors[style][0] + 'm' + str +
'\u001b[' + inspect.colors[style][1] + 'm';
} else {
return str;
}
}
function stylizeNoColor(str, styleType) {
return str;
}
function arrayToHash(array) {
var hash = Object.create(null);
array.forEach(function(val) {
hash[val] = true;
});
return hash;
}
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;
}
function ensureDebugIsInitialized() {
if (Debug === undefined) {
const runInDebugContext = require('vm').runInDebugContext;
Debug = runInDebugContext('Debug');
}
}
function inspectPromise(p) {
ensureDebugIsInitialized();
// Only create a mirror if the object is a Promise.
if (!binding.isPromise(p))
return null;
const mirror = Debug.MakeMirror(p, true);
return {status: mirror.status(), value: mirror.promiseValue().value_};
}
function formatValue(ctx, value, recurseTimes) {
// Provide a hook for user-specified inspect functions.
// Check that value is an object with an inspect function on it
if (ctx.customInspect &&
value &&
typeof value.inspect === 'function' &&
// Filter out the util module, it's inspect function is special
value.inspect !== exports.inspect &&
// Also filter out any prototype objects using the circular check.
!(value.constructor && value.constructor.prototype === value)) {
var ret = value.inspect(recurseTimes, ctx);
if (typeof ret !== 'string') {
ret = formatValue(ctx, ret, recurseTimes);
}
return ret;
}
// Primitive types cannot have properties
var primitive = formatPrimitive(ctx, value);
if (primitive) {
return primitive;
}
// Look up the keys of the object.
var keys = Object.keys(value);
var visibleKeys = arrayToHash(keys);
if (ctx.showHidden) {
keys = Object.getOwnPropertyNames(value);
keys = keys.concat(Object.getOwnPropertySymbols(value));
}
// This could be a boxed primitive (new String(), etc.), check valueOf()
// NOTE: Avoid calling `valueOf` on `Date` instance because it will return
// a number which, when object has some additional user-stored `keys`,
// will be printed out.
var formatted;
var raw = value;
try {
// the .valueOf() call can fail for a multitude of reasons
if (!isDate(value))
raw = value.valueOf();
} catch (e) {
// ignore...
}
if (typeof raw === 'string') {
// for boxed Strings, we have to remove the 0-n indexed entries,
// since they just noisey up the output and are redundant
keys = keys.filter(function(key) {
return !(key >= 0 && key < raw.length);
});
}
// Some type of object without properties can be shortcutted.
if (keys.length === 0) {
if (typeof value === 'function') {
var name = value.name ? ': ' + value.name : '';
return ctx.stylize('[Function' + name + ']', 'special');
}
if (isRegExp(value)) {
return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
}
if (isDate(value)) {
return ctx.stylize(Date.prototype.toString.call(value), 'date');
}
if (isError(value)) {
return formatError(value);
}
// now check the `raw` value to handle boxed primitives
if (typeof raw === 'string') {
formatted = formatPrimitiveNoColor(ctx, raw);
return ctx.stylize('[String: ' + formatted + ']', 'string');
}
if (typeof raw === 'number') {
formatted = formatPrimitiveNoColor(ctx, raw);
return ctx.stylize('[Number: ' + formatted + ']', 'number');
}
if (typeof raw === 'boolean') {
formatted = formatPrimitiveNoColor(ctx, raw);
return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
}
// Fast path for ArrayBuffer. Can't do the same for DataView because it
// has a non-primitive .buffer property that we need to recurse for.
if (binding.isArrayBuffer(value)) {
return `${getConstructorOf(value).name}` +
` { byteLength: ${formatNumber(ctx, value.byteLength)} }`;
}
}
var constructor = getConstructorOf(value);
var base = '', empty = false, braces;
var formatter = formatObject;
// We can't compare constructors for various objects using a comparison like
// `constructor === Array` because the object could have come from a different
// context and thus the constructor won't match. Instead we check the
// constructor names (including those up the prototype chain where needed) to
// determine object types.
if (Array.isArray(value)) {
// Unset the constructor to prevent "Array [...]" for ordinary arrays.
if (constructor && constructor.name === 'Array')
constructor = null;
braces = ['[', ']'];
empty = value.length === 0;
formatter = formatArray;
} else if (objectToString(value) === '[object Set]') {
braces = ['{', '}'];
// 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)
keys.unshift('size');
empty = value.size === 0;
formatter = formatSet;
} else if (objectToString(value) === '[object Map]') {
braces = ['{', '}'];
// Ditto.
if (ctx.showHidden)
keys.unshift('size');
empty = value.size === 0;
formatter = formatMap;
} else if (binding.isArrayBuffer(value)) {
braces = ['{', '}'];
keys.unshift('byteLength');
visibleKeys.byteLength = true;
} else if (binding.isDataView(value)) {
braces = ['{', '}'];
// .buffer goes last, it's not a primitive like the others.
keys.unshift('byteLength', 'byteOffset', 'buffer');
visibleKeys.byteLength = true;
visibleKeys.byteOffset = true;
visibleKeys.buffer = true;
} else if (binding.isTypedArray(value)) {
braces = ['[', ']'];
formatter = formatTypedArray;
if (ctx.showHidden) {
// .buffer goes last, it's not a primitive like the others.
keys.unshift('BYTES_PER_ELEMENT',
'length',
'byteLength',
'byteOffset',
'buffer');
}
} else {
var promiseInternals = inspectPromise(value);
if (promiseInternals) {
braces = ['{', '}'];
formatter = formatPromise;
} else {
if (binding.isMapIterator(value)) {
constructor = { name: 'MapIterator' };
braces = ['{', '}'];
empty = false;
formatter = formatCollectionIterator;
} else if (binding.isSetIterator(value)) {
constructor = { name: 'SetIterator' };
braces = ['{', '}'];
empty = false;
formatter = formatCollectionIterator;
} else {
// Unset the constructor to prevent "Object {...}" for ordinary objects.
if (constructor && constructor.name === 'Object')
constructor = null;
braces = ['{', '}'];
empty = true; // No other data than keys.
}
}
}
empty = empty === true && keys.length === 0;
// Make functions say that they are functions
if (typeof value === 'function') {
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
if (isDate(value)) {
base = ' ' + Date.prototype.toUTCString.call(value);
}
// Make error with message first say the error
if (isError(value)) {
base = ' ' + formatError(value);
}
// Make boxed primitive Strings look like such
if (typeof raw === 'string') {
formatted = formatPrimitiveNoColor(ctx, raw);
base = ' ' + '[String: ' + formatted + ']';
}
// Make boxed primitive Numbers look like such
if (typeof raw === 'number') {
formatted = formatPrimitiveNoColor(ctx, raw);
base = ' ' + '[Number: ' + formatted + ']';
}
// Make boxed primitive Booleans look like such
if (typeof raw === 'boolean') {
formatted = formatPrimitiveNoColor(ctx, raw);
base = ' ' + '[Boolean: ' + formatted + ']';
}
// Add constructor name if available
if (base === '' && constructor)
braces[0] = constructor.name + ' ' + braces[0];
if (empty === true) {
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);
var output = formatter(ctx, value, recurseTimes, visibleKeys, keys);
ctx.seen.pop();
return reduceToSingleString(output, base, braces);
}
function formatNumber(ctx, value) {
// Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
// so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
if (value === 0 && 1 / value < 0)
return ctx.stylize('-0', 'number');
return ctx.stylize('' + value, 'number');
}
function formatPrimitive(ctx, value) {
if (value === undefined)
return ctx.stylize('undefined', 'undefined');
// For some reason typeof null is "object", so special case here.
if (value === null)
return ctx.stylize('null', 'null');
var type = typeof value;
if (type === 'string') {
var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
.replace(/'/g, "\\'")
.replace(/\\"/g, '"') + '\'';
return ctx.stylize(simple, 'string');
}
if (type === 'number')
return formatNumber(ctx, value);
if (type === 'boolean')
return ctx.stylize('' + value, 'boolean');
// es6 symbol primitive
if (type === 'symbol')
return ctx.stylize(value.toString(), 'symbol');
}
function formatPrimitiveNoColor(ctx, value) {
var stylize = ctx.stylize;
ctx.stylize = stylizeNoColor;
var str = formatPrimitive(ctx, value);
ctx.stylize = stylize;
return str;
}
function formatError(value) {
return '[' + Error.prototype.toString.call(value) + ']';
}
function formatObject(ctx, value, recurseTimes, visibleKeys, keys) {
return keys.map(function(key) {
return formatProperty(ctx, value, recurseTimes, visibleKeys, key, false);
});
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
for (var i = 0, l = value.length; i < l; ++i) {
if (hasOwnProperty(value, String(i))) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
String(i), true));
} else {
output.push('');
}
}
keys.forEach(function(key) {
if (typeof key === 'symbol' || !key.match(/^\d+$/)) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, true));
}
});
return output;
}
function formatTypedArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = new Array(value.length);
for (var i = 0, l = value.length; i < l; ++i)
output[i] = formatNumber(ctx, value[i]);
for (const key of keys) {
if (typeof key === 'symbol' || !key.match(/^\d+$/)) {
output.push(
formatProperty(ctx, value, recurseTimes, visibleKeys, key, true));
}
}
return output;
}
function formatSet(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
value.forEach(function(v) {
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
var str = formatValue(ctx, v, nextRecurseTimes);
output.push(str);
});
keys.forEach(function(key) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, false));
});
return output;
}
function formatMap(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
value.forEach(function(v, k) {
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
var str = formatValue(ctx, k, nextRecurseTimes);
str += ' => ';
str += formatValue(ctx, v, nextRecurseTimes);
output.push(str);
});
keys.forEach(function(key) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, false));
});
return output;
}
function formatCollectionIterator(ctx, value, recurseTimes, visibleKeys, keys) {
ensureDebugIsInitialized();
const mirror = Debug.MakeMirror(value, true);
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
var vals = mirror.preview();
var output = [];
for (const o of vals) {
output.push(formatValue(ctx, o, nextRecurseTimes));
}
return output;
}
function formatPromise(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
var internals = inspectPromise(value);
if (internals.status === 'pending') {
output.push('<pending>');
} else {
var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1;
var str = formatValue(ctx, internals.value, nextRecurseTimes);
if (internals.status === 'rejected') {
output.push('<rejected> ' + str);
} else {
output.push(str);
}
}
keys.forEach(function(key) {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
key, false));
});
return output;
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
var name, str, desc;
desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
if (desc.get) {
if (desc.set) {
str = ctx.stylize('[Getter/Setter]', 'special');
} else {
str = ctx.stylize('[Getter]', 'special');
}
} else {
if (desc.set) {
str = ctx.stylize('[Setter]', 'special');
}
}
if (!hasOwnProperty(visibleKeys, key)) {
if (typeof key === 'symbol') {
name = '[' + ctx.stylize(key.toString(), 'symbol') + ']';
} else {
name = '[' + key + ']';
}
}
if (!str) {
if (ctx.seen.indexOf(desc.value) < 0) {
if (recurseTimes === null) {
str = formatValue(ctx, desc.value, null);
} else {
str = formatValue(ctx, desc.value, recurseTimes - 1);
}
if (str.indexOf('\n') > -1) {
if (array) {
str = str.replace(/\n/g, '\n ');
} else {
str = str.replace(/(^|\n)/g, '\n ');
}
}
} else {
str = ctx.stylize('[Circular]', 'special');
}
}
if (name === undefined) {
if (array && key.match(/^\d+$/)) {
return str;
}
name = JSON.stringify('' + key);
if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
name = name.substr(1, name.length - 2);
name = ctx.stylize(name, 'name');
} else {
name = name.replace(/'/g, "\\'")
.replace(/\\"/g, '"')
.replace(/(^"|"$)/g, "'")
.replace(/\\\\/g, '\\');
name = ctx.stylize(name, 'string');
}
}
return name + ': ' + str;
}
function reduceToSingleString(output, base, braces) {
var length = output.reduce(function(prev, cur) {
return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
}, 0);
if (length > 60) {
return braces[0] +
// 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.
(base === '' && braces[0].length === 1 ? '' : base + '\n ') +
' ' +
output.join(',\n ') +
' ' +
braces[1];
}
return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}
// NOTE: These type checking functions intentionally don't use `instanceof`
// because it is fragile and can be easily faked with `Object.create()`.
exports.isArray = Array.isArray;
function isBoolean(arg) {
return typeof arg === 'boolean';
}
exports.isBoolean = isBoolean;
function isNull(arg) {
return arg === null;
}
exports.isNull = isNull;
function isNullOrUndefined(arg) {
return arg === null || arg === undefined;
}
exports.isNullOrUndefined = isNullOrUndefined;
function isNumber(arg) {
return typeof arg === 'number';
}
exports.isNumber = isNumber;
function isString(arg) {
return typeof arg === 'string';
}
exports.isString = isString;
function isSymbol(arg) {
return typeof arg === 'symbol';
}
exports.isSymbol = isSymbol;
function isUndefined(arg) {
return arg === undefined;
}
exports.isUndefined = isUndefined;
function isRegExp(re) {
return objectToString(re) === '[object RegExp]';
}
exports.isRegExp = isRegExp;
function isObject(arg) {
return arg !== null && typeof arg === 'object';
}
exports.isObject = isObject;
function isDate(d) {
return objectToString(d) === '[object Date]';
}
exports.isDate = isDate;
exports.isError = isError;
function isFunction(arg) {
return typeof arg === 'function';
}
exports.isFunction = isFunction;
function isPrimitive(arg) {
return arg === null ||
typeof arg !== 'object' && typeof arg !== 'function';
}
exports.isPrimitive = isPrimitive;
exports.isBuffer = Buffer.isBuffer;
function pad(n) {
return n < 10 ? '0' + n.toString(10) : n.toString(10);
}
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'];
// 26 Feb 16:19:34
function timestamp() {
var d = new Date();
var time = [pad(d.getHours()),
pad(d.getMinutes()),
pad(d.getSeconds())].join(':');
return [d.getDate(), months[d.getMonth()], time].join(' ');
}
// log is just a thin wrapper to console.log that prepends a timestamp
exports.log = function() {
console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
};
/**
* Inherit the prototype methods from one constructor into another.
*
* The Function.prototype.inherits from lang.js rewritten as a standalone
* function (not on Function.prototype). NOTE: If this file is to be loaded
* during bootstrapping this function needs to be rewritten using some native
* functions as prototype setup using normal JavaScript does not work as
* expected during bootstrapping (see mirror.js in r114903).
*
* @param {function} ctor Constructor function which needs to inherit the
* prototype.
* @param {function} superCtor Constructor function to inherit prototype from.
* @throws {TypeError} Will error if either constructor is null, or if
* the super constructor lacks a prototype.
*/
exports.inherits = function(ctor, superCtor) {
if (ctor === undefined || ctor === null)
throw new TypeError('The constructor to "inherits" must not be ' +
'null or undefined');
if (superCtor === undefined || superCtor === null)
throw new TypeError('The super constructor to "inherits" must not ' +
'be null or undefined');
if (superCtor.prototype === undefined)
throw new TypeError('The super constructor to "inherits" must ' +
'have a prototype');
ctor.super_ = superCtor;
Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
};
exports._extend = function(origin, add) {
// Don't do anything if add isn't an object
if (add === null || typeof add !== 'object') return origin;
var keys = Object.keys(add);
var i = keys.length;
while (i--) {
origin[keys[i]] = add[keys[i]];
}
return origin;
};
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
// Deprecated old stuff.
exports.print = internalUtil.deprecate(function() {
for (var i = 0, len = arguments.length; i < len; ++i) {
process.stdout.write(String(arguments[i]));
}
}, 'util.print is deprecated. Use console.log instead.');
exports.puts = internalUtil.deprecate(function() {
for (var i = 0, len = arguments.length; i < len; ++i) {
process.stdout.write(arguments[i] + '\n');
}
}, 'util.puts is deprecated. Use console.log instead.');
exports.debug = internalUtil.deprecate(function(x) {
process.stderr.write('DEBUG: ' + x + '\n');
}, 'util.debug is deprecated. Use console.error instead.');
exports.error = internalUtil.deprecate(function(x) {
for (var i = 0, len = arguments.length; i < len; ++i) {
process.stderr.write(arguments[i] + '\n');
}
}, 'util.error is deprecated. Use console.error instead.');
exports._errnoException = function(err, syscall, original) {
var errname = uv.errname(err);
var message = syscall + ' ' + errname;
if (original)
message += ' ' + original;
var e = new Error(message);
e.code = errname;
e.errno = errname;
e.syscall = syscall;
return e;
};
exports._exceptionWithHostPort = function(err,
syscall,
address,
port,
additional) {
var details;
if (port && port > 0) {
details = address + ':' + port;
} else {
details = address;
}
if (additional) {
details += ' - Local (' + additional + ')';
}
var ex = exports._errnoException(err, syscall, details);
ex.address = address;
if (port) {
ex.port = port;
}
return ex;
};