text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import LRU from 'lru-cache';
  import {
    isElement,
    typeOf,
    ContextConsumer,
    ContextProvider,
    ForwardRef,
    Fragment,
    Lazy,
    Memo,
    Portal,
    Profiler,
    StrictMode,
    Suspense,
  } from 'react-is';
  import {REACT_SUSPENSE_LIST_TYPE as SuspenseList} from 'shared/ReactSymbols';
  import {
    TREE_OPERATION_ADD,
    TREE_OPERATION_REMOVE,
    TREE_OPERATION_REMOVE_ROOT,
    TREE_OPERATION_REORDER_CHILDREN,
    TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
    TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
  } from './constants';
  import {ElementTypeRoot} from 'react-devtools-shared/src/types';
  import {
    LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
    LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
    LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
    LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
  } from './constants';
  import {ComponentFilterElementType, ElementTypeHostComponent} from './types';
  import {
    ElementTypeClass,
    ElementTypeForwardRef,
    ElementTypeFunction,
    ElementTypeMemo,
  } from 'react-devtools-shared/src/types';
  import {localStorageGetItem, localStorageSetItem} from './storage';
  import {meta} from './hydration';
  import type {ComponentFilter, ElementType} from './types';
  
  const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
  
  // On large trees, encoding takes significant time.
  // Try to reuse the already encoded strings.
  const encodedStringCache = new LRU({max: 1000});
  
  export function alphaSortKeys(
    a: string | number | Symbol,
    b: string | number | Symbol,
  ): number {
    if (a.toString() > b.toString()) {
      return 1;
    } else if (b.toString() > a.toString()) {
      return -1;
    } else {
      return 0;
    }
  }
  
  export function getAllEnumerableKeys(
    obj: Object,
  ): Set<string | number | Symbol> {
    const keys = new Set();
    let current = obj;
    while (current != null) {
      const currentKeys = [
        ...Object.keys(current),
        ...Object.getOwnPropertySymbols(current),
      ];
      const descriptors = Object.getOwnPropertyDescriptors(current);
      currentKeys.forEach(key => {
        // $FlowFixMe: key can be a Symbol https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
        if (descriptors[key].enumerable) {
          keys.add(key);
        }
      });
      current = Object.getPrototypeOf(current);
    }
    return keys;
  }
  
  export function getDisplayName(
    type: Function,
    fallbackName: string = 'Anonymous',
  ): string {
    const nameFromCache = cachedDisplayNames.get(type);
    if (nameFromCache != null) {
      return nameFromCache;
    }
  
    let displayName = fallbackName;
  
    // The displayName property is not guaranteed to be a string.
    // It's only safe to use for our purposes if it's a string.
    // github.com/facebook/react-devtools/issues/803
    if (typeof type.displayName === 'string') {
      displayName = type.displayName;
    } else if (typeof type.name === 'string' && type.name !== '') {
      displayName = type.name;
    }
  
    cachedDisplayNames.set(type, displayName);
    return displayName;
  }
  
  let uidCounter: number = 0;
  
  export function getUID(): number {
    return ++uidCounter;
  }
  
  export function utfDecodeString(array: Array<number>): string {
    return String.fromCodePoint(...array);
  }
  
  export function utfEncodeString(string: string): Array<number> {
    const cached = encodedStringCache.get(string);
    if (cached !== undefined) {
      return cached;
    }
  
    const encoded = new Array(string.length);
    for (let i = 0; i < string.length; i++) {
      encoded[i] = string.codePointAt(i);
    }
    encodedStringCache.set(string, encoded);
    return encoded;
  }
  
  export function printOperationsArray(operations: Array<number>) {
    // The first two values are always rendererID and rootID
    const rendererID = operations[0];
    const rootID = operations[1];
  
    const logs = [`operations for renderer:${rendererID} and root:${rootID}`];
  
    let i = 2;
  
    // Reassemble the string table.
    const stringTable = [
      null, // ID = 0 corresponds to the null string.
    ];
    const stringTableSize = operations[i++];
    const stringTableEnd = i + stringTableSize;
    while (i < stringTableEnd) {
      const nextLength = operations[i++];
      const nextString = utfDecodeString(
        (operations.slice(i, i + nextLength): any),
      );
      stringTable.push(nextString);
      i += nextLength;
    }
  
    while (i < operations.length) {
      const operation = operations[i];
  
      switch (operation) {
        case TREE_OPERATION_ADD: {
          const id = ((operations[i + 1]: any): number);
          const type = ((operations[i + 2]: any): ElementType);
  
          i += 3;
  
          if (type === ElementTypeRoot) {
            logs.push(`Add new root node ${id}`);
  
            i++; // supportsProfiling
            i++; // hasOwnerMetadata
          } else {
            const parentID = ((operations[i]: any): number);
            i++;
  
            i++; // ownerID
  
            const displayNameStringID = operations[i];
            const displayName = stringTable[displayNameStringID];
            i++;
  
            i++; // key
  
            logs.push(
              `Add node ${id} (${displayName || 'null'}) as child of ${parentID}`,
            );
          }
          break;
        }
        case TREE_OPERATION_REMOVE: {
          const removeLength = ((operations[i + 1]: any): number);
          i += 2;
  
          for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) {
            const id = ((operations[i]: any): number);
            i += 1;
  
            logs.push(`Remove node ${id}`);
          }
          break;
        }
        case TREE_OPERATION_REMOVE_ROOT: {
          i += 1;
  
          logs.push(`Remove root ${rootID}`);
          break;
        }
        case TREE_OPERATION_REORDER_CHILDREN: {
          const id = ((operations[i + 1]: any): number);
          const numChildren = ((operations[i + 2]: any): number);
          i += 3;
          const children = operations.slice(i, i + numChildren);
          i += numChildren;
  
          logs.push(`Re-order node ${id} children ${children.join(',')}`);
          break;
        }
        case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
          // Base duration updates are only sent while profiling is in progress.
          // We can ignore them at this point.
          // The profiler UI uses them lazily in order to generate the tree.
          i += 3;
          break;
        case TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS:
          const id = operations[i + 1];
          const numErrors = operations[i + 2];
          const numWarnings = operations[i + 3];
  
          i += 4;
  
          logs.push(
            `Node ${id} has ${numErrors} errors and ${numWarnings} warnings`,
          );
          break;
        default:
          throw Error(`Unsupported Bridge operation ${operation}`);
      }
    }
  
    console.log(logs.join('\n  '));
  }
  
  export function getDefaultComponentFilters(): Array<ComponentFilter> {
    return [
      {
        type: ComponentFilterElementType,
        value: ElementTypeHostComponent,
        isEnabled: true,
      },
    ];
  }
  
  export function getSavedComponentFilters(): Array<ComponentFilter> {
    try {
      const raw = localStorageGetItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
      if (raw != null) {
        return JSON.parse(raw);
      }
    } catch (error) {}
    return getDefaultComponentFilters();
  }
  
  export function saveComponentFilters(
    componentFilters: Array<ComponentFilter>,
  ): void {
    localStorageSetItem(
      LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
      JSON.stringify(componentFilters),
    );
  }
  
  export function getAppendComponentStack(): boolean {
    try {
      const raw = localStorageGetItem(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY);
      if (raw != null) {
        return JSON.parse(raw);
      }
    } catch (error) {}
    return true;
  }
  
  export function setAppendComponentStack(value: boolean): void {
    localStorageSetItem(
      LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
      JSON.stringify(value),
    );
  }
  
  export function getBreakOnConsoleErrors(): boolean {
    try {
      const raw = localStorageGetItem(
        LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
      );
      if (raw != null) {
        return JSON.parse(raw);
      }
    } catch (error) {}
    return false;
  }
  
  export function setBreakOnConsoleErrors(value: boolean): void {
    localStorageSetItem(
      LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
      JSON.stringify(value),
    );
  }
  
  export function getShowInlineWarningsAndErrors(): boolean {
    try {
      const raw = localStorageGetItem(
        LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
      );
      if (raw != null) {
        return JSON.parse(raw);
      }
    } catch (error) {}
    return true;
  }
  
  export function setShowInlineWarningsAndErrors(value: boolean): void {
    localStorageSetItem(
      LOCAL_STORAGE_SHOW_INLINE_WARNINGS_AND_ERRORS_KEY,
      JSON.stringify(value),
    );
  }
  
  export function separateDisplayNameAndHOCs(
    displayName: string | null,
    type: ElementType,
  ): [string | null, Array<string> | null] {
    if (displayName === null) {
      return [null, null];
    }
  
    let hocDisplayNames = null;
  
    switch (type) {
      case ElementTypeClass:
      case ElementTypeForwardRef:
      case ElementTypeFunction:
      case ElementTypeMemo:
        if (displayName.indexOf('(') >= 0) {
          const matches = displayName.match(/[^()]+/g);
          if (matches != null) {
            displayName = matches.pop();
            hocDisplayNames = matches;
          }
        }
        break;
      default:
        break;
    }
  
    if (type === ElementTypeMemo) {
      if (hocDisplayNames === null) {
        hocDisplayNames = ['Memo'];
      } else {
        hocDisplayNames.unshift('Memo');
      }
    } else if (type === ElementTypeForwardRef) {
      if (hocDisplayNames === null) {
        hocDisplayNames = ['ForwardRef'];
      } else {
        hocDisplayNames.unshift('ForwardRef');
      }
    }
  
    return [displayName, hocDisplayNames];
  }
  
  // Pulled from react-compat
  // https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349
  export function shallowDiffers(prev: Object, next: Object): boolean {
    for (const attribute in prev) {
      if (!(attribute in next)) {
        return true;
      }
    }
    for (const attribute in next) {
      if (prev[attribute] !== next[attribute]) {
        return true;
      }
    }
    return false;
  }
  
  export function getInObject(object: Object, path: Array<string | number>): any {
    return path.reduce((reduced: Object, attr: any): any => {
      if (reduced) {
        if (hasOwnProperty.call(reduced, attr)) {
          return reduced[attr];
        }
        if (typeof reduced[Symbol.iterator] === 'function') {
          // Convert iterable to array and return array[index]
          //
          // TRICKY
          // Don't use [...spread] syntax for this purpose.
          // This project uses @babel/plugin-transform-spread in "loose" mode which only works with Array values.
          // Other types (e.g. typed arrays, Sets) will not spread correctly.
          return Array.from(reduced)[attr];
        }
      }
  
      return null;
    }, object);
  }
  
  export function deletePathInObject(
    object: Object,
    path: Array<string | number>,
  ) {
    const length = path.length;
    const last = path[length - 1];
    if (object != null) {
      const parent = getInObject(object, path.slice(0, length - 1));
      if (parent) {
        if (Array.isArray(parent)) {
          parent.splice(((last: any): number), 1);
        } else {
          delete parent[last];
        }
      }
    }
  }
  
  export function renamePathInObject(
    object: Object,
    oldPath: Array<string | number>,
    newPath: Array<string | number>,
  ) {
    const length = oldPath.length;
    if (object != null) {
      const parent = getInObject(object, oldPath.slice(0, length - 1));
      if (parent) {
        const lastOld = oldPath[length - 1];
        const lastNew = newPath[length - 1];
        parent[lastNew] = parent[lastOld];
        if (Array.isArray(parent)) {
          parent.splice(((lastOld: any): number), 1);
        } else {
          delete parent[lastOld];
        }
      }
    }
  }
  
  export function setInObject(
    object: Object,
    path: Array<string | number>,
    value: any,
  ) {
    const length = path.length;
    const last = path[length - 1];
    if (object != null) {
      const parent = getInObject(object, path.slice(0, length - 1));
      if (parent) {
        parent[last] = value;
      }
    }
  }
  
  export type DataType =
    | 'array'
    | 'array_buffer'
    | 'bigint'
    | 'boolean'
    | 'data_view'
    | 'date'
    | 'function'
    | 'html_all_collection'
    | 'html_element'
    | 'infinity'
    | 'iterator'
    | 'opaque_iterator'
    | 'nan'
    | 'null'
    | 'number'
    | 'object'
    | 'react_element'
    | 'regexp'
    | 'string'
    | 'symbol'
    | 'typed_array'
    | 'undefined'
    | 'unknown';
  
  /**
   * Get a enhanced/artificial type string based on the object instance
   */
  export function getDataType(data: Object): DataType {
    if (data === null) {
      return 'null';
    } else if (data === undefined) {
      return 'undefined';
    }
  
    if (isElement(data)) {
      return 'react_element';
    }
  
    if (typeof HTMLElement !== 'undefined' && data instanceof HTMLElement) {
      return 'html_element';
    }
  
    const type = typeof data;
    switch (type) {
      case 'bigint':
        return 'bigint';
      case 'boolean':
        return 'boolean';
      case 'function':
        return 'function';
      case 'number':
        if (Number.isNaN(data)) {
          return 'nan';
        } else if (!Number.isFinite(data)) {
          return 'infinity';
        } else {
          return 'number';
        }
      case 'object':
        if (Array.isArray(data)) {
          return 'array';
        } else if (ArrayBuffer.isView(data)) {
          return hasOwnProperty.call(data.constructor, 'BYTES_PER_ELEMENT')
            ? 'typed_array'
            : 'data_view';
        } else if (data.constructor && data.constructor.name === 'ArrayBuffer') {
          // HACK This ArrayBuffer check is gross; is there a better way?
          // We could try to create a new DataView with the value.
          // If it doesn't error, we know it's an ArrayBuffer,
          // but this seems kind of awkward and expensive.
          return 'array_buffer';
        } else if (typeof data[Symbol.iterator] === 'function') {
          return data[Symbol.iterator]() === data
            ? 'opaque_iterator'
            : 'iterator';
        } else if (data.constructor && data.constructor.name === 'RegExp') {
          return 'regexp';
        } else {
          const toStringValue = Object.prototype.toString.call(data);
          if (toStringValue === '[object Date]') {
            return 'date';
          } else if (toStringValue === '[object HTMLAllCollection]') {
            return 'html_all_collection';
          }
        }
        return 'object';
      case 'string':
        return 'string';
      case 'symbol':
        return 'symbol';
      case 'undefined':
        if (
          Object.prototype.toString.call(data) === '[object HTMLAllCollection]'
        ) {
          return 'html_all_collection';
        }
        return 'undefined';
      default:
        return 'unknown';
    }
  }
  
  export function getDisplayNameForReactElement(
    element: React$Element<any>,
  ): string | null {
    const elementType = typeOf(element);
    switch (elementType) {
      case ContextConsumer:
        return 'ContextConsumer';
      case ContextProvider:
        return 'ContextProvider';
      case ForwardRef:
        return 'ForwardRef';
      case Fragment:
        return 'Fragment';
      case Lazy:
        return 'Lazy';
      case Memo:
        return 'Memo';
      case Portal:
        return 'Portal';
      case Profiler:
        return 'Profiler';
      case StrictMode:
        return 'StrictMode';
      case Suspense:
        return 'Suspense';
      case SuspenseList:
        return 'SuspenseList';
      default:
        const {type} = element;
        if (typeof type === 'string') {
          return type;
        } else if (typeof type === 'function') {
          return getDisplayName(type, 'Anonymous');
        } else if (type != null) {
          return 'NotImplementedInDevtools';
        } else {
          return 'Element';
        }
    }
  }
  
  const MAX_PREVIEW_STRING_LENGTH = 50;
  
  function truncateForDisplay(
    string: string,
    length: number = MAX_PREVIEW_STRING_LENGTH,
  ) {
    if (string.length > length) {
      return string.substr(0, length) + '…';
    } else {
      return string;
    }
  }
  
  // Attempts to mimic Chrome's inline preview for values.
  // For example, the following value...
  //   {
  //      foo: 123,
  //      bar: "abc",
  //      baz: [true, false],
  //      qux: { ab: 1, cd: 2 }
  //   };
  //
  // Would show a preview of...
  //   {foo: 123, bar: "abc", baz: Array(2), qux: {…}}
  //
  // And the following value...
  //   [
  //     123,
  //     "abc",
  //     [true, false],
  //     { foo: 123, bar: "abc" }
  //   ];
  //
  // Would show a preview of...
  //   [123, "abc", Array(2), {…}]
  export function formatDataForPreview(
    data: any,
    showFormattedValue: boolean,
  ): string {
    if (data != null && hasOwnProperty.call(data, meta.type)) {
      return showFormattedValue
        ? data[meta.preview_long]
        : data[meta.preview_short];
    }
  
    const type = getDataType(data);
  
    switch (type) {
      case 'html_element':
        return `<${truncateForDisplay(data.tagName.toLowerCase())} />`;
      case 'function':
        return truncateForDisplay(
          `ƒ ${typeof data.name === 'function' ? '' : data.name}() {}`,
        );
      case 'string':
        return `"${data}"`;
      case 'bigint':
        return truncateForDisplay(data.toString() + 'n');
      case 'regexp':
        return truncateForDisplay(data.toString());
      case 'symbol':
        return truncateForDisplay(data.toString());
      case 'react_element':
        return `<${truncateForDisplay(
          getDisplayNameForReactElement(data) || 'Unknown',
        )} />`;
      case 'array_buffer':
        return `ArrayBuffer(${data.byteLength})`;
      case 'data_view':
        return `DataView(${data.buffer.byteLength})`;
      case 'array':
        if (showFormattedValue) {
          let formatted = '';
          for (let i = 0; i < data.length; i++) {
            if (i > 0) {
              formatted += ', ';
            }
            formatted += formatDataForPreview(data[i], false);
            if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
              // Prevent doing a lot of unnecessary iteration...
              break;
            }
          }
          return `[${truncateForDisplay(formatted)}]`;
        } else {
          const length = hasOwnProperty.call(data, meta.size)
            ? data[meta.size]
            : data.length;
          return `Array(${length})`;
        }
      case 'typed_array':
        const shortName = `${data.constructor.name}(${data.length})`;
        if (showFormattedValue) {
          let formatted = '';
          for (let i = 0; i < data.length; i++) {
            if (i > 0) {
              formatted += ', ';
            }
            formatted += data[i];
            if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
              // Prevent doing a lot of unnecessary iteration...
              break;
            }
          }
          return `${shortName} [${truncateForDisplay(formatted)}]`;
        } else {
          return shortName;
        }
      case 'iterator':
        const name = data.constructor.name;
  
        if (showFormattedValue) {
          // TRICKY
          // Don't use [...spread] syntax for this purpose.
          // This project uses @babel/plugin-transform-spread in "loose" mode which only works with Array values.
          // Other types (e.g. typed arrays, Sets) will not spread correctly.
          const array = Array.from(data);
  
          let formatted = '';
          for (let i = 0; i < array.length; i++) {
            const entryOrEntries = array[i];
  
            if (i > 0) {
              formatted += ', ';
            }
  
            // TRICKY
            // Browsers display Maps and Sets differently.
            // To mimic their behavior, detect if we've been given an entries tuple.
            //   Map(2) {"abc" => 123, "def" => 123}
            //   Set(2) {"abc", 123}
            if (Array.isArray(entryOrEntries)) {
              const key = formatDataForPreview(entryOrEntries[0], true);
              const value = formatDataForPreview(entryOrEntries[1], false);
              formatted += `${key} => ${value}`;
            } else {
              formatted += formatDataForPreview(entryOrEntries, false);
            }
  
            if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
              // Prevent doing a lot of unnecessary iteration...
              break;
            }
          }
  
          return `${name}(${data.size}) {${truncateForDisplay(formatted)}}`;
        } else {
          return `${name}(${data.size})`;
        }
      case 'opaque_iterator': {
        return data[Symbol.toStringTag];
      }
      case 'date':
        return data.toString();
      case 'object':
        if (showFormattedValue) {
          const keys = Array.from(getAllEnumerableKeys(data)).sort(alphaSortKeys);
  
          let formatted = '';
          for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            if (i > 0) {
              formatted += ', ';
            }
            formatted += `${key.toString()}: ${formatDataForPreview(
              data[key],
              false,
            )}`;
            if (formatted.length > MAX_PREVIEW_STRING_LENGTH) {
              // Prevent doing a lot of unnecessary iteration...
              break;
            }
          }
          return `{${truncateForDisplay(formatted)}}`;
        } else {
          return '{…}';
        }
      case 'boolean':
      case 'number':
      case 'infinity':
      case 'nan':
      case 'null':
      case 'undefined':
        return data;
      default:
        try {
          return truncateForDisplay('' + data);
        } catch (error) {
          return 'unserializable';
        }
    }
  }
  