should-format.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. 'use strict';
  2. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  3. var t = _interopDefault(require('should-type'));
  4. var shouldTypeAdaptors = require('should-type-adaptors');
  5. function looksLikeANumber(n) {
  6. return !!n.match(/\d+/);
  7. }
  8. function keyCompare(a, b) {
  9. var aNum = looksLikeANumber(a);
  10. var bNum = looksLikeANumber(b);
  11. if (aNum && bNum) {
  12. return 1*a - 1*b;
  13. } else if (aNum && !bNum) {
  14. return -1;
  15. } else if (!aNum && bNum) {
  16. return 1;
  17. } else {
  18. return a.localeCompare(b);
  19. }
  20. }
  21. function genKeysFunc(f) {
  22. return function(value) {
  23. var k = f(value);
  24. k.sort(keyCompare);
  25. return k;
  26. };
  27. }
  28. function Formatter(opts) {
  29. opts = opts || {};
  30. this.seen = [];
  31. var keysFunc;
  32. if (typeof opts.keysFunc === 'function') {
  33. keysFunc = opts.keysFunc;
  34. } else if (opts.keys === false) {
  35. keysFunc = Object.getOwnPropertyNames;
  36. } else {
  37. keysFunc = Object.keys;
  38. }
  39. this.getKeys = genKeysFunc(keysFunc);
  40. this.maxLineLength = typeof opts.maxLineLength === 'number' ? opts.maxLineLength : 60;
  41. this.propSep = opts.propSep || ',';
  42. this.isUTCdate = !!opts.isUTCdate;
  43. }
  44. Formatter.prototype = {
  45. constructor: Formatter,
  46. format: function(value) {
  47. var tp = t(value);
  48. if (this.alreadySeen(value)) {
  49. return '[Circular]';
  50. }
  51. var tries = tp.toTryTypes();
  52. var f = this.defaultFormat;
  53. while (tries.length) {
  54. var toTry = tries.shift();
  55. var name = Formatter.formatterFunctionName(toTry);
  56. if (this[name]) {
  57. f = this[name];
  58. break;
  59. }
  60. }
  61. return f.call(this, value).trim();
  62. },
  63. defaultFormat: function(obj) {
  64. return String(obj);
  65. },
  66. alreadySeen: function(value) {
  67. return this.seen.indexOf(value) >= 0;
  68. }
  69. };
  70. Formatter.addType = function addType(tp, f) {
  71. Formatter.prototype[Formatter.formatterFunctionName(tp)] = f;
  72. };
  73. Formatter.formatterFunctionName = function formatterFunctionName(tp) {
  74. return '_format_' + tp.toString('_');
  75. };
  76. var EOL = '\n';
  77. function indent(v, indentation) {
  78. return v
  79. .split(EOL)
  80. .map(function(vv) {
  81. return indentation + vv;
  82. })
  83. .join(EOL);
  84. }
  85. function pad(str, value, filler) {
  86. str = String(str);
  87. var isRight = false;
  88. if (value < 0) {
  89. isRight = true;
  90. value = -value;
  91. }
  92. if (str.length < value) {
  93. var padding = new Array(value - str.length + 1).join(filler);
  94. return isRight ? str + padding : padding + str;
  95. } else {
  96. return str;
  97. }
  98. }
  99. function pad0(str, value) {
  100. return pad(str, value, '0');
  101. }
  102. var functionNameRE = /^\s*function\s*(\S*)\s*\(/;
  103. function functionName(f) {
  104. if (f.name) {
  105. return f.name;
  106. }
  107. var matches = f.toString().match(functionNameRE);
  108. if (matches === null) {
  109. // `functionNameRE` doesn't match arrow functions.
  110. return '';
  111. }
  112. var name = matches[1];
  113. return name;
  114. }
  115. function constructorName(obj) {
  116. while (obj) {
  117. var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
  118. if (descriptor !== undefined && typeof descriptor.value === 'function') {
  119. var name = functionName(descriptor.value);
  120. if (name !== '') {
  121. return name;
  122. }
  123. }
  124. obj = Object.getPrototypeOf(obj);
  125. }
  126. }
  127. var INDENT = ' ';
  128. function addSpaces(str) {
  129. return indent(str, INDENT);
  130. }
  131. function typeAdaptorForEachFormat(obj, opts) {
  132. opts = opts || {};
  133. var filterKey = opts.filterKey || function() { return true; };
  134. var formatKey = opts.formatKey || this.format;
  135. var formatValue = opts.formatValue || this.format;
  136. var keyValueSep = typeof opts.keyValueSep !== 'undefined' ? opts.keyValueSep : ': ';
  137. this.seen.push(obj);
  138. var formatLength = 0;
  139. var pairs = [];
  140. shouldTypeAdaptors.forEach(obj, function(value, key) {
  141. if (!filterKey(key)) {
  142. return;
  143. }
  144. var formattedKey = formatKey.call(this, key);
  145. var formattedValue = formatValue.call(this, value, key);
  146. var pair = formattedKey ? (formattedKey + keyValueSep + formattedValue) : formattedValue;
  147. formatLength += pair.length;
  148. pairs.push(pair);
  149. }, this);
  150. this.seen.pop();
  151. (opts.additionalKeys || []).forEach(function(keyValue) {
  152. var pair = keyValue[0] + keyValueSep + this.format(keyValue[1]);
  153. formatLength += pair.length;
  154. pairs.push(pair);
  155. }, this);
  156. var prefix = opts.prefix || constructorName(obj) || '';
  157. if (prefix.length > 0) {
  158. prefix += ' ';
  159. }
  160. var lbracket, rbracket;
  161. if (Array.isArray(opts.brackets)) {
  162. lbracket = opts.brackets[0];
  163. rbracket = opts.brackets[1];
  164. } else {
  165. lbracket = '{';
  166. rbracket = '}';
  167. }
  168. var rootValue = opts.value || '';
  169. if (pairs.length === 0) {
  170. return rootValue || (prefix + lbracket + rbracket);
  171. }
  172. if (formatLength <= this.maxLineLength) {
  173. return prefix + lbracket + ' ' + (rootValue ? rootValue + ' ' : '') + pairs.join(this.propSep + ' ') + ' ' + rbracket;
  174. } else {
  175. return prefix + lbracket + '\n' + (rootValue ? ' ' + rootValue + '\n' : '') + pairs.map(addSpaces).join(this.propSep + '\n') + '\n' + rbracket;
  176. }
  177. }
  178. function formatPlainObjectKey(key) {
  179. return typeof key === 'string' && key.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/) ? key : this.format(key);
  180. }
  181. function getPropertyDescriptor(obj, key) {
  182. var desc;
  183. try {
  184. desc = Object.getOwnPropertyDescriptor(obj, key) || { value: obj[key] };
  185. } catch (e) {
  186. desc = { value: e };
  187. }
  188. return desc;
  189. }
  190. function formatPlainObjectValue(obj, key) {
  191. var desc = getPropertyDescriptor(obj, key);
  192. if (desc.get && desc.set) {
  193. return '[Getter/Setter]';
  194. }
  195. if (desc.get) {
  196. return '[Getter]';
  197. }
  198. if (desc.set) {
  199. return '[Setter]';
  200. }
  201. return this.format(desc.value);
  202. }
  203. function formatPlainObject(obj, opts) {
  204. opts = opts || {};
  205. opts.keyValueSep = ': ';
  206. opts.formatKey = opts.formatKey || formatPlainObjectKey;
  207. opts.formatValue = opts.formatValue || function(value, key) {
  208. return formatPlainObjectValue.call(this, obj, key);
  209. };
  210. return typeAdaptorForEachFormat.call(this, obj, opts);
  211. }
  212. function formatWrapper1(value) {
  213. return formatPlainObject.call(this, value, {
  214. additionalKeys: [['[[PrimitiveValue]]', value.valueOf()]]
  215. });
  216. }
  217. function formatWrapper2(value) {
  218. var realValue = value.valueOf();
  219. return formatPlainObject.call(this, value, {
  220. filterKey: function(key) {
  221. //skip useless indexed properties
  222. return !(key.match(/\d+/) && parseInt(key, 10) < realValue.length);
  223. },
  224. additionalKeys: [['[[PrimitiveValue]]', realValue]]
  225. });
  226. }
  227. function formatRegExp(value) {
  228. return formatPlainObject.call(this, value, {
  229. value: String(value)
  230. });
  231. }
  232. function formatFunction(value) {
  233. return formatPlainObject.call(this, value, {
  234. prefix: 'Function',
  235. additionalKeys: [['name', functionName(value)]]
  236. });
  237. }
  238. function formatArray(value) {
  239. return formatPlainObject.call(this, value, {
  240. formatKey: function(key) {
  241. if (!key.match(/\d+/)) {
  242. return formatPlainObjectKey.call(this, key);
  243. }
  244. },
  245. brackets: ['[', ']']
  246. });
  247. }
  248. function formatArguments(value) {
  249. return formatPlainObject.call(this, value, {
  250. formatKey: function(key) {
  251. if (!key.match(/\d+/)) {
  252. return formatPlainObjectKey.call(this, key);
  253. }
  254. },
  255. brackets: ['[', ']'],
  256. prefix: 'Arguments'
  257. });
  258. }
  259. function _formatDate(value, isUTC) {
  260. var prefix = isUTC ? 'UTC' : '';
  261. var date = value['get' + prefix + 'FullYear']() +
  262. '-' +
  263. pad0(value['get' + prefix + 'Month']() + 1, 2) +
  264. '-' +
  265. pad0(value['get' + prefix + 'Date'](), 2);
  266. var time = pad0(value['get' + prefix + 'Hours'](), 2) +
  267. ':' +
  268. pad0(value['get' + prefix + 'Minutes'](), 2) +
  269. ':' +
  270. pad0(value['get' + prefix + 'Seconds'](), 2) +
  271. '.' +
  272. pad0(value['get' + prefix + 'Milliseconds'](), 3);
  273. var to = value.getTimezoneOffset();
  274. var absTo = Math.abs(to);
  275. var hours = Math.floor(absTo / 60);
  276. var minutes = absTo - hours * 60;
  277. var tzFormat = (to < 0 ? '+' : '-') + pad0(hours, 2) + pad0(minutes, 2);
  278. return date + ' ' + time + (isUTC ? '' : ' ' + tzFormat);
  279. }
  280. function formatDate(value) {
  281. return formatPlainObject.call(this, value, { value: _formatDate(value, this.isUTCdate) });
  282. }
  283. function formatError(value) {
  284. return formatPlainObject.call(this, value, {
  285. prefix: value.name,
  286. additionalKeys: [['message', value.message]]
  287. });
  288. }
  289. function generateFormatForNumberArray(lengthProp, name, padding) {
  290. return function(value) {
  291. var max = this.byteArrayMaxLength || 50;
  292. var length = value[lengthProp];
  293. var formattedValues = [];
  294. var len = 0;
  295. for (var i = 0; i < max && i < length; i++) {
  296. var b = value[i] || 0;
  297. var v = pad0(b.toString(16), padding);
  298. len += v.length;
  299. formattedValues.push(v);
  300. }
  301. var prefix = value.constructor.name || name || '';
  302. if (prefix) {
  303. prefix += ' ';
  304. }
  305. if (formattedValues.length === 0) {
  306. return prefix + '[]';
  307. }
  308. if (len <= this.maxLineLength) {
  309. return prefix + '[ ' + formattedValues.join(this.propSep + ' ') + ' ' + ']';
  310. } else {
  311. return prefix + '[\n' + formattedValues.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
  312. }
  313. };
  314. }
  315. function formatMap(obj) {
  316. return typeAdaptorForEachFormat.call(this, obj, {
  317. keyValueSep: ' => '
  318. });
  319. }
  320. function formatSet(obj) {
  321. return typeAdaptorForEachFormat.call(this, obj, {
  322. keyValueSep: '',
  323. formatKey: function() { return ''; }
  324. });
  325. }
  326. function genSimdVectorFormat(constructorName, length) {
  327. return function(value) {
  328. var Constructor = value.constructor;
  329. var extractLane = Constructor.extractLane;
  330. var len = 0;
  331. var props = [];
  332. for (var i = 0; i < length; i ++) {
  333. var key = this.format(extractLane(value, i));
  334. len += key.length;
  335. props.push(key);
  336. }
  337. if (len <= this.maxLineLength) {
  338. return constructorName + ' [ ' + props.join(this.propSep + ' ') + ' ]';
  339. } else {
  340. return constructorName + ' [\n' + props.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
  341. }
  342. };
  343. }
  344. function defaultFormat(value, opts) {
  345. return new Formatter(opts).format(value);
  346. }
  347. defaultFormat.Formatter = Formatter;
  348. defaultFormat.addSpaces = addSpaces;
  349. defaultFormat.pad0 = pad0;
  350. defaultFormat.functionName = functionName;
  351. defaultFormat.constructorName = constructorName;
  352. defaultFormat.formatPlainObjectKey = formatPlainObjectKey;
  353. defaultFormat.formatPlainObject = formatPlainObject;
  354. defaultFormat.typeAdaptorForEachFormat = typeAdaptorForEachFormat;
  355. // adding primitive types
  356. Formatter.addType(new t.Type(t.UNDEFINED), function() {
  357. return 'undefined';
  358. });
  359. Formatter.addType(new t.Type(t.NULL), function() {
  360. return 'null';
  361. });
  362. Formatter.addType(new t.Type(t.BOOLEAN), function(value) {
  363. return value ? 'true': 'false';
  364. });
  365. Formatter.addType(new t.Type(t.SYMBOL), function(value) {
  366. return value.toString();
  367. });
  368. Formatter.addType(new t.Type(t.NUMBER), function(value) {
  369. if (value === 0 && 1 / value < 0) {
  370. return '-0';
  371. }
  372. return String(value);
  373. });
  374. Formatter.addType(new t.Type(t.STRING), function(value) {
  375. return '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
  376. .replace(/'/g, "\\'")
  377. .replace(/\\"/g, '"') + '\'';
  378. });
  379. Formatter.addType(new t.Type(t.FUNCTION), formatFunction);
  380. // plain object
  381. Formatter.addType(new t.Type(t.OBJECT), formatPlainObject);
  382. // type wrappers
  383. Formatter.addType(new t.Type(t.OBJECT, t.NUMBER), formatWrapper1);
  384. Formatter.addType(new t.Type(t.OBJECT, t.BOOLEAN), formatWrapper1);
  385. Formatter.addType(new t.Type(t.OBJECT, t.STRING), formatWrapper2);
  386. Formatter.addType(new t.Type(t.OBJECT, t.REGEXP), formatRegExp);
  387. Formatter.addType(new t.Type(t.OBJECT, t.ARRAY), formatArray);
  388. Formatter.addType(new t.Type(t.OBJECT, t.ARGUMENTS), formatArguments);
  389. Formatter.addType(new t.Type(t.OBJECT, t.DATE), formatDate);
  390. Formatter.addType(new t.Type(t.OBJECT, t.ERROR), formatError);
  391. Formatter.addType(new t.Type(t.OBJECT, t.SET), formatSet);
  392. Formatter.addType(new t.Type(t.OBJECT, t.MAP), formatMap);
  393. Formatter.addType(new t.Type(t.OBJECT, t.WEAK_MAP), formatMap);
  394. Formatter.addType(new t.Type(t.OBJECT, t.WEAK_SET), formatSet);
  395. Formatter.addType(new t.Type(t.OBJECT, t.BUFFER), generateFormatForNumberArray('length', 'Buffer', 2));
  396. Formatter.addType(new t.Type(t.OBJECT, t.ARRAY_BUFFER), generateFormatForNumberArray('byteLength', 'ArrayBuffer', 2));
  397. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int8'), generateFormatForNumberArray('length', 'Int8Array', 2));
  398. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint8'), generateFormatForNumberArray('length', 'Uint8Array', 2));
  399. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint8clamped'), generateFormatForNumberArray('length', 'Uint8ClampedArray', 2));
  400. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int16'), generateFormatForNumberArray('length', 'Int16Array', 4));
  401. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint16'), generateFormatForNumberArray('length', 'Uint16Array', 4));
  402. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'int32'), generateFormatForNumberArray('length', 'Int32Array', 8));
  403. Formatter.addType(new t.Type(t.OBJECT, t.TYPED_ARRAY, 'uint32'), generateFormatForNumberArray('length', 'Uint32Array', 8));
  404. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool16x8'), genSimdVectorFormat('Bool16x8', 8));
  405. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool32x4'), genSimdVectorFormat('Bool32x4', 4));
  406. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'bool8x16'), genSimdVectorFormat('Bool8x16', 16));
  407. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'float32x4'), genSimdVectorFormat('Float32x4', 4));
  408. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int16x8'), genSimdVectorFormat('Int16x8', 8));
  409. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int32x4'), genSimdVectorFormat('Int32x4', 4));
  410. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'int8x16'), genSimdVectorFormat('Int8x16', 16));
  411. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint16x8'), genSimdVectorFormat('Uint16x8', 8));
  412. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint32x4'), genSimdVectorFormat('Uint32x4', 4));
  413. Formatter.addType(new t.Type(t.OBJECT, t.SIMD, 'uint8x16'), genSimdVectorFormat('Uint8x16', 16));
  414. Formatter.addType(new t.Type(t.OBJECT, t.PROMISE), function() {
  415. return '[Promise]';//TODO it could be nice to inspect its state and value
  416. });
  417. Formatter.addType(new t.Type(t.OBJECT, t.XHR), function() {
  418. return '[XMLHttpRequest]';//TODO it could be nice to inspect its state
  419. });
  420. Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT), function(value) {
  421. return value.outerHTML;
  422. });
  423. Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT, '#text'), function(value) {
  424. return value.nodeValue;
  425. });
  426. Formatter.addType(new t.Type(t.OBJECT, t.HTML_ELEMENT, '#document'), function(value) {
  427. return value.documentElement.outerHTML;
  428. });
  429. Formatter.addType(new t.Type(t.OBJECT, t.HOST), function() {
  430. return '[Host]';
  431. });
  432. module.exports = defaultFormat;