deriveStackFromNestedError.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. /** @import { ErrorWithHideStack } from "./ModuleBuildError" */
  7. /** @import WebpackError from "./WebpackError" */
  8. /**
  9. * Returns the nested error's stack without its leading "Name: message" line.
  10. * @param {ErrorWithHideStack} nestedError the wrapped error
  11. * @returns {string | undefined} frames of the nested stack
  12. */
  13. const getNestedFrames = (nestedError) => {
  14. const stack = nestedError.stack;
  15. if (!stack) return undefined;
  16. const firstLineEnd = stack.indexOf("\n");
  17. return firstLineEnd === -1 ? "" : stack.slice(firstLineEnd + 1);
  18. };
  19. /**
  20. * Derives `details` — or, for a `hideStack` error, `stack` — of a diagnostic
  21. * that wraps `nestedError`. V8 formats a stack trace only when it is read, and
  22. * a diagnostic is normally printed without one (stats needs `errorDetails` or
  23. * `errorStack` for that), so the derived string is materialized on first
  24. * access: a build with thousands of dependency warnings would otherwise spend a
  25. * large share of its time formatting stacks that nothing ever reads.
  26. * @param {WebpackError} error the wrapping diagnostic
  27. * @param {ErrorWithHideStack | undefined} nestedError the wrapped error
  28. * @returns {void}
  29. */
  30. const deriveStackFromNestedError = (error, nestedError) => {
  31. if (!nestedError) return;
  32. if (nestedError.hideStack) {
  33. // the frames are hidden, but they are still prepended to the own stack
  34. const ownStack =
  35. /** @type {PropertyDescriptor} */
  36. (Object.getOwnPropertyDescriptor(error, "stack"));
  37. /** @type {string | undefined} */
  38. let stack;
  39. let computed = false;
  40. Object.defineProperty(error, "stack", {
  41. configurable: true,
  42. enumerable: false,
  43. get() {
  44. if (!computed) {
  45. computed = true;
  46. const own = ownStack.get ? ownStack.get.call(error) : ownStack.value;
  47. const frames = getNestedFrames(nestedError);
  48. stack = frames === undefined ? own : `${frames}\n\n${own}`;
  49. // older V8 replaces the own `stack` accessor when it is first read
  50. Object.defineProperty(error, "stack", {
  51. configurable: true,
  52. enumerable: false,
  53. writable: true,
  54. value: stack
  55. });
  56. }
  57. return stack;
  58. },
  59. set(value) {
  60. computed = true;
  61. stack = value;
  62. }
  63. });
  64. return;
  65. }
  66. /** @type {string | undefined} */
  67. let details;
  68. let computed = false;
  69. Object.defineProperty(error, "details", {
  70. configurable: true,
  71. enumerable: true,
  72. get() {
  73. if (!computed) {
  74. computed = true;
  75. details = getNestedFrames(nestedError);
  76. }
  77. return details;
  78. },
  79. set(value) {
  80. computed = true;
  81. details = value;
  82. }
  83. });
  84. };
  85. module.exports = deriveStackFromNestedError;