532 lines
15 KiB
JavaScript
532 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
|
|
|
var t = _interopDefault(require('should-type'));
|
|
var shouldTypeAdaptors = require('should-type-adaptors');
|
|
|
|
function looksLikeANumber(n) {
|
|
return !!n.match(/\d+/);
|
|
}
|
|
|
|
function keyCompare(a, b) {
|
|
var aNum = looksLikeANumber(a);
|
|
var bNum = looksLikeANumber(b);
|
|
if (aNum && bNum) {
|
|
return 1*a - 1*b;
|
|
} else if (aNum && !bNum) {
|
|
return -1;
|
|
} else if (!aNum && bNum) {
|
|
return 1;
|
|
} else {
|
|
return a.localeCompare(b);
|
|
}
|
|
}
|
|
|
|
function genKeysFunc(f) {
|
|
return function(value) {
|
|
var k = f(value);
|
|
k.sort(keyCompare);
|
|
return k;
|
|
};
|
|
}
|
|
|
|
function Formatter(opts) {
|
|
opts = opts || {};
|
|
|
|
this.seen = [];
|
|
|
|
var keysFunc;
|
|
if (typeof opts.keysFunc === 'function') {
|
|
keysFunc = opts.keysFunc;
|
|
} else if (opts.keys === false) {
|
|
keysFunc = Object.getOwnPropertyNames;
|
|
} else {
|
|
keysFunc = Object.keys;
|
|
}
|
|
|
|
this.getKeys = genKeysFunc(keysFunc);
|
|
|
|
this.maxLineLength = typeof opts.maxLineLength === 'number' ? opts.maxLineLength : 60;
|
|
this.propSep = opts.propSep || ',';
|
|
|
|
this.isUTCdate = !!opts.isUTCdate;
|
|
}
|
|
|
|
|
|
|
|
Formatter.prototype = {
|
|
constructor: Formatter,
|
|
|
|
format: function(value) {
|
|
var tp = t(value);
|
|
|
|
if (this.alreadySeen(value)) {
|
|
return '[Circular]';
|
|
}
|
|
|
|
var tries = tp.toTryTypes();
|
|
var f = this.defaultFormat;
|
|
while (tries.length) {
|
|
var toTry = tries.shift();
|
|
var name = Formatter.formatterFunctionName(toTry);
|
|
if (this[name]) {
|
|
f = this[name];
|
|
break;
|
|
}
|
|
}
|
|
return f.call(this, value).trim();
|
|
},
|
|
|
|
defaultFormat: function(obj) {
|
|
return String(obj);
|
|
},
|
|
|
|
alreadySeen: function(value) {
|
|
return this.seen.indexOf(value) >= 0;
|
|
}
|
|
|
|
};
|
|
|
|
Formatter.addType = function addType(tp, f) {
|
|
Formatter.prototype[Formatter.formatterFunctionName(tp)] = f;
|
|
};
|
|
|
|
Formatter.formatterFunctionName = function formatterFunctionName(tp) {
|
|
return '_format_' + tp.toString('_');
|
|
};
|
|
|
|
var EOL = '\n';
|
|
|
|
function indent(v, indentation) {
|
|
return v
|
|
.split(EOL)
|
|
.map(function(vv) {
|
|
return indentation + vv;
|
|
})
|
|
.join(EOL);
|
|
}
|
|
|
|
function pad(str, value, filler) {
|
|
str = String(str);
|
|
var isRight = false;
|
|
|
|
if (value < 0) {
|
|
isRight = true;
|
|
value = -value;
|
|
}
|
|
|
|
if (str.length < value) {
|
|
var padding = new Array(value - str.length + 1).join(filler);
|
|
return isRight ? str + padding : padding + str;
|
|
} else {
|
|
return str;
|
|
}
|
|
}
|
|
|
|
function pad0(str, value) {
|
|
return pad(str, value, '0');
|
|
}
|
|
|
|
var functionNameRE = /^\s*function\s*(\S*)\s*\(/;
|
|
|
|
function functionName(f) {
|
|
if (f.name) {
|
|
return f.name;
|
|
}
|
|
var matches = f.toString().match(functionNameRE);
|
|
if (matches === null) {
|
|
// `functionNameRE` doesn't match arrow functions.
|
|
return '';
|
|
}
|
|
var name = matches[1];
|
|
return name;
|
|
}
|
|
|
|
function constructorName(obj) {
|
|
while (obj) {
|
|
var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
|
|
if (descriptor !== undefined && typeof descriptor.value === 'function') {
|
|
var name = functionName(descriptor.value);
|
|
if (name !== '') {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
obj = Object.getPrototypeOf(obj);
|
|
}
|
|
}
|
|
|
|
var INDENT = ' ';
|
|
|
|
function addSpaces(str) {
|
|
return indent(str, INDENT);
|
|
}
|
|
|
|
function typeAdaptorForEachFormat(obj, opts) {
|
|
opts = opts || {};
|
|
var filterKey = opts.filterKey || function() { return true; };
|
|
|
|
var formatKey = opts.formatKey || this.format;
|
|
var formatValue = opts.formatValue || this.format;
|
|
|
|
var keyValueSep = typeof opts.keyValueSep !== 'undefined' ? opts.keyValueSep : ': ';
|
|
|
|
this.seen.push(obj);
|
|
|
|
var formatLength = 0;
|
|
var pairs = [];
|
|
|
|
shouldTypeAdaptors.forEach(obj, function(value, key) {
|
|
if (!filterKey(key)) {
|
|
return;
|
|
}
|
|
|
|
var formattedKey = formatKey.call(this, key);
|
|
var formattedValue = formatValue.call(this, value, key);
|
|
|
|
var pair = formattedKey ? (formattedKey + keyValueSep + formattedValue) : formattedValue;
|
|
|
|
formatLength += pair.length;
|
|
pairs.push(pair);
|
|
}, this);
|
|
|
|
this.seen.pop();
|
|
|
|
(opts.additionalKeys || []).forEach(function(keyValue) {
|
|
var pair = keyValue[0] + keyValueSep + this.format(keyValue[1]);
|
|
formatLength += pair.length;
|
|
pairs.push(pair);
|
|
}, this);
|
|
|
|
var prefix = opts.prefix || constructorName(obj) || '';
|
|
if (prefix.length > 0) {
|
|
prefix += ' ';
|
|
}
|
|
|
|
var lbracket, rbracket;
|
|
if (Array.isArray(opts.brackets)) {
|
|
lbracket = opts.brackets[0];
|
|
rbracket = opts.brackets[1];
|
|
} else {
|
|
lbracket = '{';
|
|
rbracket = '}';
|
|
}
|
|
|
|
var rootValue = opts.value || '';
|
|
|
|
if (pairs.length === 0) {
|
|
return rootValue || (prefix + lbracket + rbracket);
|
|
}
|
|
|
|
if (formatLength <= this.maxLineLength) {
|
|
return prefix + lbracket + ' ' + (rootValue ? rootValue + ' ' : '') + pairs.join(this.propSep + ' ') + ' ' + rbracket;
|
|
} else {
|
|
return prefix + lbracket + '\n' + (rootValue ? ' ' + rootValue + '\n' : '') + pairs.map(addSpaces).join(this.propSep + '\n') + '\n' + rbracket;
|
|
}
|
|
}
|
|
|
|
function formatPlainObjectKey(key) {
|
|
return typeof key === 'string' && key.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/) ? key : this.format(key);
|
|
}
|
|
|
|
function getPropertyDescriptor(obj, key) {
|
|
var desc;
|
|
try {
|
|
desc = Object.getOwnPropertyDescriptor(obj, key) || { value: obj[key] };
|
|
} catch (e) {
|
|
desc = { value: e };
|
|
}
|
|
return desc;
|
|
}
|
|
|
|
function formatPlainObjectValue(obj, key) {
|
|
var desc = getPropertyDescriptor(obj, key);
|
|
if (desc.get && desc.set) {
|
|
return '[Getter/Setter]';
|
|
}
|
|
if (desc.get) {
|
|
return '[Getter]';
|
|
}
|
|
if (desc.set) {
|
|
return '[Setter]';
|
|
}
|
|
|
|
return this.format(desc.value);
|
|
}
|
|
|
|
function formatPlainObject(obj, opts) {
|
|
opts = opts || {};
|
|
opts.keyValueSep = ': ';
|
|
opts.formatKey = opts.formatKey || formatPlainObjectKey;
|
|
opts.formatValue = opts.formatValue || function(value, key) {
|
|
return formatPlainObjectValue.call(this, obj, key);
|
|
};
|
|
return typeAdaptorForEachFormat.call(this, obj, opts);
|
|
}
|
|
|
|
function formatWrapper1(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
additionalKeys: [['[[PrimitiveValue]]', value.valueOf()]]
|
|
});
|
|
}
|
|
|
|
|
|
function formatWrapper2(value) {
|
|
var realValue = value.valueOf();
|
|
|
|
return formatPlainObject.call(this, value, {
|
|
filterKey: function(key) {
|
|
//skip useless indexed properties
|
|
return !(key.match(/\d+/) && parseInt(key, 10) < realValue.length);
|
|
},
|
|
additionalKeys: [['[[PrimitiveValue]]', realValue]]
|
|
});
|
|
}
|
|
|
|
function formatRegExp(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
value: String(value)
|
|
});
|
|
}
|
|
|
|
function formatFunction(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
prefix: 'Function',
|
|
additionalKeys: [['name', functionName(value)]]
|
|
});
|
|
}
|
|
|
|
function formatArray(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
formatKey: function(key) {
|
|
if (!key.match(/\d+/)) {
|
|
return formatPlainObjectKey.call(this, key);
|
|
}
|
|
},
|
|
brackets: ['[', ']']
|
|
});
|
|
}
|
|
|
|
function formatArguments(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
formatKey: function(key) {
|
|
if (!key.match(/\d+/)) {
|
|
return formatPlainObjectKey.call(this, key);
|
|
}
|
|
},
|
|
brackets: ['[', ']'],
|
|
prefix: 'Arguments'
|
|
});
|
|
}
|
|
|
|
function _formatDate(value, isUTC) {
|
|
var prefix = isUTC ? 'UTC' : '';
|
|
|
|
var date = value['get' + prefix + 'FullYear']() +
|
|
'-' +
|
|
pad0(value['get' + prefix + 'Month']() + 1, 2) +
|
|
'-' +
|
|
pad0(value['get' + prefix + 'Date'](), 2);
|
|
|
|
var time = pad0(value['get' + prefix + 'Hours'](), 2) +
|
|
':' +
|
|
pad0(value['get' + prefix + 'Minutes'](), 2) +
|
|
':' +
|
|
pad0(value['get' + prefix + 'Seconds'](), 2) +
|
|
'.' +
|
|
pad0(value['get' + prefix + 'Milliseconds'](), 3);
|
|
|
|
var to = value.getTimezoneOffset();
|
|
var absTo = Math.abs(to);
|
|
var hours = Math.floor(absTo / 60);
|
|
var minutes = absTo - hours * 60;
|
|
var tzFormat = (to < 0 ? '+' : '-') + pad0(hours, 2) + pad0(minutes, 2);
|
|
|
|
return date + ' ' + time + (isUTC ? '' : ' ' + tzFormat);
|
|
}
|
|
|
|
function formatDate(value) {
|
|
return formatPlainObject.call(this, value, { value: _formatDate(value, this.isUTCdate) });
|
|
}
|
|
|
|
function formatError(value) {
|
|
return formatPlainObject.call(this, value, {
|
|
prefix: value.name,
|
|
additionalKeys: [['message', value.message]]
|
|
});
|
|
}
|
|
|
|
function generateFormatForNumberArray(lengthProp, name, padding) {
|
|
return function(value) {
|
|
var max = this.byteArrayMaxLength || 50;
|
|
var length = value[lengthProp];
|
|
var formattedValues = [];
|
|
var len = 0;
|
|
for (var i = 0; i < max && i < length; i++) {
|
|
var b = value[i] || 0;
|
|
var v = pad0(b.toString(16), padding);
|
|
len += v.length;
|
|
formattedValues.push(v);
|
|
}
|
|
var prefix = value.constructor.name || name || '';
|
|
if (prefix) {
|
|
prefix += ' ';
|
|
}
|
|
|
|
if (formattedValues.length === 0) {
|
|
return prefix + '[]';
|
|
}
|
|
|
|
if (len <= this.maxLineLength) {
|
|
return prefix + '[ ' + formattedValues.join(this.propSep + ' ') + ' ' + ']';
|
|
} else {
|
|
return prefix + '[\n' + formattedValues.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
|
|
}
|
|
};
|
|
}
|
|
|
|
function formatMap(obj) {
|
|
return typeAdaptorForEachFormat.call(this, obj, {
|
|
keyValueSep: ' => '
|
|
});
|
|
}
|
|
|
|
function formatSet(obj) {
|
|
return typeAdaptorForEachFormat.call(this, obj, {
|
|
keyValueSep: '',
|
|
formatKey: function() { return ''; }
|
|
});
|
|
}
|
|
|
|
function genSimdVectorFormat(constructorName, length) {
|
|
return function(value) {
|
|
var Constructor = value.constructor;
|
|
var extractLane = Constructor.extractLane;
|
|
|
|
var len = 0;
|
|
var props = [];
|
|
|
|
for (var i = 0; i < length; i ++) {
|
|
var key = this.format(extractLane(value, i));
|
|
len += key.length;
|
|
props.push(key);
|
|
}
|
|
|
|
if (len <= this.maxLineLength) {
|
|
return constructorName + ' [ ' + props.join(this.propSep + ' ') + ' ]';
|
|
} else {
|
|
return constructorName + ' [\n' + props.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
|
|
}
|
|
};
|
|
}
|
|
|
|
function defaultFormat(value, opts) {
|
|
return new Formatter(opts).format(value);
|
|
}
|
|
|
|
defaultFormat.Formatter = Formatter;
|
|
defaultFormat.addSpaces = addSpaces;
|
|
defaultFormat.pad0 = pad0;
|
|
defaultFormat.functionName = functionName;
|
|
defaultFormat.constructorName = constructorName;
|
|
defaultFormat.formatPlainObjectKey = formatPlainObjectKey;
|
|
defaultFormat.formatPlainObject = formatPlainObject;
|
|
defaultFormat.typeAdaptorForEachFormat = typeAdaptorForEachFormat;
|
|
// adding primitive types
|
|
Formatter.addType(new t.Type(t.UNDEFINED), function() {
|
|
return 'undefined';
|
|
});
|
|
Formatter.addType(new t.Type(t.NULL), function() {
|
|
return 'null';
|
|
});
|
|
Formatter.addType(new t.Type(t.BOOLEAN), function(value) {
|
|
return value ? 'true': 'false';
|
|
});
|
|
Formatter.addType(new t.Type(t.SYMBOL), function(value) {
|
|
return value.toString();
|
|
});
|
|
Formatter.addType(new t.Type(t.NUMBER), function(value) {
|
|
if (value === 0 && 1 / value < 0) {
|
|
return '-0';
|
|
}
|
|
return String(value);
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.STRING), function(value) {
|
|
return '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
|
|
.replace(/'/g, "\\'")
|
|
.replace(/\\"/g, '"') + '\'';
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.FUNCTION), formatFunction);
|
|
|
|
// plain object
|
|
Formatter.addType(new t.Type(t.OBJECT), formatPlainObject);
|
|
|
|
// type wrappers
|
|
Formatter.addType(new t.Type(t.OBJECT, t.NUMBER), formatWrapper1);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.BOOLEAN), formatWrapper1);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.STRING), formatWrapper2);
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.REGEXP), formatRegExp);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.ARRAY), formatArray);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.ARGUMENTS), formatArguments);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.DATE), formatDate);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.ERROR), formatError);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SET), formatSet);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.MAP), formatMap);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.WEAK_MAP), formatMap);
|
|
Formatter.addType(new t.Type(t.OBJECT, t.WEAK_SET), formatSet);
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.BUFFER), generateFormatForNumberArray('length', 'Buffer', 2));
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.ARRAY_BUFFER), generateFormatForNumberArray('byteLength', 'ArrayBuffer', 2));
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int8'), generateFormatForNumberArray('length', 'Int8Array', 2));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint8'), generateFormatForNumberArray('length', 'Uint8Array', 2));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint8clamped'), generateFormatForNumberArray('length', 'Uint8ClampedArray', 2));
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int16'), generateFormatForNumberArray('length', 'Int16Array', 4));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint16'), generateFormatForNumberArray('length', 'Uint16Array', 4));
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int32'), generateFormatForNumberArray('length', 'Int32Array', 8));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint32'), generateFormatForNumberArray('length', 'Uint32Array', 8));
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool16x8'), genSimdVectorFormat('Bool16x8', 8));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool32x4'), genSimdVectorFormat('Bool32x4', 4));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool8x16'), genSimdVectorFormat('Bool8x16', 16));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'float32x4'), genSimdVectorFormat('Float32x4', 4));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int16x8'), genSimdVectorFormat('Int16x8', 8));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int32x4'), genSimdVectorFormat('Int32x4', 4));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int8x16'), genSimdVectorFormat('Int8x16', 16));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint16x8'), genSimdVectorFormat('Uint16x8', 8));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint32x4'), genSimdVectorFormat('Uint32x4', 4));
|
|
Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint8x16'), genSimdVectorFormat('Uint8x16', 16));
|
|
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.PROMISE), function() {
|
|
return '[Promise]';//TODO it could be nice to inspect its state and value
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.XHR), function() {
|
|
return '[XMLHttpRequest]';//TODO it could be nice to inspect its state
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT), function(value) {
|
|
return value.outerHTML;
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT, '#text'), function(value) {
|
|
return value.nodeValue;
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT, '#document'), function(value) {
|
|
return value.documentElement.outerHTML;
|
|
});
|
|
|
|
Formatter.addType(new t.Type(t.OBJECT, t.HOST), function() {
|
|
return '[Host]';
|
|
});
|
|
|
|
module.exports = defaultFormat; |