index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. 'use strict';
  2. var _ansiStyles = require('ansi-styles');
  3. var _ansiStyles2 = _interopRequireDefault(_ansiStyles);
  4. var _collections = require('./collections');
  5. var _asymmetric_matcher = require('./plugins/asymmetric_matcher');
  6. var _asymmetric_matcher2 = _interopRequireDefault(_asymmetric_matcher);
  7. var _convert_ansi = require('./plugins/convert_ansi');
  8. var _convert_ansi2 = _interopRequireDefault(_convert_ansi);
  9. var _dom_collection = require('./plugins/dom_collection');
  10. var _dom_collection2 = _interopRequireDefault(_dom_collection);
  11. var _dom_element = require('./plugins/dom_element');
  12. var _dom_element2 = _interopRequireDefault(_dom_element);
  13. var _immutable = require('./plugins/immutable');
  14. var _immutable2 = _interopRequireDefault(_immutable);
  15. var _react_element = require('./plugins/react_element');
  16. var _react_element2 = _interopRequireDefault(_react_element);
  17. var _react_test_component = require('./plugins/react_test_component');
  18. var _react_test_component2 = _interopRequireDefault(_react_test_component);
  19. function _interopRequireDefault(obj) {
  20. return obj && obj.__esModule ? obj : {default: obj};
  21. }
  22. /**
  23. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  24. *
  25. * This source code is licensed under the MIT license found in the
  26. * LICENSE file in the root directory of this source tree.
  27. *
  28. *
  29. */
  30. const toString = Object.prototype.toString;
  31. const toISOString = Date.prototype.toISOString;
  32. const errorToString = Error.prototype.toString;
  33. const regExpToString = RegExp.prototype.toString;
  34. const symbolToString = Symbol.prototype.toString;
  35. // Explicitly comparing typeof constructor to function avoids undefined as name
  36. // when mock identity-obj-proxy returns the key as the value for any key.
  37. const getConstructorName = val =>
  38. (typeof val.constructor === 'function' && val.constructor.name) || 'Object';
  39. // Is val is equal to global window object? Works even if it does not exist :)
  40. /* global window */
  41. const isWindow = val => typeof window !== 'undefined' && val === window;
  42. const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
  43. const NEWLINE_REGEXP = /\n/gi;
  44. class PrettyFormatPluginError extends Error {
  45. constructor(message, stack) {
  46. super(message);
  47. this.stack = stack;
  48. this.name = this.constructor.name;
  49. }
  50. }
  51. function isToStringedArrayType(toStringed) {
  52. return (
  53. toStringed === '[object Array]' ||
  54. toStringed === '[object ArrayBuffer]' ||
  55. toStringed === '[object DataView]' ||
  56. toStringed === '[object Float32Array]' ||
  57. toStringed === '[object Float64Array]' ||
  58. toStringed === '[object Int8Array]' ||
  59. toStringed === '[object Int16Array]' ||
  60. toStringed === '[object Int32Array]' ||
  61. toStringed === '[object Uint8Array]' ||
  62. toStringed === '[object Uint8ClampedArray]' ||
  63. toStringed === '[object Uint16Array]' ||
  64. toStringed === '[object Uint32Array]'
  65. );
  66. }
  67. function printNumber(val) {
  68. return Object.is(val, -0) ? '-0' : String(val);
  69. }
  70. function printFunction(val, printFunctionName) {
  71. if (!printFunctionName) {
  72. return '[Function]';
  73. }
  74. return '[Function ' + (val.name || 'anonymous') + ']';
  75. }
  76. function printSymbol(val) {
  77. return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
  78. }
  79. function printError(val) {
  80. return '[' + errorToString.call(val) + ']';
  81. }
  82. function printBasicValue(val, printFunctionName, escapeRegex) {
  83. if (val === true || val === false) {
  84. return '' + val;
  85. }
  86. if (val === undefined) {
  87. return 'undefined';
  88. }
  89. if (val === null) {
  90. return 'null';
  91. }
  92. const typeOf = typeof val;
  93. if (typeOf === 'number') {
  94. return printNumber(val);
  95. }
  96. if (typeOf === 'string') {
  97. return '"' + val.replace(/"|\\/g, '\\$&') + '"';
  98. }
  99. if (typeOf === 'function') {
  100. return printFunction(val, printFunctionName);
  101. }
  102. if (typeOf === 'symbol') {
  103. return printSymbol(val);
  104. }
  105. const toStringed = toString.call(val);
  106. if (toStringed === '[object WeakMap]') {
  107. return 'WeakMap {}';
  108. }
  109. if (toStringed === '[object WeakSet]') {
  110. return 'WeakSet {}';
  111. }
  112. if (
  113. toStringed === '[object Function]' ||
  114. toStringed === '[object GeneratorFunction]'
  115. ) {
  116. return printFunction(val, printFunctionName);
  117. }
  118. if (toStringed === '[object Symbol]') {
  119. return printSymbol(val);
  120. }
  121. if (toStringed === '[object Date]') {
  122. return isNaN(+val) ? 'Date { NaN }' : toISOString.call(val);
  123. }
  124. if (toStringed === '[object Error]') {
  125. return printError(val);
  126. }
  127. if (toStringed === '[object RegExp]') {
  128. if (escapeRegex) {
  129. // https://github.com/benjamingr/RegExp.escape/blob/master/polyfill.js
  130. return regExpToString.call(val).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
  131. }
  132. return regExpToString.call(val);
  133. }
  134. if (val instanceof Error) {
  135. return printError(val);
  136. }
  137. return null;
  138. }
  139. function printComplexValue(
  140. val,
  141. config,
  142. indentation,
  143. depth,
  144. refs,
  145. hasCalledToJSON
  146. ) {
  147. if (refs.indexOf(val) !== -1) {
  148. return '[Circular]';
  149. }
  150. refs = refs.slice();
  151. refs.push(val);
  152. const hitMaxDepth = ++depth > config.maxDepth;
  153. const min = config.min;
  154. if (
  155. config.callToJSON &&
  156. !hitMaxDepth &&
  157. val.toJSON &&
  158. typeof val.toJSON === 'function' &&
  159. !hasCalledToJSON
  160. ) {
  161. return printer(val.toJSON(), config, indentation, depth, refs, true);
  162. }
  163. const toStringed = toString.call(val);
  164. if (toStringed === '[object Arguments]') {
  165. return hitMaxDepth
  166. ? '[Arguments]'
  167. : (min ? '' : 'Arguments ') +
  168. '[' +
  169. (0, _collections.printListItems)(
  170. val,
  171. config,
  172. indentation,
  173. depth,
  174. refs,
  175. printer
  176. ) +
  177. ']';
  178. }
  179. if (isToStringedArrayType(toStringed)) {
  180. return hitMaxDepth
  181. ? '[' + val.constructor.name + ']'
  182. : (min ? '' : val.constructor.name + ' ') +
  183. '[' +
  184. (0, _collections.printListItems)(
  185. val,
  186. config,
  187. indentation,
  188. depth,
  189. refs,
  190. printer
  191. ) +
  192. ']';
  193. }
  194. if (toStringed === '[object Map]') {
  195. return hitMaxDepth
  196. ? '[Map]'
  197. : 'Map {' +
  198. (0, _collections.printIteratorEntries)(
  199. val.entries(),
  200. config,
  201. indentation,
  202. depth,
  203. refs,
  204. printer,
  205. ' => '
  206. ) +
  207. '}';
  208. }
  209. if (toStringed === '[object Set]') {
  210. return hitMaxDepth
  211. ? '[Set]'
  212. : 'Set {' +
  213. (0, _collections.printIteratorValues)(
  214. val.values(),
  215. config,
  216. indentation,
  217. depth,
  218. refs,
  219. printer
  220. ) +
  221. '}';
  222. }
  223. // Avoid failure to serialize global window object in jsdom test environment.
  224. // For example, not even relevant if window is prop of React element.
  225. return hitMaxDepth || isWindow(val)
  226. ? '[' + getConstructorName(val) + ']'
  227. : (min ? '' : getConstructorName(val) + ' ') +
  228. '{' +
  229. (0, _collections.printObjectProperties)(
  230. val,
  231. config,
  232. indentation,
  233. depth,
  234. refs,
  235. printer
  236. ) +
  237. '}';
  238. }
  239. function printPlugin(plugin, val, config, indentation, depth, refs) {
  240. let printed;
  241. try {
  242. printed = plugin.serialize
  243. ? plugin.serialize(val, config, indentation, depth, refs, printer)
  244. : plugin.print(
  245. val,
  246. valChild => printer(valChild, config, indentation, depth, refs),
  247. str => {
  248. const indentationNext = indentation + config.indent;
  249. return (
  250. indentationNext +
  251. str.replace(NEWLINE_REGEXP, '\n' + indentationNext)
  252. );
  253. },
  254. {
  255. edgeSpacing: config.spacingOuter,
  256. min: config.min,
  257. spacing: config.spacingInner
  258. },
  259. config.colors
  260. );
  261. } catch (error) {
  262. throw new PrettyFormatPluginError(error.message, error.stack);
  263. }
  264. if (typeof printed !== 'string') {
  265. throw new Error(
  266. `pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`
  267. );
  268. }
  269. return printed;
  270. }
  271. function findPlugin(plugins, val) {
  272. for (let p = 0; p < plugins.length; p++) {
  273. try {
  274. if (plugins[p].test(val)) {
  275. return plugins[p];
  276. }
  277. } catch (error) {
  278. throw new PrettyFormatPluginError(error.message, error.stack);
  279. }
  280. }
  281. return null;
  282. }
  283. function printer(val, config, indentation, depth, refs, hasCalledToJSON) {
  284. const plugin = findPlugin(config.plugins, val);
  285. if (plugin !== null) {
  286. return printPlugin(plugin, val, config, indentation, depth, refs);
  287. }
  288. const basicResult = printBasicValue(
  289. val,
  290. config.printFunctionName,
  291. config.escapeRegex
  292. );
  293. if (basicResult !== null) {
  294. return basicResult;
  295. }
  296. return printComplexValue(
  297. val,
  298. config,
  299. indentation,
  300. depth,
  301. refs,
  302. hasCalledToJSON
  303. );
  304. }
  305. const DEFAULT_THEME = {
  306. comment: 'gray',
  307. content: 'reset',
  308. prop: 'yellow',
  309. tag: 'cyan',
  310. value: 'green'
  311. };
  312. const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME);
  313. const DEFAULT_OPTIONS = {
  314. callToJSON: true,
  315. escapeRegex: false,
  316. highlight: false,
  317. indent: 2,
  318. maxDepth: Infinity,
  319. min: false,
  320. plugins: [],
  321. printFunctionName: true,
  322. theme: DEFAULT_THEME
  323. };
  324. function validateOptions(options) {
  325. Object.keys(options).forEach(key => {
  326. if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
  327. throw new Error(`pretty-format: Unknown option "${key}".`);
  328. }
  329. });
  330. if (options.min && options.indent !== undefined && options.indent !== 0) {
  331. throw new Error(
  332. 'pretty-format: Options "min" and "indent" cannot be used together.'
  333. );
  334. }
  335. if (options.theme !== undefined) {
  336. if (options.theme === null) {
  337. throw new Error(`pretty-format: Option "theme" must not be null.`);
  338. }
  339. if (typeof options.theme !== 'object') {
  340. throw new Error(
  341. `pretty-format: Option "theme" must be of type "object" but instead received "${typeof options.theme}".`
  342. );
  343. }
  344. }
  345. }
  346. const getColorsHighlight = (
  347. options
  348. // $FlowFixMe: Flow thinks keys from `Colors` are missing from `DEFAULT_THEME_KEYS`
  349. ) =>
  350. DEFAULT_THEME_KEYS.reduce((colors, key) => {
  351. const value =
  352. options.theme && options.theme[key] !== undefined
  353. ? options.theme[key]
  354. : DEFAULT_THEME[key];
  355. const color = _ansiStyles2.default[value];
  356. if (
  357. color &&
  358. typeof color.close === 'string' &&
  359. typeof color.open === 'string'
  360. ) {
  361. colors[key] = color;
  362. } else {
  363. throw new Error(
  364. `pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`
  365. );
  366. }
  367. return colors;
  368. }, Object.create(null));
  369. const getColorsEmpty = () =>
  370. // $FlowFixMe: Flow thinks keys from `Colors` are missing from `DEFAULT_THEME_KEYS`
  371. DEFAULT_THEME_KEYS.reduce((colors, key) => {
  372. colors[key] = {close: '', open: ''};
  373. return colors;
  374. }, Object.create(null));
  375. const getPrintFunctionName = options =>
  376. options && options.printFunctionName !== undefined
  377. ? options.printFunctionName
  378. : DEFAULT_OPTIONS.printFunctionName;
  379. const getEscapeRegex = options =>
  380. options && options.escapeRegex !== undefined
  381. ? options.escapeRegex
  382. : DEFAULT_OPTIONS.escapeRegex;
  383. const getConfig = options => ({
  384. callToJSON:
  385. options && options.callToJSON !== undefined
  386. ? options.callToJSON
  387. : DEFAULT_OPTIONS.callToJSON,
  388. colors:
  389. options && options.highlight
  390. ? getColorsHighlight(options)
  391. : getColorsEmpty(),
  392. escapeRegex: getEscapeRegex(options),
  393. indent:
  394. options && options.min
  395. ? ''
  396. : createIndent(
  397. options && options.indent !== undefined
  398. ? options.indent
  399. : DEFAULT_OPTIONS.indent
  400. ),
  401. maxDepth:
  402. options && options.maxDepth !== undefined
  403. ? options.maxDepth
  404. : DEFAULT_OPTIONS.maxDepth,
  405. min: options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min,
  406. plugins:
  407. options && options.plugins !== undefined
  408. ? options.plugins
  409. : DEFAULT_OPTIONS.plugins,
  410. printFunctionName: getPrintFunctionName(options),
  411. spacingInner: options && options.min ? ' ' : '\n',
  412. spacingOuter: options && options.min ? '' : '\n'
  413. });
  414. function createIndent(indent) {
  415. return new Array(indent + 1).join(' ');
  416. }
  417. function prettyFormat(val, options) {
  418. if (options) {
  419. validateOptions(options);
  420. if (options.plugins) {
  421. const plugin = findPlugin(options.plugins, val);
  422. if (plugin !== null) {
  423. return printPlugin(plugin, val, getConfig(options), '', 0, []);
  424. }
  425. }
  426. }
  427. const basicResult = printBasicValue(
  428. val,
  429. getPrintFunctionName(options),
  430. getEscapeRegex(options)
  431. );
  432. if (basicResult !== null) {
  433. return basicResult;
  434. }
  435. return printComplexValue(val, getConfig(options), '', 0, []);
  436. }
  437. prettyFormat.plugins = {
  438. AsymmetricMatcher: _asymmetric_matcher2.default,
  439. ConvertAnsi: _convert_ansi2.default,
  440. DOMCollection: _dom_collection2.default,
  441. DOMElement: _dom_element2.default,
  442. Immutable: _immutable2.default,
  443. ReactElement: _react_element2.default,
  444. ReactTestComponent: _react_test_component2.default
  445. };
  446. module.exports = prettyFormat;