Repository: facebook/react
File: packages/react-server/src/ReactFlightStackConfigV8.js (JavaScript)

Code before:
/**
 * Copyright (c) Meta Platforms, Inc. and 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 type {ReactStackTrace} from 'shared/ReactTypes';

import DefaultPrepareStackTrace from 'shared/DefaultPrepareStackTrace';

function getStack(error: Error): string {
  // We override Error.prepareStackTrace with our own version that normalizes
  // the stack to V8 formatting even if the server uses other formatting.
  // It also ensures that source maps are NOT applied to this since that can
  // be slow we're better off doing that lazily from the client instead of
  // eagerly on the server. If the stack has already been read, then we might
  // not get a normalized stack and it might still have been source mapped.
  const previousPrepare = Error.prepareStackTrace;
  Error.prepareStackTrace = DefaultPrepareStackTrace;
  try {
    // eslint-disable-next-line react-internal/safe-string-coercion
    return String(error.stack);
  } finally {
    Error.prepareStackTrace = previousPrepare;
  }
}

// This matches either of these V8 formats.
//     at name (filename:0:0)
//     at filename:0:0
//     at async filename:0:0
const frameRegExp =
  /^ {3} at (?:(.+) \((?:(.+):(\d+):(\d+)|\<anonymous\>)\)|(?:async )?(.+):(\d+):(\d+)|\<anonymous\>)$/;

export function parseStackTrace(
  error: Error,
  skipFrames: number,
): ReactStackTrace {
  let stack = getStack(error);
  if (stack.startsWith('Error: react-stack-top-frame\n')) {
    // V8's default formatting prefixes with the error message which we
    // don't want/need.
    stack = stack.slice(29);
  }
  let idx = stack.indexOf('react-stack-bottom-frame');
  if (idx !== -1) {
    idx = stack.lastIndexOf('\n', idx);
  }
  if (idx !== -1) {
    // Cut off everything after the bottom frame since it'll be internals.
    stack = stack.slice(0, idx);
  }
  const frames = stack.split('\n');
  const parsedFrames: ReactStackTrace = [];
  // We skip top frames here since they may or may not be parseable but we
  // want to skip the same number of frames regardless. I.e. we can't do it
  // in the caller.
  for (let i = skipFrames; i < frames.length; i++) {
    const parsed = frameRegExp.exec(frames[i]);
    if (!parsed) {
      continue;
    }
    let name = parsed[1] || '';
    if (name === '<anonymous>') {
      name = '';
    }
    let filename = parsed[2] || parsed[5] || '';
    if (filename === '<anonymous>') {
      filename = '';
    }
    const line = +(parsed[3] || parsed[6]);
    const col = +(parsed[4] || parsed[7]);
    parsedFrames.push([name, filename, line, col]);
  }
  return parsedFrames;
}


Review comment (suggestion):
We might have to call the previous one regardless in case the previous one is a custom one that also contains side-effects. Or do that in Next.js depending on the timing. The tricky bit is figuring how if that's the native one we want to avoid because it would be wasteful to call that.

Code after:
      // as function names in UIs on the client as well as stacks.
      if (callSite.isConstructor()) {
        name = 'new ' + name;
      } else if (!callSite.isToplevel()) {
        name = getMethodCallName(callSite);
      }
      if (name === '<anonymous>') {
        name = '';
      }
      let filename = callSite.getScriptNameOrSourceURL() || '<anonymous>';
      if (filename === '<anonymous>') {
        filename = '';
      }
      if (callSite.isEval() && !filename) {
        const origin = callSite.getEvalOrigin();
        if (origin) {
          filename = origin.toString() + ', <anonymous>';
        }
      }
      const line = callSite.getLineNumber() || 0;
      const col = callSite.getColumnNumber() || 0;
      result.push([name, filename, line, col]);
    }
  }
  // At the same time we generate a string stack trace just in case someone
  // else reads it. Ideally, we'd call the previous prepareStackTrace to
  // ensure it's in the expected format but it's common for that to be
  // source mapped and since we do a lot of eager parsing of errors, it
  // would be slow in those environments. We could maybe just rely on those
  // environments having to disable source mapping globally to speed things up.
  // For now, we just generate a default V8 formatted stack trace without
  // source mapping as a fallback.
  const name = error.name || 'Error';
  const message = error.message || '';
  let stack = name + ': ' + message;
  for (let i = 0; i < structuredStackTrace.length; i++) {
    stack += '\n    at ' + structuredStackTrace[i].toString();
  }
  collectedStackTrace = result;
  return stack;
}

// This matches either of these V8 formats.
//     at name (filename:0:0)
//     at filename:0:0
//     at async filename:0:0
const frameRegExp =
  /^ {3} at (?:(.+) \((?:(.+):(\d+):(\d+)|\<anonymous\>)\)|(?:async )?(.+):(\d+):(\d+)|\<anonymous\>)$/;

export function parseStackTrace(
  error: Error,