index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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) { return obj && obj.__esModule ? obj : { default: obj }; }
  20. /**
  21. * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  22. *
  23. * This source code is licensed under the MIT license found in the
  24. * LICENSE file in the root directory of this source tree.
  25. *
  26. *
  27. */
  28. const toString = Object.prototype.toString;
  29. const toISOString = Date.prototype.toISOString;
  30. const errorToString = Error.prototype.toString;
  31. const regExpToString = RegExp.prototype.toString;
  32. const symbolToString = Symbol.prototype.toString;
  33. // Explicitly comparing typeof constructor to function avoids undefined as name
  34. // when mock identity-obj-proxy returns the key as the value for any key.
  35. const getConstructorName = val => typeof val.constructor === 'function' && val.constructor.name || 'Object';
  36. // Is val is equal to global window object? Works even if it does not exist :)
  37. /* global window */
  38. const isWindow = val => typeof window !== 'undefined' && val === window;
  39. const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
  40. const NEWLINE_REGEXP = /\n/gi;
  41. class PrettyFormatPluginError extends Error {
  42. constructor(message, stack) {
  43. super(message);
  44. this.stack = stack;
  45. this.name = this.constructor.name;
  46. }
  47. }
  48. function isToStringedArrayType(toStringed) {
  49. return toStringed === '[object Array]' || toStringed === '[object ArrayBuffer]' || toStringed === '[object DataView]' || toStringed === '[object Float32Array]' || toStringed === '[object Float64Array]' || toStringed === '[object Int8Array]' || toStringed === '[object Int16Array]' || toStringed === '[object Int32Array]' || toStringed === '[object Uint8Array]' || toStringed === '[object Uint8ClampedArray]' || toStringed === '[object Uint16Array]' || toStringed === '[object Uint32Array]';
  50. }
  51. function printNumber(val) {
  52. return Object.is(val, -0) ? '-0' : String(val);
  53. }
  54. function printFunction(val, printFunctionName) {
  55. if (!printFunctionName) {
  56. return '[Function]';
  57. }
  58. return '[Function ' + (val.name || 'anonymous') + ']';
  59. }
  60. function printSymbol(val) {
  61. return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
  62. }
  63. function printError(val) {
  64. return '[' + errorToString.call(val) + ']';
  65. }
  66. function printBasicValue(val, printFunctionName, escapeRegex) {
  67. if (val === true || val === false) {
  68. return '' + val;
  69. }
  70. if (val === undefined) {
  71. return 'undefined';
  72. }
  73. if (val === null) {
  74. return 'null';
  75. }
  76. const typeOf = typeof val;
  77. if (typeOf === 'number') {
  78. return printNumber(val);
  79. }
  80. if (typeOf === 'string') {
  81. return '"' + val.replace(/"|\\/g, '\\$&') + '"';
  82. }
  83. if (typeOf === 'function') {
  84. return printFunction(val, printFunctionName);
  85. }
  86. if (typeOf === 'symbol') {
  87. return printSymbol(val);
  88. }
  89. const toStringed = toString.call(val);
  90. if (toStringed === '[object WeakMap]') {
  91. return 'WeakMap {}';
  92. }
  93. if (toStringed === '[object WeakSet]') {
  94. return 'WeakSet {}';
  95. }
  96. if (toStringed === '[object Function]' || toStringed === '[object GeneratorFunction]') {
  97. return printFunction(val, printFunctionName);
  98. }
  99. if (toStringed === '[object Symbol]') {
  100. return printSymbol(val);
  101. }
  102. if (toStringed === '[object Date]') {
  103. return toISOString.call(val);
  104. }
  105. if (toStringed === '[object Error]') {
  106. return printError(val);
  107. }
  108. if (toStringed === '[object RegExp]') {
  109. if (escapeRegex) {
  110. // https://github.com/benjamingr/RegExp.escape/blob/master/polyfill.js
  111. return regExpToString.call(val).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
  112. }
  113. return regExpToString.call(val);
  114. }
  115. if (val instanceof Error) {
  116. return printError(val);
  117. }
  118. return null;
  119. }
  120. function printComplexValue(val, config, indentation, depth, refs, hasCalledToJSON) {
  121. if (refs.indexOf(val) !== -1) {
  122. return '[Circular]';
  123. }
  124. refs = refs.slice();
  125. refs.push(val);
  126. const hitMaxDepth = ++depth > config.maxDepth;
  127. const min = config.min;
  128. if (config.callToJSON && !hitMaxDepth && val.toJSON && typeof val.toJSON === 'function' && !hasCalledToJSON) {
  129. return printer(val.toJSON(), config, indentation, depth, refs, true);
  130. }
  131. const toStringed = toString.call(val);
  132. if (toStringed === '[object Arguments]') {
  133. return hitMaxDepth ? '[Arguments]' : (min ? '' : 'Arguments ') + '[' + (0, _collections.printListItems)(val, config, indentation, depth, refs, printer) + ']';
  134. }
  135. if (isToStringedArrayType(toStringed)) {
  136. return hitMaxDepth ? '[' + val.constructor.name + ']' : (min ? '' : val.constructor.name + ' ') + '[' + (0, _collections.printListItems)(val, config, indentation, depth, refs, printer) + ']';
  137. }
  138. if (toStringed === '[object Map]') {
  139. return hitMaxDepth ? '[Map]' : 'Map {' + (0, _collections.printIteratorEntries)(val.entries(), config, indentation, depth, refs, printer, ' => ') + '}';
  140. }
  141. if (toStringed === '[object Set]') {
  142. return hitMaxDepth ? '[Set]' : 'Set {' + (0, _collections.printIteratorValues)(val.values(), config, indentation, depth, refs, printer) + '}';
  143. }
  144. // Avoid failure to serialize global window object in jsdom test environment.
  145. // For example, not even relevant if window is prop of React element.
  146. return hitMaxDepth || isWindow(val) ? '[' + getConstructorName(val) + ']' : (min ? '' : getConstructorName(val) + ' ') + '{' + (0, _collections.printObjectProperties)(val, config, indentation, depth, refs, printer) + '}';
  147. }
  148. function printPlugin(plugin, val, config, indentation, depth, refs) {
  149. let printed;
  150. try {
  151. printed = plugin.serialize ? plugin.serialize(val, config, indentation, depth, refs, printer) : plugin.print(val, valChild => printer(valChild, config, indentation, depth, refs), str => {
  152. const indentationNext = indentation + config.indent;
  153. return indentationNext + str.replace(NEWLINE_REGEXP, '\n' + indentationNext);
  154. }, {
  155. edgeSpacing: config.spacingOuter,
  156. min: config.min,
  157. spacing: config.spacingInner
  158. }, config.colors);
  159. } catch (error) {
  160. throw new PrettyFormatPluginError(error.message, error.stack);
  161. }
  162. if (typeof printed !== 'string') {
  163. throw new Error(`pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`);
  164. }
  165. return printed;
  166. }
  167. function findPlugin(plugins, val) {
  168. for (let p = 0; p < plugins.length; p++) {
  169. try {
  170. if (plugins[p].test(val)) {
  171. return plugins[p];
  172. }
  173. } catch (error) {
  174. throw new PrettyFormatPluginError(error.message, error.stack);
  175. }
  176. }
  177. return null;
  178. }
  179. function printer(val, config, indentation, depth, refs, hasCalledToJSON) {
  180. const plugin = findPlugin(config.plugins, val);
  181. if (plugin !== null) {
  182. return printPlugin(plugin, val, config, indentation, depth, refs);
  183. }
  184. const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex);
  185. if (basicResult !== null) {
  186. return basicResult;
  187. }
  188. return printComplexValue(val, config, indentation, depth, refs, hasCalledToJSON);
  189. }
  190. const DEFAULT_THEME = {
  191. comment: 'gray',
  192. content: 'reset',
  193. prop: 'yellow',
  194. tag: 'cyan',
  195. value: 'green'
  196. };
  197. const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME);
  198. const DEFAULT_OPTIONS = {
  199. callToJSON: true,
  200. escapeRegex: false,
  201. highlight: false,
  202. indent: 2,
  203. maxDepth: Infinity,
  204. min: false,
  205. plugins: [],
  206. printFunctionName: true,
  207. theme: DEFAULT_THEME
  208. };
  209. function validateOptions(options) {
  210. Object.keys(options).forEach(key => {
  211. if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
  212. throw new Error(`pretty-format: Unknown option "${key}".`);
  213. }
  214. });
  215. if (options.min && options.indent !== undefined && options.indent !== 0) {
  216. throw new Error('pretty-format: Options "min" and "indent" cannot be used together.');
  217. }
  218. if (options.theme !== undefined) {
  219. if (options.theme === null) {
  220. throw new Error(`pretty-format: Option "theme" must not be null.`);
  221. }
  222. if (typeof options.theme !== 'object') {
  223. throw new Error(`pretty-format: Option "theme" must be of type "object" but instead received "${typeof options.theme}".`);
  224. }
  225. }
  226. }
  227. const getColorsHighlight = (options
  228. // $FlowFixMe: Flow thinks keys from `Colors` are missing from `DEFAULT_THEME_KEYS`
  229. ) => DEFAULT_THEME_KEYS.reduce((colors, key) => {
  230. const value = options.theme && options.theme[key] !== undefined ? options.theme[key] : DEFAULT_THEME[key];
  231. const color = _ansiStyles2.default[value];
  232. if (color && typeof color.close === 'string' && typeof color.open === 'string') {
  233. colors[key] = color;
  234. } else {
  235. throw new Error(`pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`);
  236. }
  237. return colors;
  238. }, Object.create(null));
  239. const getColorsEmpty = () =>
  240. // $FlowFixMe: Flow thinks keys from `Colors` are missing from `DEFAULT_THEME_KEYS`
  241. DEFAULT_THEME_KEYS.reduce((colors, key) => {
  242. colors[key] = { close: '', open: '' };
  243. return colors;
  244. }, Object.create(null));
  245. const getPrintFunctionName = options => options && options.printFunctionName !== undefined ? options.printFunctionName : DEFAULT_OPTIONS.printFunctionName;
  246. const getEscapeRegex = options => options && options.escapeRegex !== undefined ? options.escapeRegex : DEFAULT_OPTIONS.escapeRegex;
  247. const getConfig = options => ({
  248. callToJSON: options && options.callToJSON !== undefined ? options.callToJSON : DEFAULT_OPTIONS.callToJSON,
  249. colors: options && options.highlight ? getColorsHighlight(options) : getColorsEmpty(),
  250. escapeRegex: getEscapeRegex(options),
  251. indent: options && options.min ? '' : createIndent(options && options.indent !== undefined ? options.indent : DEFAULT_OPTIONS.indent),
  252. maxDepth: options && options.maxDepth !== undefined ? options.maxDepth : DEFAULT_OPTIONS.maxDepth,
  253. min: options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min,
  254. plugins: options && options.plugins !== undefined ? options.plugins : DEFAULT_OPTIONS.plugins,
  255. printFunctionName: getPrintFunctionName(options),
  256. spacingInner: options && options.min ? ' ' : '\n',
  257. spacingOuter: options && options.min ? '' : '\n'
  258. });
  259. function createIndent(indent) {
  260. return new Array(indent + 1).join(' ');
  261. }
  262. function prettyFormat(val, options) {
  263. if (options) {
  264. validateOptions(options);
  265. if (options.plugins) {
  266. const plugin = findPlugin(options.plugins, val);
  267. if (plugin !== null) {
  268. return printPlugin(plugin, val, getConfig(options), '', 0, []);
  269. }
  270. }
  271. }
  272. const basicResult = printBasicValue(val, getPrintFunctionName(options), getEscapeRegex(options));
  273. if (basicResult !== null) {
  274. return basicResult;
  275. }
  276. return printComplexValue(val, getConfig(options), '', 0, []);
  277. }
  278. prettyFormat.plugins = {
  279. AsymmetricMatcher: _asymmetric_matcher2.default,
  280. ConvertAnsi: _convert_ansi2.default,
  281. DOMCollection: _dom_collection2.default,
  282. DOMElement: _dom_element2.default,
  283. Immutable: _immutable2.default,
  284. ReactElement: _react_element2.default,
  285. ReactTestComponent: _react_test_component2.default
  286. };
  287. module.exports = prettyFormat;