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

Code before:
function isReactWrapper(functionName: any, wrapperName: string) {
  if (!functionName) {
    return false;
  }
  if (functionName.length < wrapperName.length) {
    return false;
  }
  return (
    functionName.lastIndexOf(wrapperName) ===
    functionName.length - wrapperName.length
  );
}

function findPrimitiveIndex(hookStack: any, hook: HookLogEntry) {
  const stackCache = getPrimitiveStackCache();
  const primitiveStack = stackCache.get(hook.primitive);
  if (primitiveStack === undefined) {
    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++;
      }
      if (
        i < hookStack.length - 1 &&
        isReactWrapper(hookStack[i].functionName, hook.dispatcherMethodName)
      ) {
        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 ||

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. 

It also sounds like it contradicts the current implementation of `findPrimitiveIndex` where we always assumed there are two stack frames for the dispatcher method and the public method right next to each other.

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;
    }
  }