UNPKG

14.7 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = exports.DEFAULT_OPTIONS = void 0;
7exports.format = format;
8exports.plugins = void 0;
9
10var _ansiStyles = _interopRequireDefault(require('ansi-styles'));
11
12var _collections = require('./collections');
13
14var _AsymmetricMatcher = _interopRequireDefault(
15 require('./plugins/AsymmetricMatcher')
16);
17
18var _ConvertAnsi = _interopRequireDefault(require('./plugins/ConvertAnsi'));
19
20var _DOMCollection = _interopRequireDefault(require('./plugins/DOMCollection'));
21
22var _DOMElement = _interopRequireDefault(require('./plugins/DOMElement'));
23
24var _Immutable = _interopRequireDefault(require('./plugins/Immutable'));
25
26var _ReactElement = _interopRequireDefault(require('./plugins/ReactElement'));
27
28var _ReactTestComponent = _interopRequireDefault(
29 require('./plugins/ReactTestComponent')
30);
31
32function _interopRequireDefault(obj) {
33 return obj && obj.__esModule ? obj : {default: obj};
34}
35
36/**
37 * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
38 *
39 * This source code is licensed under the MIT license found in the
40 * LICENSE file in the root directory of this source tree.
41 */
42
43/* eslint-disable local/ban-types-eventually */
44const toString = Object.prototype.toString;
45const toISOString = Date.prototype.toISOString;
46const errorToString = Error.prototype.toString;
47const regExpToString = RegExp.prototype.toString;
48/**
49 * Explicitly comparing typeof constructor to function avoids undefined as name
50 * when mock identity-obj-proxy returns the key as the value for any key.
51 */
52
53const getConstructorName = val =>
54 (typeof val.constructor === 'function' && val.constructor.name) || 'Object';
55/* global window */
56
57/** Is val is equal to global window object? Works even if it does not exist :) */
58
59const isWindow = val => typeof window !== 'undefined' && val === window;
60
61const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
62const NEWLINE_REGEXP = /\n/gi;
63
64class PrettyFormatPluginError extends Error {
65 constructor(message, stack) {
66 super(message);
67 this.stack = stack;
68 this.name = this.constructor.name;
69 }
70}
71
72function isToStringedArrayType(toStringed) {
73 return (
74 toStringed === '[object Array]' ||
75 toStringed === '[object ArrayBuffer]' ||
76 toStringed === '[object DataView]' ||
77 toStringed === '[object Float32Array]' ||
78 toStringed === '[object Float64Array]' ||
79 toStringed === '[object Int8Array]' ||
80 toStringed === '[object Int16Array]' ||
81 toStringed === '[object Int32Array]' ||
82 toStringed === '[object Uint8Array]' ||
83 toStringed === '[object Uint8ClampedArray]' ||
84 toStringed === '[object Uint16Array]' ||
85 toStringed === '[object Uint32Array]'
86 );
87}
88
89function printNumber(val) {
90 return Object.is(val, -0) ? '-0' : String(val);
91}
92
93function printBigInt(val) {
94 return String(`${val}n`);
95}
96
97function printFunction(val, printFunctionName) {
98 if (!printFunctionName) {
99 return '[Function]';
100 }
101
102 return '[Function ' + (val.name || 'anonymous') + ']';
103}
104
105function printSymbol(val) {
106 return String(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
107}
108
109function printError(val) {
110 return '[' + errorToString.call(val) + ']';
111}
112/**
113 * The first port of call for printing an object, handles most of the
114 * data-types in JS.
115 */
116
117function printBasicValue(val, printFunctionName, escapeRegex, escapeString) {
118 if (val === true || val === false) {
119 return '' + val;
120 }
121
122 if (val === undefined) {
123 return 'undefined';
124 }
125
126 if (val === null) {
127 return 'null';
128 }
129
130 const typeOf = typeof val;
131
132 if (typeOf === 'number') {
133 return printNumber(val);
134 }
135
136 if (typeOf === 'bigint') {
137 return printBigInt(val);
138 }
139
140 if (typeOf === 'string') {
141 if (escapeString) {
142 return '"' + val.replace(/"|\\/g, '\\$&') + '"';
143 }
144
145 return '"' + val + '"';
146 }
147
148 if (typeOf === 'function') {
149 return printFunction(val, printFunctionName);
150 }
151
152 if (typeOf === 'symbol') {
153 return printSymbol(val);
154 }
155
156 const toStringed = toString.call(val);
157
158 if (toStringed === '[object WeakMap]') {
159 return 'WeakMap {}';
160 }
161
162 if (toStringed === '[object WeakSet]') {
163 return 'WeakSet {}';
164 }
165
166 if (
167 toStringed === '[object Function]' ||
168 toStringed === '[object GeneratorFunction]'
169 ) {
170 return printFunction(val, printFunctionName);
171 }
172
173 if (toStringed === '[object Symbol]') {
174 return printSymbol(val);
175 }
176
177 if (toStringed === '[object Date]') {
178 return isNaN(+val) ? 'Date { NaN }' : toISOString.call(val);
179 }
180
181 if (toStringed === '[object Error]') {
182 return printError(val);
183 }
184
185 if (toStringed === '[object RegExp]') {
186 if (escapeRegex) {
187 // https://github.com/benjamingr/RegExp.escape/blob/main/polyfill.js
188 return regExpToString.call(val).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
189 }
190
191 return regExpToString.call(val);
192 }
193
194 if (val instanceof Error) {
195 return printError(val);
196 }
197
198 return null;
199}
200/**
201 * Handles more complex objects ( such as objects with circular references.
202 * maps and sets etc )
203 */
204
205function printComplexValue(
206 val,
207 config,
208 indentation,
209 depth,
210 refs,
211 hasCalledToJSON
212) {
213 if (refs.indexOf(val) !== -1) {
214 return '[Circular]';
215 }
216
217 refs = refs.slice();
218 refs.push(val);
219 const hitMaxDepth = ++depth > config.maxDepth;
220 const min = config.min;
221
222 if (
223 config.callToJSON &&
224 !hitMaxDepth &&
225 val.toJSON &&
226 typeof val.toJSON === 'function' &&
227 !hasCalledToJSON
228 ) {
229 return printer(val.toJSON(), config, indentation, depth, refs, true);
230 }
231
232 const toStringed = toString.call(val);
233
234 if (toStringed === '[object Arguments]') {
235 return hitMaxDepth
236 ? '[Arguments]'
237 : (min ? '' : 'Arguments ') +
238 '[' +
239 (0, _collections.printListItems)(
240 val,
241 config,
242 indentation,
243 depth,
244 refs,
245 printer
246 ) +
247 ']';
248 }
249
250 if (isToStringedArrayType(toStringed)) {
251 return hitMaxDepth
252 ? '[' + val.constructor.name + ']'
253 : (min
254 ? ''
255 : !config.printBasicPrototype && val.constructor.name === 'Array'
256 ? ''
257 : val.constructor.name + ' ') +
258 '[' +
259 (0, _collections.printListItems)(
260 val,
261 config,
262 indentation,
263 depth,
264 refs,
265 printer
266 ) +
267 ']';
268 }
269
270 if (toStringed === '[object Map]') {
271 return hitMaxDepth
272 ? '[Map]'
273 : 'Map {' +
274 (0, _collections.printIteratorEntries)(
275 val.entries(),
276 config,
277 indentation,
278 depth,
279 refs,
280 printer,
281 ' => '
282 ) +
283 '}';
284 }
285
286 if (toStringed === '[object Set]') {
287 return hitMaxDepth
288 ? '[Set]'
289 : 'Set {' +
290 (0, _collections.printIteratorValues)(
291 val.values(),
292 config,
293 indentation,
294 depth,
295 refs,
296 printer
297 ) +
298 '}';
299 } // Avoid failure to serialize global window object in jsdom test environment.
300 // For example, not even relevant if window is prop of React element.
301
302 return hitMaxDepth || isWindow(val)
303 ? '[' + getConstructorName(val) + ']'
304 : (min
305 ? ''
306 : !config.printBasicPrototype && getConstructorName(val) === 'Object'
307 ? ''
308 : getConstructorName(val) + ' ') +
309 '{' +
310 (0, _collections.printObjectProperties)(
311 val,
312 config,
313 indentation,
314 depth,
315 refs,
316 printer
317 ) +
318 '}';
319}
320
321function isNewPlugin(plugin) {
322 return plugin.serialize != null;
323}
324
325function printPlugin(plugin, val, config, indentation, depth, refs) {
326 let printed;
327
328 try {
329 printed = isNewPlugin(plugin)
330 ? plugin.serialize(val, config, indentation, depth, refs, printer)
331 : plugin.print(
332 val,
333 valChild => printer(valChild, config, indentation, depth, refs),
334 str => {
335 const indentationNext = indentation + config.indent;
336 return (
337 indentationNext +
338 str.replace(NEWLINE_REGEXP, '\n' + indentationNext)
339 );
340 },
341 {
342 edgeSpacing: config.spacingOuter,
343 min: config.min,
344 spacing: config.spacingInner
345 },
346 config.colors
347 );
348 } catch (error) {
349 throw new PrettyFormatPluginError(error.message, error.stack);
350 }
351
352 if (typeof printed !== 'string') {
353 throw new Error(
354 `pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".`
355 );
356 }
357
358 return printed;
359}
360
361function findPlugin(plugins, val) {
362 for (let p = 0; p < plugins.length; p++) {
363 try {
364 if (plugins[p].test(val)) {
365 return plugins[p];
366 }
367 } catch (error) {
368 throw new PrettyFormatPluginError(error.message, error.stack);
369 }
370 }
371
372 return null;
373}
374
375function printer(val, config, indentation, depth, refs, hasCalledToJSON) {
376 const plugin = findPlugin(config.plugins, val);
377
378 if (plugin !== null) {
379 return printPlugin(plugin, val, config, indentation, depth, refs);
380 }
381
382 const basicResult = printBasicValue(
383 val,
384 config.printFunctionName,
385 config.escapeRegex,
386 config.escapeString
387 );
388
389 if (basicResult !== null) {
390 return basicResult;
391 }
392
393 return printComplexValue(
394 val,
395 config,
396 indentation,
397 depth,
398 refs,
399 hasCalledToJSON
400 );
401}
402
403const DEFAULT_THEME = {
404 comment: 'gray',
405 content: 'reset',
406 prop: 'yellow',
407 tag: 'cyan',
408 value: 'green'
409};
410const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME);
411const DEFAULT_OPTIONS = {
412 callToJSON: true,
413 compareKeys: undefined,
414 escapeRegex: false,
415 escapeString: true,
416 highlight: false,
417 indent: 2,
418 maxDepth: Infinity,
419 min: false,
420 plugins: [],
421 printBasicPrototype: true,
422 printFunctionName: true,
423 theme: DEFAULT_THEME
424};
425exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
426
427function validateOptions(options) {
428 Object.keys(options).forEach(key => {
429 if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
430 throw new Error(`pretty-format: Unknown option "${key}".`);
431 }
432 });
433
434 if (options.min && options.indent !== undefined && options.indent !== 0) {
435 throw new Error(
436 'pretty-format: Options "min" and "indent" cannot be used together.'
437 );
438 }
439
440 if (options.theme !== undefined) {
441 if (options.theme === null) {
442 throw new Error(`pretty-format: Option "theme" must not be null.`);
443 }
444
445 if (typeof options.theme !== 'object') {
446 throw new Error(
447 `pretty-format: Option "theme" must be of type "object" but instead received "${typeof options.theme}".`
448 );
449 }
450 }
451}
452
453const getColorsHighlight = options =>
454 DEFAULT_THEME_KEYS.reduce((colors, key) => {
455 const value =
456 options.theme && options.theme[key] !== undefined
457 ? options.theme[key]
458 : DEFAULT_THEME[key];
459 const color = value && _ansiStyles.default[value];
460
461 if (
462 color &&
463 typeof color.close === 'string' &&
464 typeof color.open === 'string'
465 ) {
466 colors[key] = color;
467 } else {
468 throw new Error(
469 `pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.`
470 );
471 }
472
473 return colors;
474 }, Object.create(null));
475
476const getColorsEmpty = () =>
477 DEFAULT_THEME_KEYS.reduce((colors, key) => {
478 colors[key] = {
479 close: '',
480 open: ''
481 };
482 return colors;
483 }, Object.create(null));
484
485const getPrintFunctionName = options =>
486 options && options.printFunctionName !== undefined
487 ? options.printFunctionName
488 : DEFAULT_OPTIONS.printFunctionName;
489
490const getEscapeRegex = options =>
491 options && options.escapeRegex !== undefined
492 ? options.escapeRegex
493 : DEFAULT_OPTIONS.escapeRegex;
494
495const getEscapeString = options =>
496 options && options.escapeString !== undefined
497 ? options.escapeString
498 : DEFAULT_OPTIONS.escapeString;
499
500const getConfig = options => {
501 var _options$printBasicPr;
502
503 return {
504 callToJSON:
505 options && options.callToJSON !== undefined
506 ? options.callToJSON
507 : DEFAULT_OPTIONS.callToJSON,
508 colors:
509 options && options.highlight
510 ? getColorsHighlight(options)
511 : getColorsEmpty(),
512 compareKeys:
513 options && typeof options.compareKeys === 'function'
514 ? options.compareKeys
515 : DEFAULT_OPTIONS.compareKeys,
516 escapeRegex: getEscapeRegex(options),
517 escapeString: getEscapeString(options),
518 indent:
519 options && options.min
520 ? ''
521 : createIndent(
522 options && options.indent !== undefined
523 ? options.indent
524 : DEFAULT_OPTIONS.indent
525 ),
526 maxDepth:
527 options && options.maxDepth !== undefined
528 ? options.maxDepth
529 : DEFAULT_OPTIONS.maxDepth,
530 min:
531 options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min,
532 plugins:
533 options && options.plugins !== undefined
534 ? options.plugins
535 : DEFAULT_OPTIONS.plugins,
536 printBasicPrototype:
537 (_options$printBasicPr =
538 options === null || options === void 0
539 ? void 0
540 : options.printBasicPrototype) !== null &&
541 _options$printBasicPr !== void 0
542 ? _options$printBasicPr
543 : true,
544 printFunctionName: getPrintFunctionName(options),
545 spacingInner: options && options.min ? ' ' : '\n',
546 spacingOuter: options && options.min ? '' : '\n'
547 };
548};
549
550function createIndent(indent) {
551 return new Array(indent + 1).join(' ');
552}
553/**
554 * Returns a presentation string of your `val` object
555 * @param val any potential JavaScript object
556 * @param options Custom settings
557 */
558
559function format(val, options) {
560 if (options) {
561 validateOptions(options);
562
563 if (options.plugins) {
564 const plugin = findPlugin(options.plugins, val);
565
566 if (plugin !== null) {
567 return printPlugin(plugin, val, getConfig(options), '', 0, []);
568 }
569 }
570 }
571
572 const basicResult = printBasicValue(
573 val,
574 getPrintFunctionName(options),
575 getEscapeRegex(options),
576 getEscapeString(options)
577 );
578
579 if (basicResult !== null) {
580 return basicResult;
581 }
582
583 return printComplexValue(val, getConfig(options), '', 0, []);
584}
585
586const plugins = {
587 AsymmetricMatcher: _AsymmetricMatcher.default,
588 ConvertAnsi: _ConvertAnsi.default,
589 DOMCollection: _DOMCollection.default,
590 DOMElement: _DOMElement.default,
591 Immutable: _Immutable.default,
592 ReactElement: _ReactElement.default,
593 ReactTestComponent: _ReactTestComponent.default
594};
595exports.plugins = plugins;
596var _default = format;
597exports.default = _default;