{"version":3,"file":"rewrite-stack-trace.js","sources":["../../src/errors/rewrite-stack-trace.ts"],"sourcesContent":["/**\n * This file uses the internal V8 Stack Trace API (https://v8.dev/docs/stack-trace-api)\n * to provide utilities to rewrite the stack trace.\n * When this API is not present, all the functions in this file become noops.\n *\n * beginHiddenCallStack(fn) and endHiddenCallStack(fn) wrap their parameter to\n * mark an hidden portion of the stack trace. The function passed to\n * beginHiddenCallStack is the first hidden function, while the function passed\n * to endHiddenCallStack is the first shown function.\n *\n * When an error is thrown _outside_ of the hidden zone, everything between\n * beginHiddenCallStack and endHiddenCallStack will not be shown.\n * If an error is thrown _inside_ the hidden zone, then the whole stack trace\n * will be visible: this is to avoid hiding real bugs.\n * However, if an error inside the hidden zone is expected, it can be marked\n * with the expectedError(error) function to keep the hidden frames hidden.\n *\n * Consider this call stack (the outer function is the bottom one):\n *\n *   1. a()\n *   2. endHiddenCallStack(b)()\n *   3. c()\n *   4. beginHiddenCallStack(d)()\n *   5. e()\n *   6. f()\n *\n * - If a() throws an error, then its shown call stack will be \"a, b, e, f\"\n * - If b() throws an error, then its shown call stack will be \"b, e, f\"\n * - If c() throws an expected error, then its shown call stack will be \"e, f\"\n * - If c() throws an unexpected error, then its shown call stack will be \"c, d, e, f\"\n * - If d() throws an expected error, then its shown call stack will be \"e, f\"\n * - If d() throws an unexpected error, then its shown call stack will be \"d, e, f\"\n * - If e() throws an error, then its shown call stack will be \"e, f\"\n *\n * Additionally, an error can inject additional \"virtual\" stack frames using the\n * injectVirtualStackFrame(error, filename) function: those are injected as a\n * replacement of the hidden frames.\n * In the example above, if we called injectVirtualStackFrame(err, \"h\") and\n * injectVirtualStackFrame(err, \"i\") on the expected error thrown by c(), its\n * shown call stack would have been \"h, i, e, f\".\n * This can be useful, for example, to report config validation errors as if they\n * were directly thrown in the config file.\n */\n\nconst ErrorToString = Function.call.bind(Error.prototype.toString);\n\nconst SUPPORTED =\n  !!Error.captureStackTrace &&\n  Object.getOwnPropertyDescriptor(Error, \"stackTraceLimit\")?.writable === true;\n\nconst START_HIDING = \"startHiding - secret - don't use this - v1\";\nconst STOP_HIDING = \"stopHiding - secret - don't use this - v1\";\n\ntype CallSite = NodeJS.CallSite;\n\nconst expectedErrors = new WeakSet<Error>();\nconst virtualFrames = new WeakMap<Error, CallSite[]>();\n\nfunction CallSite(filename: string): CallSite {\n  // We need to use a prototype otherwise it breaks source-map-support's internals\n  return Object.create({\n    isNative: () => false,\n    isConstructor: () => false,\n    isToplevel: () => true,\n    getFileName: () => filename,\n    getLineNumber: () => undefined,\n    getColumnNumber: () => undefined,\n    getFunctionName: () => undefined,\n    getMethodName: () => undefined,\n    getTypeName: () => undefined,\n    toString: () => filename,\n  });\n}\n\nexport function injectVirtualStackFrame(error: Error, filename: string) {\n  if (!SUPPORTED) return;\n\n  let frames = virtualFrames.get(error);\n  if (!frames) virtualFrames.set(error, (frames = []));\n  frames.push(CallSite(filename));\n\n  return error;\n}\n\nexport function expectedError(error: Error) {\n  if (!SUPPORTED) return;\n  expectedErrors.add(error);\n  return error;\n}\n\nexport function beginHiddenCallStack<A extends unknown[], R>(\n  fn: (...args: A) => R,\n) {\n  if (!SUPPORTED) return fn;\n\n  return Object.defineProperty(\n    function (...args: A) {\n      setupPrepareStackTrace();\n      return fn(...args);\n    },\n    \"name\",\n    { value: STOP_HIDING },\n  );\n}\n\nexport function endHiddenCallStack<A extends unknown[], R>(\n  fn: (...args: A) => R,\n) {\n  if (!SUPPORTED) return fn;\n\n  return Object.defineProperty(\n    function (...args: A) {\n      return fn(...args);\n    },\n    \"name\",\n    { value: START_HIDING },\n  );\n}\n\nfunction setupPrepareStackTrace() {\n  // @ts-expect-error This function is a singleton\n  setupPrepareStackTrace = () => {};\n\n  const { prepareStackTrace = defaultPrepareStackTrace } = Error;\n\n  // We add some extra frames to Error.stackTraceLimit, so that we can\n  // always show some useful frames even after deleting ours.\n  // STACK_TRACE_LIMIT_DELTA should be around the maximum expected number\n  // of internal frames, and not too big because capturing the stack trace\n  // is slow (this is why Error.stackTraceLimit does not default to Infinity!).\n  // Increase it if needed.\n  // However, we only do it if the user did not explicitly set it to 0.\n  const MIN_STACK_TRACE_LIMIT = 50;\n  Error.stackTraceLimit &&= Math.max(\n    Error.stackTraceLimit,\n    MIN_STACK_TRACE_LIMIT,\n  );\n\n  Error.prepareStackTrace = function stackTraceRewriter(err, trace) {\n    let newTrace = [];\n\n    const isExpected = expectedErrors.has(err);\n    let status: \"showing\" | \"hiding\" | \"unknown\" = isExpected\n      ? \"hiding\"\n      : \"unknown\";\n    for (let i = 0; i < trace.length; i++) {\n      const name = trace[i].getFunctionName();\n      if (name === START_HIDING) {\n        status = \"hiding\";\n      } else if (name === STOP_HIDING) {\n        if (status === \"hiding\") {\n          status = \"showing\";\n          if (virtualFrames.has(err)) {\n            newTrace.unshift(...virtualFrames.get(err)!);\n          }\n        } else if (status === \"unknown\") {\n          // Unexpected internal error, show the full stack trace\n          newTrace = trace;\n          break;\n        }\n      } else if (status !== \"hiding\") {\n        newTrace.push(trace[i]);\n      }\n    }\n\n    return prepareStackTrace(err, newTrace);\n  };\n}\n\nfunction defaultPrepareStackTrace(err: Error, trace: CallSite[]) {\n  if (trace.length === 0) return ErrorToString(err);\n  // eslint-disable-next-line @typescript-eslint/no-base-to-string\n  return `${ErrorToString(err)}\\n    at ${trace.join(\"\\n    at \")}`;\n}\n"],"names":[],"mappings":"AA4CA,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;AAElE,MAAM,SAAS,GACb,CAAC,CAAC,KAAK,CAAC,iBAAiB,IACzB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,iBAAiB,CAAC,EAAE,QAAQ,KAAK,IAAI;AAE9E,MAAM,YAAY,GAAG,4CAA4C;AACjE,MAAM,WAAW,GAAG,2CAA2C;AAI/D,MAAM,cAAc,GAAG,IAAI,OAAO,EAAS;AAC3C,MAAM,aAAa,GAAG,IAAI,OAAO,EAAqB;AAEtD,SAAS,QAAQ,CAAC,QAAgB,EAAY;EAE5C,OAAO,MAAM,CAAC,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,KAAK;IACrB,aAAa,EAAE,MAAM,KAAK;IAC1B,UAAU,EAAE,MAAM,IAAI;IACtB,WAAW,EAAE,MAAM,QAAQ;IAC3B,aAAa,EAAE,MAAM,SAAS;IAC9B,eAAe,EAAE,MAAM,SAAS;IAChC,eAAe,EAAE,MAAM,SAAS;IAChC,aAAa,EAAE,MAAM,SAAS;IAC9B,WAAW,EAAE,MAAM,SAAS;AAC5B,IAAA,QAAQ,EAAE,MAAM;AAClB,GAAC,CAAC;AACJ;AAEO,SAAS,uBAAuB,CAAC,KAAY,EAAE,QAAgB,EAAE;EACtE,IAAI,CAAC,SAAS,EAAE;AAEhB,EAAA,IAAI,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC;AACrC,EAAA,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,KAAK,EAAG,MAAM,GAAG,EAAG,CAAC;AACpD,EAAA,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAE/B,EAAA,OAAO,KAAK;AACd;AAEO,SAAS,aAAa,CAAC,KAAY,EAAE;EAC1C,IAAI,CAAC,SAAS,EAAE;AAChB,EAAA,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC;AACzB,EAAA,OAAO,KAAK;AACd;AAEO,SAAS,oBAAoB,CAClC,EAAqB,EACrB;AACA,EAAA,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE;AAEzB,EAAA,OAAO,MAAM,CAAC,cAAc,CAC1B,UAAU,GAAG,IAAO,EAAE;AACpB,IAAA,sBAAsB,EAAE;AACxB,IAAA,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;EACpB,CAAC,EACD,MAAM,EACN;AAAE,IAAA,KAAK,EAAE;AAAY,GACvB,CAAC;AACH;AAEO,SAAS,kBAAkB,CAChC,EAAqB,EACrB;AACA,EAAA,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE;AAEzB,EAAA,OAAO,MAAM,CAAC,cAAc,CAC1B,UAAU,GAAG,IAAO,EAAE;AACpB,IAAA,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;EACpB,CAAC,EACD,MAAM,EACN;AAAE,IAAA,KAAK,EAAE;AAAa,GACxB,CAAC;AACH;AAEA,SAAS,sBAAsB,GAAG;EAEhC,sBAAsB,GAAG,MAAM,CAAC,CAAC;EAEjC,MAAM;AAAE,IAAA,iBAAiB,GAAG;AAAyB,GAAC,GAAG,KAAK;EAS9D,MAAM,qBAAqB,GAAG,EAAE;AAChC,EAAA,KAAK,CAAC,eAAe,KAAK,IAAI,CAAC,GAAG,CAChC,KAAK,CAAC,eAAe,EACrB,qBACF,CAAC;EAED,KAAK,CAAC,iBAAiB,GAAG,SAAS,kBAAkB,CAAC,GAAG,EAAE,KAAK,EAAE;IAChE,IAAI,QAAQ,GAAG,EAAE;AAEjB,IAAA,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;AAC1C,IAAA,IAAI,MAAwC,GAAG,UAAU,GACrD,QAAQ,GACR,SAAS;AACb,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;MACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE;MACvC,IAAI,IAAI,KAAK,YAAY,EAAE;AACzB,QAAA,MAAM,GAAG,QAAQ;AACnB,MAAA,CAAC,MAAM,IAAI,IAAI,KAAK,WAAW,EAAE;QAC/B,IAAI,MAAM,KAAK,QAAQ,EAAE;AACvB,UAAA,MAAM,GAAG,SAAS;AAClB,UAAA,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAC1B,QAAQ,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;AAC9C,UAAA;AACF,QAAA,CAAC,MAAM,IAAI,MAAM,KAAK,SAAS,EAAE;AAE/B,UAAA,QAAQ,GAAG,KAAK;AAChB,UAAA;AACF,QAAA;AACF,MAAA,CAAC,MAAM,IAAI,MAAM,KAAK,QAAQ,EAAE;AAC9B,QAAA,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzB,MAAA;AACF,IAAA;AAEA,IAAA,OAAO,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC;EACzC,CAAC;AACH;AAEA,SAAS,wBAAwB,CAAC,GAAU,EAAE,KAAiB,EAAE;EAC/D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,aAAa,CAAC,GAAG,CAAC;AAEjD,EAAA,OAAO,CAAA,EAAG,aAAa,CAAC,GAAG,CAAC,CAAA,SAAA,EAAY,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA,CAAE;AACnE;;;;"}