'use strict'; var _ansiStyles = _interopRequireDefault(require('ansi-styles')); var _collections = require('./collections'); var _AsymmetricMatcher = _interopRequireDefault( require('./plugins/AsymmetricMatcher') ); var _ConvertAnsi = _interopRequireDefault(require('./plugins/ConvertAnsi')); var _DOMCollection = _interopRequireDefault(require('./plugins/DOMCollection')); var _DOMElement = _interopRequireDefault(require('./plugins/DOMElement')); var _Immutable = _interopRequireDefault(require('./plugins/Immutable')); var _ReactElement = _interopRequireDefault(require('./plugins/ReactElement')); var _ReactTestComponent = _interopRequireDefault( require('./plugins/ReactTestComponent') ); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } /** * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* eslint-disable local/ban-types-eventually */ const toString = Object.prototype.toString; const toISOString = Date.prototype.toISOString; const errorToString = Error.prototype.toString; const regExpToString = RegExp.prototype.toString; /** * Explicitly comparing typeof constructor to function avoids undefined as name * when mock identity-obj-proxy returns the key as the value for any key. */ const getConstructorName = val => (typeof val.constructor === 'function' && val.constructor.name) || 'Object'; /* global window */ /** Is val is equal to global window object? Works even if it does not exist :) */ const isWindow = val => typeof window !== 'undefined' && val === window; const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/; const NEWLINE_REGEXP = /\n/gi; class PrettyFormatPluginError extends Error { constructor(message, stack) { super(message); this.stack = stack; this.name = this.constructor.name; } } function isToStringedArrayType(toStringed) { 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]' ); } function printNumber(val) { return Object.is(val, -0) ? '-0' : String(val); } function printBigInt(val) { return String(`${val}n`); } function printFunction(val, printFunctionName) { if (!printFunctionName) { return '[Function]'; } return '[Function ' + (val.name || 'anonymous') + ']'; } function printSymbol(val) { return String(val).replace(SYMBOL_REGEXP, 'Symbol($1)'); } function printError(val) { return '[' + errorToString.call(val) + ']'; } /** * The first port of call for printing an object, handles most of the * data-types in JS. */ function printBasicValue(val, printFunctionName, escapeRegex, escapeString) { if (val === true || val === false) { return '' + val; } if (val === undefined) { return 'undefined'; } if (val === null) { return 'null'; } const typeOf = typeof val; if (typeOf === 'number') { return printNumber(val); } if (typeOf === 'bigint') { return printBigInt(val); } if (typeOf === 'string') { if (escapeString) { return '"' + val.replace(/"|\\/g, '\\$&') + '"'; } return '"' + val + '"'; } if (typeOf === 'function') { return printFunction(val, printFunctionName); } if (typeOf === 'symbol') { return printSymbol(val); } const toStringed = toString.call(val); if (toStringed === '[object WeakMap]') { return 'WeakMap {}'; } if (toStringed === '[object WeakSet]') { return 'WeakSet {}'; } if ( toStringed === '[object Function]' || toStringed === '[object GeneratorFunction]' ) { return printFunction(val, printFunctionName); } if (toStringed === '[object Symbol]') { return printSymbol(val); } if (toStringed === '[object Date]') { return isNaN(+val) ? 'Date { NaN }' : toISOString.call(val); } if (toStringed === '[object Error]') { return printError(val); } if (toStringed === '[object RegExp]') { if (escapeRegex) { // https://github.com/benjamingr/RegExp.escape/blob/master/polyfill.js return regExpToString.call(val).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); } return regExpToString.call(val); } if (val instanceof Error) { return printError(val); } return null; } /** * Handles more complex objects ( such as objects with circular references. * maps and sets etc ) */ function printComplexValue( val, config, indentation, depth, refs, hasCalledToJSON ) { if (refs.indexOf(val) !== -1) { return '[Circular]'; } refs = refs.slice(); refs.push(val); const hitMaxDepth = ++depth > config.maxDepth; const min = config.min; if ( config.callToJSON && !hitMaxDepth && val.toJSON && typeof val.toJSON === 'function' && !hasCalledToJSON ) { return printer(val.toJSON(), config, indentation, depth, refs, true); } const toStringed = toString.call(val); if (toStringed === '[object Arguments]') { return hitMaxDepth ? '[Arguments]' : (min ? '' : 'Arguments ') + '[' + (0, _collections.printListItems)( val, config, indentation, depth, refs, printer ) + ']'; } if (isToStringedArrayType(toStringed)) { return hitMaxDepth ? '[' + val.constructor.name + ']' : (min ? '' : val.constructor.name + ' ') + '[' + (0, _collections.printListItems)( val, config, indentation, depth, refs, printer ) + ']'; } if (toStringed === '[object Map]') { return hitMaxDepth ? '[Map]' : 'Map {' + (0, _collections.printIteratorEntries)( val.entries(), config, indentation, depth, refs, printer, ' => ' ) + '}'; } if (toStringed === '[object Set]') { return hitMaxDepth ? '[Set]' : 'Set {' + (0, _collections.printIteratorValues)( val.values(), config, indentation, depth, refs, printer ) + '}'; } // Avoid failure to serialize global window object in jsdom test environment. // For example, not even relevant if window is prop of React element. return hitMaxDepth || isWindow(val) ? '[' + getConstructorName(val) + ']' : (min ? '' : getConstructorName(val) + ' ') + '{' + (0, _collections.printObjectProperties)( val, config, indentation, depth, refs, printer ) + '}'; } function isNewPlugin(plugin) { return plugin.serialize != null; } function printPlugin(plugin, val, config, indentation, depth, refs) { let printed; try { printed = isNewPlugin(plugin) ? plugin.serialize(val, config, indentation, depth, refs, printer) : plugin.print( val, valChild => printer(valChild, config, indentation, depth, refs), str => { const indentationNext = indentation + config.indent; return ( indentationNext + str.replace(NEWLINE_REGEXP, '\n' + indentationNext) ); }, { edgeSpacing: config.spacingOuter, min: config.min, spacing: config.spacingInner }, config.colors ); } catch (error) { throw new PrettyFormatPluginError(error.message, error.stack); } if (typeof printed !== 'string') { throw new Error( `pretty-format: Plugin must return type "string" but instead returned "${typeof printed}".` ); } return printed; } function findPlugin(plugins, val) { for (let p = 0; p < plugins.length; p++) { try { if (plugins[p].test(val)) { return plugins[p]; } } catch (error) { throw new PrettyFormatPluginError(error.message, error.stack); } } return null; } function printer(val, config, indentation, depth, refs, hasCalledToJSON) { const plugin = findPlugin(config.plugins, val); if (plugin !== null) { return printPlugin(plugin, val, config, indentation, depth, refs); } const basicResult = printBasicValue( val, config.printFunctionName, config.escapeRegex, config.escapeString ); if (basicResult !== null) { return basicResult; } return printComplexValue( val, config, indentation, depth, refs, hasCalledToJSON ); } const DEFAULT_THEME = { comment: 'gray', content: 'reset', prop: 'yellow', tag: 'cyan', value: 'green' }; const DEFAULT_THEME_KEYS = Object.keys(DEFAULT_THEME); const DEFAULT_OPTIONS = { callToJSON: true, escapeRegex: false, escapeString: true, highlight: false, indent: 2, maxDepth: Infinity, min: false, plugins: [], printFunctionName: true, theme: DEFAULT_THEME }; function validateOptions(options) { Object.keys(options).forEach(key => { if (!DEFAULT_OPTIONS.hasOwnProperty(key)) { throw new Error(`pretty-format: Unknown option "${key}".`); } }); if (options.min && options.indent !== undefined && options.indent !== 0) { throw new Error( 'pretty-format: Options "min" and "indent" cannot be used together.' ); } if (options.theme !== undefined) { if (options.theme === null) { throw new Error(`pretty-format: Option "theme" must not be null.`); } if (typeof options.theme !== 'object') { throw new Error( `pretty-format: Option "theme" must be of type "object" but instead received "${typeof options.theme}".` ); } } } const getColorsHighlight = options => DEFAULT_THEME_KEYS.reduce((colors, key) => { const value = options.theme && options.theme[key] !== undefined ? options.theme[key] : DEFAULT_THEME[key]; const color = value && _ansiStyles.default[value]; if ( color && typeof color.close === 'string' && typeof color.open === 'string' ) { colors[key] = color; } else { throw new Error( `pretty-format: Option "theme" has a key "${key}" whose value "${value}" is undefined in ansi-styles.` ); } return colors; }, Object.create(null)); const getColorsEmpty = () => DEFAULT_THEME_KEYS.reduce((colors, key) => { colors[key] = { close: '', open: '' }; return colors; }, Object.create(null)); const getPrintFunctionName = options => options && options.printFunctionName !== undefined ? options.printFunctionName : DEFAULT_OPTIONS.printFunctionName; const getEscapeRegex = options => options && options.escapeRegex !== undefined ? options.escapeRegex : DEFAULT_OPTIONS.escapeRegex; const getEscapeString = options => options && options.escapeString !== undefined ? options.escapeString : DEFAULT_OPTIONS.escapeString; const getConfig = options => ({ callToJSON: options && options.callToJSON !== undefined ? options.callToJSON : DEFAULT_OPTIONS.callToJSON, colors: options && options.highlight ? getColorsHighlight(options) : getColorsEmpty(), escapeRegex: getEscapeRegex(options), escapeString: getEscapeString(options), indent: options && options.min ? '' : createIndent( options && options.indent !== undefined ? options.indent : DEFAULT_OPTIONS.indent ), maxDepth: options && options.maxDepth !== undefined ? options.maxDepth : DEFAULT_OPTIONS.maxDepth, min: options && options.min !== undefined ? options.min : DEFAULT_OPTIONS.min, plugins: options && options.plugins !== undefined ? options.plugins : DEFAULT_OPTIONS.plugins, printFunctionName: getPrintFunctionName(options), spacingInner: options && options.min ? ' ' : '\n', spacingOuter: options && options.min ? '' : '\n' }); function createIndent(indent) { return new Array(indent + 1).join(' '); } /** * Returns a presentation string of your `val` object * @param val any potential JavaScript object * @param options Custom settings */ function prettyFormat(val, options) { if (options) { validateOptions(options); if (options.plugins) { const plugin = findPlugin(options.plugins, val); if (plugin !== null) { return printPlugin(plugin, val, getConfig(options), '', 0, []); } } } const basicResult = printBasicValue( val, getPrintFunctionName(options), getEscapeRegex(options), getEscapeString(options) ); if (basicResult !== null) { return basicResult; } return printComplexValue(val, getConfig(options), '', 0, []); } prettyFormat.plugins = { AsymmetricMatcher: _AsymmetricMatcher.default, ConvertAnsi: _ConvertAnsi.default, DOMCollection: _DOMCollection.default, DOMElement: _DOMElement.default, Immutable: _Immutable.default, ReactElement: _ReactElement.default, ReactTestComponent: _ReactTestComponent.default }; module.exports = prettyFormat;