index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /* !
  2. * loupe
  3. * Copyright(c) 2013 Jake Luer <jake@alogicalparadox.com>
  4. * MIT Licensed
  5. */
  6. import inspectArray from './lib/array'
  7. import inspectTypedArray from './lib/typedarray'
  8. import inspectDate from './lib/date'
  9. import inspectFunction from './lib/function'
  10. import inspectMap from './lib/map'
  11. import inspectNumber from './lib/number'
  12. import inspectBigInt from './lib/bigint'
  13. import inspectRegExp from './lib/regexp'
  14. import inspectSet from './lib/set'
  15. import inspectString from './lib/string'
  16. import inspectSymbol from './lib/symbol'
  17. import inspectPromise from './lib/promise'
  18. import inspectClass from './lib/class'
  19. import inspectObject from './lib/object'
  20. import inspectArguments from './lib/arguments'
  21. import inspectError from './lib/error'
  22. import inspectHTMLElement, { inspectHTMLCollection } from './lib/html'
  23. import { normaliseOptions } from './lib/helpers'
  24. const symbolsSupported = typeof Symbol === 'function' && typeof Symbol.for === 'function'
  25. const chaiInspect = symbolsSupported ? Symbol.for('chai/inspect') : '@@chai/inspect'
  26. let nodeInspect = false
  27. try {
  28. // eslint-disable-next-line global-require
  29. const nodeUtil = require('util')
  30. nodeInspect = nodeUtil.inspect ? nodeUtil.inspect.custom : false
  31. } catch (noNodeInspect) {
  32. nodeInspect = false
  33. }
  34. function FakeMap() {
  35. // eslint-disable-next-line prefer-template
  36. this.key = 'chai/loupe__' + Math.random() + Date.now()
  37. }
  38. FakeMap.prototype = {
  39. // eslint-disable-next-line object-shorthand
  40. get: function get(key) {
  41. return key[this.key]
  42. },
  43. // eslint-disable-next-line object-shorthand
  44. has: function has(key) {
  45. return this.key in key
  46. },
  47. // eslint-disable-next-line object-shorthand
  48. set: function set(key, value) {
  49. if (Object.isExtensible(key)) {
  50. Object.defineProperty(key, this.key, {
  51. // eslint-disable-next-line object-shorthand
  52. value: value,
  53. configurable: true,
  54. })
  55. }
  56. },
  57. }
  58. const constructorMap = new (typeof WeakMap === 'function' ? WeakMap : FakeMap)()
  59. const stringTagMap = {}
  60. const baseTypesMap = {
  61. undefined: (value, options) => options.stylize('undefined', 'undefined'),
  62. null: (value, options) => options.stylize(null, 'null'),
  63. boolean: (value, options) => options.stylize(value, 'boolean'),
  64. Boolean: (value, options) => options.stylize(value, 'boolean'),
  65. number: inspectNumber,
  66. Number: inspectNumber,
  67. bigint: inspectBigInt,
  68. BigInt: inspectBigInt,
  69. string: inspectString,
  70. String: inspectString,
  71. function: inspectFunction,
  72. Function: inspectFunction,
  73. symbol: inspectSymbol,
  74. // A Symbol polyfill will return `Symbol` not `symbol` from typedetect
  75. Symbol: inspectSymbol,
  76. Array: inspectArray,
  77. Date: inspectDate,
  78. Map: inspectMap,
  79. Set: inspectSet,
  80. RegExp: inspectRegExp,
  81. Promise: inspectPromise,
  82. // WeakSet, WeakMap are totally opaque to us
  83. WeakSet: (value, options) => options.stylize('WeakSet{…}', 'special'),
  84. WeakMap: (value, options) => options.stylize('WeakMap{…}', 'special'),
  85. Arguments: inspectArguments,
  86. Int8Array: inspectTypedArray,
  87. Uint8Array: inspectTypedArray,
  88. Uint8ClampedArray: inspectTypedArray,
  89. Int16Array: inspectTypedArray,
  90. Uint16Array: inspectTypedArray,
  91. Int32Array: inspectTypedArray,
  92. Uint32Array: inspectTypedArray,
  93. Float32Array: inspectTypedArray,
  94. Float64Array: inspectTypedArray,
  95. Generator: () => '',
  96. DataView: () => '',
  97. ArrayBuffer: () => '',
  98. Error: inspectError,
  99. HTMLCollection: inspectHTMLCollection,
  100. NodeList: inspectHTMLCollection,
  101. }
  102. // eslint-disable-next-line complexity
  103. const inspectCustom = (value, options, type) => {
  104. if (chaiInspect in value && typeof value[chaiInspect] === 'function') {
  105. return value[chaiInspect](options)
  106. }
  107. if (nodeInspect && nodeInspect in value && typeof value[nodeInspect] === 'function') {
  108. return value[nodeInspect](options.depth, options)
  109. }
  110. if ('inspect' in value && typeof value.inspect === 'function') {
  111. return value.inspect(options.depth, options)
  112. }
  113. if ('constructor' in value && constructorMap.has(value.constructor)) {
  114. return constructorMap.get(value.constructor)(value, options)
  115. }
  116. if (stringTagMap[type]) {
  117. return stringTagMap[type](value, options)
  118. }
  119. return ''
  120. }
  121. const toString = Object.prototype.toString
  122. // eslint-disable-next-line complexity
  123. export function inspect(value, options) {
  124. options = normaliseOptions(options)
  125. options.inspect = inspect
  126. const { customInspect } = options
  127. let type = value === null ? 'null' : typeof value
  128. if (type === 'object') {
  129. type = toString.call(value).slice(8, -1)
  130. }
  131. // If it is a base value that we already support, then use Loupe's inspector
  132. if (baseTypesMap[type]) {
  133. return baseTypesMap[type](value, options)
  134. }
  135. // If `options.customInspect` is set to true then try to use the custom inspector
  136. if (customInspect && value) {
  137. const output = inspectCustom(value, options, type)
  138. if (output) {
  139. if (typeof output === 'string') return output
  140. return inspect(output, options)
  141. }
  142. }
  143. const proto = value ? Object.getPrototypeOf(value) : false
  144. // If it's a plain Object then use Loupe's inspector
  145. if (proto === Object.prototype || proto === null) {
  146. return inspectObject(value, options)
  147. }
  148. // Specifically account for HTMLElements
  149. // eslint-disable-next-line no-undef
  150. if (value && typeof HTMLElement === 'function' && value instanceof HTMLElement) {
  151. return inspectHTMLElement(value, options)
  152. }
  153. if ('constructor' in value) {
  154. // If it is a class, inspect it like an object but add the constructor name
  155. if (value.constructor !== Object) {
  156. return inspectClass(value, options)
  157. }
  158. // If it is an object with an anonymous prototype, display it as an object.
  159. return inspectObject(value, options)
  160. }
  161. // last chance to check if it's an object
  162. if (value === Object(value)) {
  163. return inspectObject(value, options)
  164. }
  165. // We have run out of options! Just stringify the value
  166. return options.stylize(String(value), type)
  167. }
  168. export function registerConstructor(constructor, inspector) {
  169. if (constructorMap.has(constructor)) {
  170. return false
  171. }
  172. constructorMap.set(constructor, inspector)
  173. return true
  174. }
  175. export function registerStringTag(stringTag, inspector) {
  176. if (stringTag in stringTagMap) {
  177. return false
  178. }
  179. stringTagMap[stringTag] = inspector
  180. return true
  181. }
  182. export const custom = chaiInspect
  183. export default inspect