Repository: facebook/react
File: packages/react-debug-tools/src/ReactDebugHooks.js (JavaScript)

Code before:
    return -1;
  }
  for (let i = 0; i < primitiveStack.length && i < hookStack.length; i++) {
    if (primitiveStack[i].source !== hookStack[i].source) {
      // If the next frame is a method from the dispatcher, we
      // assume that the next frame after that is the actual public API call.
      // This prohibits nesting dispatcher calls in hooks.
      if (
        i < hookStack.length - 1 &&
        isReactWrapper(hookStack[i].functionName, hook.dispatcherMethodName)
      ) {
        i++;
        i++;
      }
      return i;
    }
  }
  return -1;
}

function parseTrimmedStack(rootStack: any, hook: HookLogEntry) {
  // Get the stack trace between the primitive hook function and
  // the root function call. I.e. the stack frames of custom hooks.
  const hookStack = ErrorStackParser.parse(hook.stackError);
  const rootIndex = findCommonAncestorIndex(rootStack, hookStack);
  const primitiveIndex = findPrimitiveIndex(hookStack, hook);
  if (
    rootIndex === -1 ||
    primitiveIndex === -1 ||
    rootIndex - primitiveIndex < 2
  ) {
    // Something went wrong. Give up.
    return null;
  }
  return [
    hookStack[primitiveIndex - 1],
    hookStack.slice(primitiveIndex, rootIndex - 1),
  ];
}

function parseHookName(functionName: void | string): string {
  if (!functionName) {
    return '';
  }
  let startIndex = functionName.lastIndexOf('.');
  if (startIndex === -1) {
    startIndex = 0;
  } else {
    startIndex += 1;
  }
  if (functionName.slice(startIndex, startIndex + 3) === 'use') {

Review comment (suggestion):
I don't see how this can happen since the dispatcher is private. Without the "wrapper -> dispatcher" assumption I'm out of ideas on how to enable arbitrary wrapper names like `useHostTransitionStatus` is trying to enable.

Code after:
export type HookSource = {
  lineNumber: number | null,
  columnNumber: number | null,
  fileName: string | null,
  functionName: string | null,
};

export type HooksNode = {
  id: number | null,
  isStateEditable: boolean,
  name: string,
  value: mixed,
  subHooks: Array<HooksNode>,
  debugInfo: null | ReactDebugInfo,
  hookSource: null | HookSource,
};
export type HooksTree = Array<HooksNode>;

// Don't assume
//
// We can't assume that stack frames are nth steps away from anything.
// E.g. we can't assume that the root call shares all frames with the stack
// of a hook call. A simple way to demonstrate this is wrapping `new Error()`
// in a wrapper constructor like a polyfill. That'll add an extra frame.
// Similar things can happen with the call to the dispatcher. The top frame
// may not be the primitive.
//
// We also can't assume that the last frame of the root call is the same
// frame as the last frame of the hook call because long stack traces can be
// truncated to a stack trace limit.

let mostLikelyAncestorIndex = 0;

function findSharedIndex(hookStack: any, rootStack: any, rootIndex: number) {
  const source = rootStack[rootIndex].source;
  hookSearch: for (let i = 0; i < hookStack.length; i++) {
    if (hookStack[i].source === source) {
      // This looks like a match. Validate that the rest of both stack match up.
      for (
        let a = rootIndex + 1, b = i + 1;
        a < rootStack.length && b < hookStack.length;
        a++, b++
      ) {
        if (hookStack[b].source !== rootStack[a].source) {
          // If not, give up and try a different match.
          continue hookSearch;
        }
      }
      return i;
    }
  }