inspect.js.flow 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // @flow strict
  2. /* eslint-disable flowtype/no-weak-types */
  3. import nodejsCustomInspectSymbol from './nodejsCustomInspectSymbol';
  4. const MAX_ARRAY_LENGTH = 10;
  5. const MAX_RECURSIVE_DEPTH = 2;
  6. /**
  7. * Used to print values in error messages.
  8. */
  9. export default function inspect(value: mixed): string {
  10. return formatValue(value, []);
  11. }
  12. function formatValue(value: mixed, seenValues: Array<mixed>): string {
  13. switch (typeof value) {
  14. case 'string':
  15. return JSON.stringify(value);
  16. case 'function':
  17. return value.name ? `[function ${value.name}]` : '[function]';
  18. case 'object':
  19. if (value === null) {
  20. return 'null';
  21. }
  22. return formatObjectValue(value, seenValues);
  23. default:
  24. return String(value);
  25. }
  26. }
  27. function formatObjectValue(
  28. value: Object,
  29. previouslySeenValues: Array<mixed>,
  30. ): string {
  31. if (previouslySeenValues.indexOf(value) !== -1) {
  32. return '[Circular]';
  33. }
  34. const seenValues = [...previouslySeenValues, value];
  35. const customInspectFn = getCustomFn(value);
  36. if (customInspectFn !== undefined) {
  37. const customValue = customInspectFn.call(value);
  38. // check for infinite recursion
  39. if (customValue !== value) {
  40. return typeof customValue === 'string'
  41. ? customValue
  42. : formatValue(customValue, seenValues);
  43. }
  44. } else if (Array.isArray(value)) {
  45. return formatArray(value, seenValues);
  46. }
  47. return formatObject(value, seenValues);
  48. }
  49. function formatObject(object: Object, seenValues: Array<mixed>): string {
  50. const keys = Object.keys(object);
  51. if (keys.length === 0) {
  52. return '{}';
  53. }
  54. if (seenValues.length > MAX_RECURSIVE_DEPTH) {
  55. return '[' + getObjectTag(object) + ']';
  56. }
  57. const properties = keys.map((key) => {
  58. const value = formatValue(object[key], seenValues);
  59. return key + ': ' + value;
  60. });
  61. return '{ ' + properties.join(', ') + ' }';
  62. }
  63. function formatArray(array: Array<mixed>, seenValues: Array<mixed>): string {
  64. if (array.length === 0) {
  65. return '[]';
  66. }
  67. if (seenValues.length > MAX_RECURSIVE_DEPTH) {
  68. return '[Array]';
  69. }
  70. const len = Math.min(MAX_ARRAY_LENGTH, array.length);
  71. const remaining = array.length - len;
  72. const items = [];
  73. for (let i = 0; i < len; ++i) {
  74. items.push(formatValue(array[i], seenValues));
  75. }
  76. if (remaining === 1) {
  77. items.push('... 1 more item');
  78. } else if (remaining > 1) {
  79. items.push(`... ${remaining} more items`);
  80. }
  81. return '[' + items.join(', ') + ']';
  82. }
  83. function getCustomFn(object: Object) {
  84. const customInspectFn = object[String(nodejsCustomInspectSymbol)];
  85. if (typeof customInspectFn === 'function') {
  86. return customInspectFn;
  87. }
  88. if (typeof object.inspect === 'function') {
  89. return object.inspect;
  90. }
  91. }
  92. function getObjectTag(object: Object): string {
  93. const tag = Object.prototype.toString
  94. .call(object)
  95. .replace(/^\[object /, '')
  96. .replace(/]$/, '');
  97. if (tag === 'Object' && typeof object.constructor === 'function') {
  98. const name = object.constructor.name;
  99. if (typeof name === 'string' && name !== '') {
  100. return name;
  101. }
  102. }
  103. return tag;
  104. }