1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import {
    
  11.   REACT_ELEMENT_TYPE,
    
  12.   REACT_FORWARD_REF_TYPE,
    
  13.   REACT_LAZY_TYPE,
    
  14.   REACT_MEMO_TYPE,
    
  15.   REACT_SUSPENSE_TYPE,
    
  16.   REACT_SUSPENSE_LIST_TYPE,
    
  17. } from 'shared/ReactSymbols';
    
  18. 
    
  19. import type {LazyComponent} from 'react/src/ReactLazy';
    
  20. 
    
  21. import isArray from 'shared/isArray';
    
  22. import getPrototypeOf from 'shared/getPrototypeOf';
    
  23. 
    
  24. // Used for DEV messages to keep track of which parent rendered some props,
    
  25. // in case they error.
    
  26. export const jsxPropsParents: WeakMap<any, any> = new WeakMap();
    
  27. export const jsxChildrenParents: WeakMap<any, any> = new WeakMap();
    
  28. 
    
  29. function isObjectPrototype(object: any): boolean {
    
  30.   if (!object) {
    
  31.     return false;
    
  32.   }
    
  33.   const ObjectPrototype = Object.prototype;
    
  34.   if (object === ObjectPrototype) {
    
  35.     return true;
    
  36.   }
    
  37.   // It might be an object from a different Realm which is
    
  38.   // still just a plain simple object.
    
  39.   if (getPrototypeOf(object)) {
    
  40.     return false;
    
  41.   }
    
  42.   const names = Object.getOwnPropertyNames(object);
    
  43.   for (let i = 0; i < names.length; i++) {
    
  44.     if (!(names[i] in ObjectPrototype)) {
    
  45.       return false;
    
  46.     }
    
  47.   }
    
  48.   return true;
    
  49. }
    
  50. 
    
  51. export function isSimpleObject(object: any): boolean {
    
  52.   if (!isObjectPrototype(getPrototypeOf(object))) {
    
  53.     return false;
    
  54.   }
    
  55.   const names = Object.getOwnPropertyNames(object);
    
  56.   for (let i = 0; i < names.length; i++) {
    
  57.     const descriptor = Object.getOwnPropertyDescriptor(object, names[i]);
    
  58.     if (!descriptor) {
    
  59.       return false;
    
  60.     }
    
  61.     if (!descriptor.enumerable) {
    
  62.       if (
    
  63.         (names[i] === 'key' || names[i] === 'ref') &&
    
  64.         typeof descriptor.get === 'function'
    
  65.       ) {
    
  66.         // React adds key and ref getters to props objects to issue warnings.
    
  67.         // Those getters will not be transferred to the client, but that's ok,
    
  68.         // so we'll special case them.
    
  69.         continue;
    
  70.       }
    
  71.       return false;
    
  72.     }
    
  73.   }
    
  74.   return true;
    
  75. }
    
  76. 
    
  77. export function objectName(object: mixed): string {
    
  78.   // $FlowFixMe[method-unbinding]
    
  79.   const name = Object.prototype.toString.call(object);
    
  80.   return name.replace(/^\[object (.*)\]$/, function (m, p0) {
    
  81.     return p0;
    
  82.   });
    
  83. }
    
  84. 
    
  85. function describeKeyForErrorMessage(key: string): string {
    
  86.   const encodedKey = JSON.stringify(key);
    
  87.   return '"' + key + '"' === encodedKey ? key : encodedKey;
    
  88. }
    
  89. 
    
  90. export function describeValueForErrorMessage(value: mixed): string {
    
  91.   switch (typeof value) {
    
  92.     case 'string': {
    
  93.       return JSON.stringify(
    
  94.         value.length <= 10 ? value : value.slice(0, 10) + '...',
    
  95.       );
    
  96.     }
    
  97.     case 'object': {
    
  98.       if (isArray(value)) {
    
  99.         return '[...]';
    
  100.       }
    
  101.       const name = objectName(value);
    
  102.       if (name === 'Object') {
    
  103.         return '{...}';
    
  104.       }
    
  105.       return name;
    
  106.     }
    
  107.     case 'function':
    
  108.       return 'function';
    
  109.     default:
    
  110.       // eslint-disable-next-line react-internal/safe-string-coercion
    
  111.       return String(value);
    
  112.   }
    
  113. }
    
  114. 
    
  115. function describeElementType(type: any): string {
    
  116.   if (typeof type === 'string') {
    
  117.     return type;
    
  118.   }
    
  119.   switch (type) {
    
  120.     case REACT_SUSPENSE_TYPE:
    
  121.       return 'Suspense';
    
  122.     case REACT_SUSPENSE_LIST_TYPE:
    
  123.       return 'SuspenseList';
    
  124.   }
    
  125.   if (typeof type === 'object') {
    
  126.     switch (type.$$typeof) {
    
  127.       case REACT_FORWARD_REF_TYPE:
    
  128.         return describeElementType(type.render);
    
  129.       case REACT_MEMO_TYPE:
    
  130.         return describeElementType(type.type);
    
  131.       case REACT_LAZY_TYPE: {
    
  132.         const lazyComponent: LazyComponent<any, any> = (type: any);
    
  133.         const payload = lazyComponent._payload;
    
  134.         const init = lazyComponent._init;
    
  135.         try {
    
  136.           // Lazy may contain any component type so we recursively resolve it.
    
  137.           return describeElementType(init(payload));
    
  138.         } catch (x) {}
    
  139.       }
    
  140.     }
    
  141.   }
    
  142.   return '';
    
  143. }
    
  144. 
    
  145. export function describeObjectForErrorMessage(
    
  146.   objectOrArray: {+[key: string | number]: mixed, ...} | $ReadOnlyArray<mixed>,
    
  147.   expandedName?: string,
    
  148. ): string {
    
  149.   const objKind = objectName(objectOrArray);
    
  150.   if (objKind !== 'Object' && objKind !== 'Array') {
    
  151.     return objKind;
    
  152.   }
    
  153.   let str = '';
    
  154.   let start = -1;
    
  155.   let length = 0;
    
  156.   if (isArray(objectOrArray)) {
    
  157.     if (__DEV__ && jsxChildrenParents.has(objectOrArray)) {
    
  158.       // Print JSX Children
    
  159.       const type = jsxChildrenParents.get(objectOrArray);
    
  160.       str = '<' + describeElementType(type) + '>';
    
  161.       const array: $ReadOnlyArray<mixed> = objectOrArray;
    
  162.       for (let i = 0; i < array.length; i++) {
    
  163.         const value = array[i];
    
  164.         let substr;
    
  165.         if (typeof value === 'string') {
    
  166.           substr = value;
    
  167.         } else if (typeof value === 'object' && value !== null) {
    
  168.           substr = '{' + describeObjectForErrorMessage(value) + '}';
    
  169.         } else {
    
  170.           substr = '{' + describeValueForErrorMessage(value) + '}';
    
  171.         }
    
  172.         if ('' + i === expandedName) {
    
  173.           start = str.length;
    
  174.           length = substr.length;
    
  175.           str += substr;
    
  176.         } else if (substr.length < 15 && str.length + substr.length < 40) {
    
  177.           str += substr;
    
  178.         } else {
    
  179.           str += '{...}';
    
  180.         }
    
  181.       }
    
  182.       str += '</' + describeElementType(type) + '>';
    
  183.     } else {
    
  184.       // Print Array
    
  185.       str = '[';
    
  186.       const array: $ReadOnlyArray<mixed> = objectOrArray;
    
  187.       for (let i = 0; i < array.length; i++) {
    
  188.         if (i > 0) {
    
  189.           str += ', ';
    
  190.         }
    
  191.         const value = array[i];
    
  192.         let substr;
    
  193.         if (typeof value === 'object' && value !== null) {
    
  194.           substr = describeObjectForErrorMessage(value);
    
  195.         } else {
    
  196.           substr = describeValueForErrorMessage(value);
    
  197.         }
    
  198.         if ('' + i === expandedName) {
    
  199.           start = str.length;
    
  200.           length = substr.length;
    
  201.           str += substr;
    
  202.         } else if (substr.length < 10 && str.length + substr.length < 40) {
    
  203.           str += substr;
    
  204.         } else {
    
  205.           str += '...';
    
  206.         }
    
  207.       }
    
  208.       str += ']';
    
  209.     }
    
  210.   } else {
    
  211.     if (objectOrArray.$$typeof === REACT_ELEMENT_TYPE) {
    
  212.       str = '<' + describeElementType(objectOrArray.type) + '/>';
    
  213.     } else if (__DEV__ && jsxPropsParents.has(objectOrArray)) {
    
  214.       // Print JSX
    
  215.       const type = jsxPropsParents.get(objectOrArray);
    
  216.       str = '<' + (describeElementType(type) || '...');
    
  217.       const object: {+[key: string | number]: mixed, ...} = objectOrArray;
    
  218.       const names = Object.keys(object);
    
  219.       for (let i = 0; i < names.length; i++) {
    
  220.         str += ' ';
    
  221.         const name = names[i];
    
  222.         str += describeKeyForErrorMessage(name) + '=';
    
  223.         const value = object[name];
    
  224.         let substr;
    
  225.         if (
    
  226.           name === expandedName &&
    
  227.           typeof value === 'object' &&
    
  228.           value !== null
    
  229.         ) {
    
  230.           substr = describeObjectForErrorMessage(value);
    
  231.         } else {
    
  232.           substr = describeValueForErrorMessage(value);
    
  233.         }
    
  234.         if (typeof value !== 'string') {
    
  235.           substr = '{' + substr + '}';
    
  236.         }
    
  237.         if (name === expandedName) {
    
  238.           start = str.length;
    
  239.           length = substr.length;
    
  240.           str += substr;
    
  241.         } else if (substr.length < 10 && str.length + substr.length < 40) {
    
  242.           str += substr;
    
  243.         } else {
    
  244.           str += '...';
    
  245.         }
    
  246.       }
    
  247.       str += '>';
    
  248.     } else {
    
  249.       // Print Object
    
  250.       str = '{';
    
  251.       const object: {+[key: string | number]: mixed, ...} = objectOrArray;
    
  252.       const names = Object.keys(object);
    
  253.       for (let i = 0; i < names.length; i++) {
    
  254.         if (i > 0) {
    
  255.           str += ', ';
    
  256.         }
    
  257.         const name = names[i];
    
  258.         str += describeKeyForErrorMessage(name) + ': ';
    
  259.         const value = object[name];
    
  260.         let substr;
    
  261.         if (typeof value === 'object' && value !== null) {
    
  262.           substr = describeObjectForErrorMessage(value);
    
  263.         } else {
    
  264.           substr = describeValueForErrorMessage(value);
    
  265.         }
    
  266.         if (name === expandedName) {
    
  267.           start = str.length;
    
  268.           length = substr.length;
    
  269.           str += substr;
    
  270.         } else if (substr.length < 10 && str.length + substr.length < 40) {
    
  271.           str += substr;
    
  272.         } else {
    
  273.           str += '...';
    
  274.         }
    
  275.       }
    
  276.       str += '}';
    
  277.     }
    
  278.   }
    
  279.   if (expandedName === undefined) {
    
  280.     return str;
    
  281.   }
    
  282.   if (start > -1 && length > 0) {
    
  283.     const highlight = ' '.repeat(start) + '^'.repeat(length);
    
  284.     return '\n  ' + str + '\n  ' + highlight;
    
  285.   }
    
  286.   return '\n  ' + str;
    
  287. }