bippy 0.6.1-dev.3a24abf → 0.6.1-dev.93556ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +26 -1
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +39 -42
  5. package/dist/core.d.ts +39 -42
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +1 -1
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +1 -1
  11. package/dist/{types.d.cts → errors.d.cts} +187 -68
  12. package/dist/{types.d.ts → errors.d.ts} +187 -68
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +11 -3
  15. package/dist/index.d.ts +11 -3
  16. package/dist/index.iife.js +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/install-hook-only.cjs +1 -1
  19. package/dist/install-hook-only.d.cts +8 -0
  20. package/dist/install-hook-only.d.ts +8 -0
  21. package/dist/install-hook-only.iife.js +1 -1
  22. package/dist/install-hook-only.js +1 -1
  23. package/dist/rdt-hook.cjs +1 -1
  24. package/dist/rdt-hook.js +1 -1
  25. package/dist/source.cjs +13 -14
  26. package/dist/source.d.cts +86 -69
  27. package/dist/source.d.ts +86 -69
  28. package/dist/source.js +13 -14
  29. package/package.json +10 -6
  30. package/src/core.ts +246 -293
  31. package/src/errors.ts +34 -0
  32. package/src/install-hook-only.ts +2 -2
  33. package/src/rdt-hook.ts +152 -110
  34. package/src/react-internals/generated/react-work-tags.ts +318 -0
  35. package/src/react-internals/index.ts +67 -0
  36. package/src/react-internals/semver.ts +80 -0
  37. package/src/{types.ts → react-internals/types.ts} +13 -86
  38. package/src/source/error-stack.ts +11 -0
  39. package/src/source/get-display-name-from-source.ts +44 -40
  40. package/src/source/get-source.ts +42 -26
  41. package/src/source/index.ts +11 -1
  42. package/src/source/inspect-hooks.ts +220 -207
  43. package/src/source/owner-stack.ts +86 -128
  44. package/src/source/parse-debug-stack.ts +4 -4
  45. package/src/source/parse-hook-names.ts +14 -53
  46. package/src/source/parse-stack.ts +14 -31
  47. package/src/source/renderer-dispatchers.ts +30 -0
  48. package/src/source/symbolication.ts +703 -135
  49. package/src/generated/react-work-tags.ts +0 -169
  50. package/src/react-internals.ts +0 -67
  51. package/src/unsubscribe.ts +0 -17
@@ -1,29 +1,25 @@
1
1
  import { getDisplayName, getType, traverseFiber } from "../core.js";
2
- import { _renderers, getRDTHook } from "../rdt-hook.js";
3
- import {
4
- ActivityComponentTag,
5
- ClassComponentTag,
6
- ForwardRefTag,
7
- FunctionComponentTag,
8
- HostComponentTag,
9
- HostHoistableTag,
10
- HostSingletonTag,
11
- LazyComponentTag,
12
- SimpleMemoComponentTag,
13
- SuspenseComponentTag,
14
- SuspenseListComponentTag,
15
- ViewTransitionComponentTag,
16
- } from "../react-internals.js";
17
- import type { Fiber, RendererDispatcherRef, ServerComponentInfo } from "../types.js";
2
+ import { getReactWorkTagsForFiber } from "../react-internals/index.js";
3
+ import type {
4
+ Fiber,
5
+ RendererDispatcherRef,
6
+ ServerComponentInfo,
7
+ } from "../react-internals/index.js";
18
8
  import {
9
+ REACT_STACK_BOTTOM_FRAME_PATTERNS,
19
10
  SERVER_FRAME_MARKER,
20
11
  SERVER_ENV_PATTERN,
21
12
  SERVER_COMPONENT_URL_PREFIXES,
22
13
  } from "./constants.js";
23
-
14
+ import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
24
15
  import { parseDebugStack } from "./parse-debug-stack.js";
25
- import { parseStack, StackFrame } from "./parse-stack.js";
26
- import { symbolicateStack } from "./symbolication.js";
16
+ import { parseStack, type StackFrame } from "./parse-stack.js";
17
+ import {
18
+ getRendererDispatcherRefs,
19
+ readDispatcher,
20
+ writeDispatcher,
21
+ } from "./renderer-dispatchers.js";
22
+ import { symbolicateStack, type SourceFetch } from "./symbolication.js";
27
23
 
28
24
  interface RendererDispatcherSnapshot {
29
25
  currentDispatcherRef: RendererDispatcherRef;
@@ -41,9 +37,6 @@ export const hasDebugStack = (
41
37
  const isFiberOwner = (owner: Fiber | ServerComponentInfo): owner is Fiber =>
42
38
  "tag" in owner && typeof owner.tag === "number";
43
39
 
44
- const getDebugOwner = (fiber: Fiber): Fiber | ServerComponentInfo | undefined =>
45
- fiber._debugOwner ?? undefined;
46
-
47
40
  /**
48
41
  * Locates a frame inside the fiber's own function body without invoking it:
49
42
  * any child fiber owned by this fiber was created by JSX inside its body, so
@@ -89,42 +82,18 @@ export const getDefinitionFrameFromOwnedChild = (fiber: Fiber): StackFrame | nul
89
82
  return null;
90
83
  };
91
84
 
92
- const getRendererDispatcherRefs = (): RendererDispatcherRef[] => {
93
- const rdtHook = getRDTHook();
94
- const renderers = new Set([..._renderers, ...rdtHook.renderers.values()]);
95
- const currentDispatcherRefs: RendererDispatcherRef[] = [];
96
- const seenCurrentDispatcherRefs = new Set<object>();
97
- for (const renderer of renderers) {
98
- const currentDispatcherRef = renderer.currentDispatcherRef;
99
- if (!currentDispatcherRef || seenCurrentDispatcherRefs.has(currentDispatcherRef)) continue;
100
- seenCurrentDispatcherRefs.add(currentDispatcherRef);
101
- currentDispatcherRefs.push(currentDispatcherRef);
102
- }
103
- return currentDispatcherRefs;
104
- };
105
-
106
85
  const clearRendererDispatchers = (): RendererDispatcherSnapshot[] => {
107
86
  const dispatcherSnapshots: RendererDispatcherSnapshot[] = [];
108
87
  for (const currentDispatcherRef of getRendererDispatcherRefs()) {
109
- const value =
110
- "H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
111
- dispatcherSnapshots.push({ currentDispatcherRef, value });
112
- if ("H" in currentDispatcherRef) {
113
- currentDispatcherRef.H = null;
114
- } else {
115
- currentDispatcherRef.current = null;
116
- }
88
+ dispatcherSnapshots.push({ currentDispatcherRef, value: readDispatcher(currentDispatcherRef) });
89
+ writeDispatcher(currentDispatcherRef, null);
117
90
  }
118
91
  return dispatcherSnapshots;
119
92
  };
120
93
 
121
94
  const restoreRendererDispatchers = (dispatcherSnapshots: RendererDispatcherSnapshot[]): void => {
122
95
  for (const { currentDispatcherRef, value } of dispatcherSnapshots) {
123
- if ("H" in currentDispatcherRef) {
124
- currentDispatcherRef.H = value;
125
- } else {
126
- currentDispatcherRef.current = value;
127
- }
96
+ writeDispatcher(currentDispatcherRef, value);
128
97
  }
129
98
  };
130
99
 
@@ -160,9 +129,8 @@ const describeNativeComponentFrame = (
160
129
  return cachedFrame;
161
130
  }
162
131
 
163
- const previousPrepareStackTrace = Error.prepareStackTrace;
164
- // HACK: V8 API allows undefined but bun-types declares it as non-optional
165
- (Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
132
+ const previousPrepareStackTrace = getPrepareStackTrace();
133
+ setPrepareStackTrace(undefined);
166
134
  reEntry = true;
167
135
 
168
136
  const dispatcherSnapshots = clearRendererDispatchers();
@@ -173,7 +141,7 @@ const describeNativeComponentFrame = (
173
141
  try {
174
142
  // HACK: Run the control and sample under the same property name so their stacks share a frame.
175
143
  const RunInRootFrame = {
176
- DetermineComponentFrameRoot() {
144
+ DetermineComponentFrameRoot(this: void) {
177
145
  let control: unknown;
178
146
  try {
179
147
  if (construct) {
@@ -304,6 +272,10 @@ const describeNativeComponentFrame = (
304
272
  if (controlIndex < 0 || sampleLines[sampleIndex] !== controlLines[controlIndex]) {
305
273
  // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
306
274
  let stackFrame = `\n${sampleLines[sampleIndex].replace(" at new ", " at ")}`;
275
+ const [parsedStackFrame] = parseStack(stackFrame);
276
+ if (!parsedStackFrame?.fileName) {
277
+ continue;
278
+ }
307
279
 
308
280
  const displayName = getDisplayName(component);
309
281
  // If our component frame is labeled "<anonymous>"
@@ -325,14 +297,14 @@ const describeNativeComponentFrame = (
325
297
  } finally {
326
298
  reEntry = false;
327
299
 
328
- Error.prepareStackTrace = previousPrepareStackTrace;
300
+ setPrepareStackTrace(previousPrepareStackTrace);
329
301
 
330
302
  restoreRendererDispatchers(dispatcherSnapshots);
331
303
  console.error = previousConsoleError;
332
304
  console.warn = previousConsoleWarn;
333
305
  }
334
306
 
335
- const componentName = component ? getDisplayName(component) : "";
307
+ const componentName = getDisplayName(component);
336
308
  const syntheticFrame = componentName ? describeBuiltInComponentFrame(componentName) : "";
337
309
  componentFrameCache.set(component, syntheticFrame);
338
310
  return syntheticFrame;
@@ -341,32 +313,33 @@ const describeNativeComponentFrame = (
341
313
  // https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
342
314
  export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
343
315
  let stackFrame = "";
316
+ const workTags = getReactWorkTagsForFiber(fiber);
344
317
  switch (fiber.tag) {
345
- case ActivityComponentTag:
318
+ case workTags.ActivityComponent:
346
319
  stackFrame = describeBuiltInComponentFrame("Activity");
347
320
  break;
348
- case ClassComponentTag:
321
+ case workTags.ClassComponent:
349
322
  stackFrame = describeNativeComponentFrame(fiber.type, true);
350
323
  break;
351
- case ForwardRefTag:
324
+ case workTags.ForwardRef:
352
325
  stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
353
326
  break;
354
- case FunctionComponentTag:
355
- case SimpleMemoComponentTag:
356
- stackFrame = describeNativeComponentFrame(fiber.type, false);
327
+ case workTags.FunctionComponent:
328
+ case workTags.SimpleMemoComponent:
329
+ stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
357
330
  break;
358
- case HostComponentTag:
359
- case HostHoistableTag:
360
- case HostSingletonTag:
331
+ case workTags.HostComponent:
332
+ case workTags.HostHoistable:
333
+ case workTags.HostSingleton:
361
334
  if (typeof fiber.type === "string") {
362
335
  stackFrame = describeBuiltInComponentFrame(fiber.type);
363
336
  }
364
337
  break;
365
- case LazyComponentTag:
338
+ case workTags.LazyComponent:
366
339
  // TODO: When we support Thenables as component types we should rename this.
367
340
  stackFrame = describeBuiltInComponentFrame("Lazy");
368
341
  break;
369
- case SuspenseComponentTag:
342
+ case workTags.SuspenseComponent:
370
343
  if (fiber.child !== childFiber && childFiber !== null) {
371
344
  // If we came from the second Fiber then we're in the Suspense Fallback.
372
345
  stackFrame = describeBuiltInComponentFrame("Suspense Fallback");
@@ -374,10 +347,10 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
374
347
  stackFrame = describeBuiltInComponentFrame("Suspense");
375
348
  }
376
349
  break;
377
- case SuspenseListComponentTag:
350
+ case workTags.SuspenseListComponent:
378
351
  stackFrame = describeBuiltInComponentFrame("SuspenseList");
379
352
  break;
380
- case ViewTransitionComponentTag:
353
+ case workTags.ViewTransitionComponent:
381
354
  // Note: enableViewTransition feature flag is not available in this codebase,
382
355
  // so we'll always include ViewTransition
383
356
  stackFrame = describeBuiltInComponentFrame("ViewTransition");
@@ -406,8 +379,8 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
406
379
  // Since we don't have __DEV__ in this codebase, we'll check for _debugInfo
407
380
  const debugInfo = currentFiber._debugInfo;
408
381
  if (debugInfo && Array.isArray(debugInfo)) {
409
- for (let i = debugInfo.length - 1; i >= 0; i--) {
410
- const debugEntry = debugInfo[i];
382
+ for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
383
+ const debugEntry = debugInfo[debugInfoIndex];
411
384
  if (typeof debugEntry.name === "string") {
412
385
  componentStack += describeDebugInfoFrame(debugEntry.name, debugEntry.env);
413
386
  }
@@ -420,7 +393,7 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
420
393
  return componentStack;
421
394
  } catch (error) {
422
395
  if (error instanceof Error) {
423
- return `\nError generating stack: ${error.message}\n${error.stack}`;
396
+ return `\nBippy couldn’t generate the stack: ${error.message}\n${error.stack}`;
424
397
  }
425
398
  return "";
426
399
  }
@@ -445,14 +418,10 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
445
418
  * @see https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js#L12
446
419
  */
447
420
  export const formatOwnerStack = (stack: string): string => {
448
- const prevPrepareStackTrace = Error.prepareStackTrace;
449
- // HACK: V8 API allows undefined but bun-types declares it as non-optional
450
- (Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
451
421
  let formattedStack = stack;
452
422
  if (!formattedStack) {
453
423
  return "";
454
424
  }
455
- Error.prepareStackTrace = prevPrepareStackTrace;
456
425
 
457
426
  if (formattedStack.startsWith("Error: react-stack-top-frame\n")) {
458
427
  // V8's default formatting prefixes with the error message which we
@@ -465,8 +434,7 @@ export const formatOwnerStack = (stack: string): string => {
465
434
  formattedStack = formattedStack.slice(firstNewlineIndex + 1);
466
435
  }
467
436
  let bottomFrameIndex = Math.max(
468
- formattedStack.indexOf("react_stack_bottom_frame"),
469
- formattedStack.indexOf("react-stack-bottom-frame"),
437
+ ...REACT_STACK_BOTTOM_FRAME_PATTERNS.map((pattern) => formattedStack.indexOf(pattern)),
470
438
  );
471
439
  if (bottomFrameIndex !== -1) {
472
440
  bottomFrameIndex = formattedStack.lastIndexOf("\n", bottomFrameIndex);
@@ -483,11 +451,6 @@ export const formatOwnerStack = (stack: string): string => {
483
451
  return formattedStack;
484
452
  };
485
453
 
486
- interface DebugStackEntry {
487
- componentName: string;
488
- stackFrames: StackFrame[];
489
- }
490
-
491
454
  const isReactServerComponentFrame = (stackFrame: StackFrame): boolean =>
492
455
  Boolean(
493
456
  stackFrame.functionName && stackFrame.fileName && isServerComponentUrl(stackFrame.fileName),
@@ -499,24 +462,22 @@ const areStackFramesEqual = (firstFrame: StackFrame, secondFrame: StackFrame): b
499
462
  firstFrame.columnNumber === secondFrame.columnNumber;
500
463
 
501
464
  const buildFunctionNameToRscFramesMap = (
502
- debugStackEntries: DebugStackEntry[],
465
+ debugStackFrames: StackFrame[],
503
466
  ): Map<string, StackFrame[]> => {
504
467
  const functionNameToRscFrames = new Map<string, StackFrame[]>();
505
468
 
506
- for (const debugStackEntry of debugStackEntries) {
507
- for (const stackFrame of debugStackEntry.stackFrames) {
508
- if (!isReactServerComponentFrame(stackFrame)) continue;
469
+ for (const stackFrame of debugStackFrames) {
470
+ if (!isReactServerComponentFrame(stackFrame)) continue;
509
471
 
510
- const functionName = stackFrame.functionName!;
511
- const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
512
- const isDuplicateFrame = framesForFunction.some((existingFrame) =>
513
- areStackFramesEqual(existingFrame, stackFrame),
514
- );
472
+ const functionName = stackFrame.functionName!;
473
+ const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
474
+ const isDuplicateFrame = framesForFunction.some((existingFrame) =>
475
+ areStackFramesEqual(existingFrame, stackFrame),
476
+ );
515
477
 
516
- if (!isDuplicateFrame) {
517
- framesForFunction.push(stackFrame);
518
- functionNameToRscFrames.set(functionName, framesForFunction);
519
- }
478
+ if (!isDuplicateFrame) {
479
+ framesForFunction.push(stackFrame);
480
+ functionNameToRscFrames.set(functionName, framesForFunction);
520
481
  }
521
482
  }
522
483
 
@@ -577,7 +538,7 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
577
538
  while (owner) {
578
539
  if (isFiberOwner(owner)) {
579
540
  const ownerFiber: Fiber = owner;
580
- owner = getDebugOwner(ownerFiber);
541
+ owner = ownerFiber._debugOwner;
581
542
  if (owner && hasDebugStack(ownerFiber)) {
582
543
  const { frames, isTrusted } = parseDebugStack(ownerFiber._debugStack);
583
544
  if (isTrusted) {
@@ -601,53 +562,39 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
601
562
  return ownerStackFrames;
602
563
  };
603
564
 
604
- const getDebugStackEntries = (rootFiber: Fiber): DebugStackEntry[] => {
605
- const debugStackEntries: DebugStackEntry[] = [];
565
+ const getDebugStackFrames = (rootFiber: Fiber): StackFrame[] => {
566
+ const debugStackFrames: StackFrame[] = [];
606
567
 
607
568
  traverseFiber(
608
569
  rootFiber,
609
570
  (currentFiber) => {
610
571
  if (!hasDebugStack(currentFiber)) return;
611
572
 
612
- const componentName =
613
- typeof currentFiber.type !== "string"
614
- ? getDisplayName(currentFiber.type) || "<anonymous>"
615
- : currentFiber.type;
616
-
617
- debugStackEntries.push({
618
- componentName,
619
- stackFrames: parseStack(formatOwnerStack(currentFiber._debugStack?.stack)),
620
- });
573
+ const { frames, isTrusted } = parseDebugStack(currentFiber._debugStack);
574
+ if (isTrusted) {
575
+ debugStackFrames.push(...frames);
576
+ }
621
577
  },
622
578
  true,
623
579
  );
624
580
 
625
- return debugStackEntries;
581
+ return debugStackFrames;
626
582
  };
627
583
 
628
584
  /**
629
- * Returns a stack of ALL ancestor components in the render tree (the fiber's
630
- * `return` chain), including wrappers that render `{children}` without having
631
- * created this fiber's JSX. Locations come from re-invoking each component
632
- * with a throwing dispatcher; server frames are enriched from debug stacks by
633
- * name matching. Works on every React version.
585
+ * The unsymbolicated frames behind {@link getParentStack}: bundle-space
586
+ * locations from re-invoking each component with a throwing dispatcher,
587
+ * with server frames enriched from debug stacks by name matching.
634
588
  */
635
- export const getParentStack = async (
636
- fiber: Fiber,
637
- shouldCache = true,
638
- fetchFunction?: (url: string) => Promise<Response>,
639
- ): Promise<StackFrame[]> => {
640
- const debugStackEntries = getDebugStackEntries(fiber);
589
+ export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
590
+ const debugStackFrames = getDebugStackFrames(fiber);
641
591
  const fallbackStackFrames = parseStack(getFallbackParentStack(fiber));
642
- const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackEntries);
592
+ const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackFrames);
643
593
  const functionNameToUsageIndex = new Map<string, number>();
644
594
 
645
595
  const enrichedStackFrames = fallbackStackFrames.map((stackFrame): StackFrame => {
646
596
  const isServerFrame =
647
- (stackFrame.source?.includes(SERVER_FRAME_MARKER) ?? false) ||
648
- (stackFrame.source !== null &&
649
- stackFrame.source !== undefined &&
650
- SERVER_ENV_PATTERN.test(stackFrame.source));
597
+ stackFrame.source !== undefined && SERVER_ENV_PATTERN.test(stackFrame.source);
651
598
 
652
599
  if (isServerFrame) {
653
600
  return getEnrichedServerStackFrame(
@@ -660,15 +607,26 @@ export const getParentStack = async (
660
607
  return stackFrame;
661
608
  });
662
609
 
663
- const deduplicatedStackFrames = enrichedStackFrames.filter((stackFrame, index, frames) => {
610
+ return enrichedStackFrames.filter((stackFrame, index, frames) => {
664
611
  if (index === 0) return true;
665
612
  const previousFrame = frames[index - 1];
666
613
  return stackFrame.functionName !== previousFrame.functionName;
667
614
  });
668
-
669
- return symbolicateStack(deduplicatedStackFrames, shouldCache, fetchFunction);
670
615
  };
671
616
 
617
+ /**
618
+ * Returns a stack of ALL ancestor components in the render tree (the fiber's
619
+ * `return` chain), including wrappers that render `{children}` without having
620
+ * created this fiber's JSX. Locations come from re-invoking each component
621
+ * with a throwing dispatcher; server frames are enriched from debug stacks by
622
+ * name matching. Works on every React version.
623
+ */
624
+ export const getParentStack = async (
625
+ fiber: Fiber,
626
+ shouldCache = true,
627
+ fetchFunction?: SourceFetch,
628
+ ): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
629
+
672
630
  // an owner frame is only actionable if it can point an editor somewhere:
673
631
  // it needs a file location and must not be ignore-listed bundler/framework code
674
632
  const isLocatableFrame = (stackFrame: StackFrame): boolean =>
@@ -687,7 +645,7 @@ const isLocatableFrame = (stackFrame: StackFrame): boolean =>
687
645
  export const getOwnerStack = async (
688
646
  fiber: Fiber,
689
647
  shouldCache = true,
690
- fetchFunction?: (url: string) => Promise<Response>,
648
+ fetchFunction?: SourceFetch,
691
649
  ): Promise<StackFrame[]> => {
692
650
  const debugStackFrames = getOwnerStackFromDebugStacks(fiber);
693
651
  if (debugStackFrames.length > 0) {
@@ -1,4 +1,5 @@
1
1
  import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
2
+ import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
2
3
  import { parseStack, StackFrame } from "./parse-stack.js";
3
4
 
4
5
  interface V8CallSite {
@@ -117,14 +118,13 @@ export const parseDebugStack = (debugStack: Error): ParsedDebugStack => {
117
118
  }
118
119
  return stackString;
119
120
  };
120
- const previousPrepareStackTrace = Error.prepareStackTrace;
121
- // node's CallSite typings disagree with browser-safe optional methods
122
- Error.prepareStackTrace = collectFramesAndFormatStack as typeof Error.prepareStackTrace;
121
+ const previousPrepareStackTrace = getPrepareStackTrace();
122
+ setPrepareStackTrace(collectFramesAndFormatStack);
123
123
  let stackString: string;
124
124
  try {
125
125
  stackString = String(debugStack.stack);
126
126
  } finally {
127
- Error.prepareStackTrace = previousPrepareStackTrace;
127
+ setPrepareStackTrace(previousPrepareStackTrace);
128
128
  }
129
129
 
130
130
  const result = structuredResult ?? parseMaterializedStack(stackString);
@@ -1,5 +1,10 @@
1
1
  import type { HooksNode, HooksTree, HookSource } from "./inspect-hooks.js";
2
- import { getSourceMap, getSourceFromSourceMap, type SourceMap } from "./symbolication.js";
2
+ import {
3
+ getSourceContentFromSourceMap,
4
+ getSourceFromSourceMap,
5
+ getSourceMap,
6
+ type SourceFetch,
7
+ } from "./symbolication.js";
3
8
 
4
9
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
5
10
  export interface HookNames extends Map<string, string> {}
@@ -33,40 +38,6 @@ const flattenHooksTree = (hooksTree: HooksTree): HooksNode[] => {
33
38
  return hooksList;
34
39
  };
35
40
 
36
- const findSourceContentByFileName = (
37
- sources: string[],
38
- sourcesContent: string[] | undefined,
39
- fileName: string,
40
- ): string | null => {
41
- if (!sourcesContent) return null;
42
- const sourceIndex = sources.indexOf(fileName);
43
- return sourceIndex !== -1 ? (sourcesContent[sourceIndex] ?? null) : null;
44
- };
45
-
46
- const getSourceContentFromSourceMap = (
47
- sourceMap: SourceMap,
48
- originalFileName: string,
49
- ): string | null => {
50
- const directResult = findSourceContentByFileName(
51
- sourceMap.sources,
52
- sourceMap.sourcesContent,
53
- originalFileName,
54
- );
55
- if (directResult) return directResult;
56
-
57
- if (sourceMap.sections) {
58
- for (const section of sourceMap.sections) {
59
- const sectionResult = findSourceContentByFileName(
60
- section.map.sources,
61
- section.map.sourcesContent,
62
- originalFileName,
63
- );
64
- if (sectionResult) return sectionResult;
65
- }
66
- }
67
- return null;
68
- };
69
-
70
41
  const extractVariableNameFromBinding = (binding: string): string | null => {
71
42
  const trimmed = binding.trim();
72
43
  if (trimmed.startsWith("[")) {
@@ -109,9 +80,8 @@ interface ResolvedSource {
109
80
  }
110
81
 
111
82
  interface SourceResolutionContext {
112
- sourceMapsByFile: Map<string, SourceMap | null>;
113
83
  sourceContentCache: Map<string, string | null>;
114
- fetchFn?: (url: string) => Promise<Response>;
84
+ fetchFn?: SourceFetch;
115
85
  }
116
86
 
117
87
  const resolveOriginalSource = async (
@@ -120,25 +90,17 @@ const resolveOriginalSource = async (
120
90
  runtimeColumn: number,
121
91
  context: SourceResolutionContext,
122
92
  ): Promise<ResolvedSource | null> => {
123
- const { sourceMapsByFile, sourceContentCache, fetchFn } = context;
93
+ const { sourceContentCache, fetchFn } = context;
124
94
 
125
- if (!sourceMapsByFile.has(runtimeFileName)) {
126
- sourceMapsByFile.set(runtimeFileName, await getSourceMap(runtimeFileName, true, fetchFn));
127
- }
128
-
129
- const sourceMap = sourceMapsByFile.get(runtimeFileName) ?? null;
95
+ const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn);
130
96
 
131
97
  if (sourceMap) {
132
98
  const originalLocation = getSourceFromSourceMap(sourceMap, runtimeLine, runtimeColumn);
133
99
  if (originalLocation?.fileName && originalLocation.lineNumber !== undefined) {
134
- const cacheKey = `sourcemap:${runtimeFileName}:${originalLocation.fileName}`;
135
- if (!sourceContentCache.has(cacheKey)) {
136
- sourceContentCache.set(
137
- cacheKey,
138
- getSourceContentFromSourceMap(sourceMap, originalLocation.fileName),
139
- );
140
- }
141
- const originalSourceCode = sourceContentCache.get(cacheKey) ?? null;
100
+ const originalSourceCode = getSourceContentFromSourceMap(
101
+ sourceMap,
102
+ originalLocation.fileName,
103
+ );
142
104
  if (originalSourceCode) {
143
105
  return {
144
106
  sourceCode: originalSourceCode,
@@ -173,7 +135,7 @@ const resolveOriginalSource = async (
173
135
 
174
136
  export const parseHookNames = async (
175
137
  hooksTree: HooksTree,
176
- fetchFn?: (url: string) => Promise<Response>,
138
+ fetchFn?: SourceFetch,
177
139
  ): Promise<HookNames> => {
178
140
  const hookNames: HookNames = new Map();
179
141
  const hooksList = flattenHooksTree(hooksTree);
@@ -181,7 +143,6 @@ export const parseHookNames = async (
181
143
  if (hooksList.length === 0) return hookNames;
182
144
 
183
145
  const resolutionContext: SourceResolutionContext = {
184
- sourceMapsByFile: new Map(),
185
146
  sourceContentCache: new Map(),
186
147
  fetchFn,
187
148
  };
@@ -1,5 +1,4 @@
1
1
  export interface StackFrame {
2
- args?: unknown[];
3
2
  columnNumber?: number;
4
3
  lineNumber?: number;
5
4
  // start of the enclosing function (the definition, not the call site);
@@ -16,8 +15,6 @@ export interface StackFrame {
16
15
  }
17
16
 
18
17
  export interface ParseOptions {
19
- slice?: number | [number, number];
20
- allowEmpty?: boolean;
21
18
  includeInElement?: boolean;
22
19
  }
23
20
 
@@ -31,22 +28,22 @@ export const parseStack = (stackString: string, options?: ParseOptions): StackFr
31
28
  const frames: StackFrame[] = [];
32
29
  for (const rawLine of lines) {
33
30
  if (/^\s*at\s+/.test(rawLine)) {
34
- const parsed = parseV8OrIeString(rawLine, undefined)[0];
31
+ const parsed = parseV8OrIeString(rawLine)[0];
35
32
  if (parsed) frames.push(parsed);
36
33
  } else if (/^\s*in\s+/.test(rawLine)) {
37
34
  const elementName = rawLine.replace(/^\s*in\s+/, "").replace(/\s*\(at .*\)$/, "");
38
35
  frames.push({ functionName: elementName, source: rawLine });
39
36
  } else if (rawLine.match(FIREFOX_SAFARI_STACK_REGEXP)) {
40
- const parsed = parseFFOrSafariString(rawLine, undefined)[0];
37
+ const parsed = parseFFOrSafariString(rawLine)[0];
41
38
  if (parsed) frames.push(parsed);
42
39
  }
43
40
  }
44
- return applySlice(frames, options);
41
+ return frames;
45
42
  }
46
43
  if (stackString.match(CHROME_IE_STACK_REGEXP)) {
47
- return parseV8OrIeString(stackString, options);
44
+ return parseV8OrIeString(stackString);
48
45
  }
49
- return parseFFOrSafariString(stackString, options);
46
+ return parseFFOrSafariString(stackString);
50
47
  };
51
48
 
52
49
  export const extractLocation = (
@@ -66,21 +63,10 @@ export const extractLocation = (
66
63
  return [parts[1], parts[2] || undefined, parts[3] || undefined] as const;
67
64
  };
68
65
 
69
- const applySlice = <T>(lines: T[], options?: ParseOptions): T[] => {
70
- if (options && options.slice !== null && options.slice !== undefined) {
71
- if (Array.isArray(options.slice)) return lines.slice(options.slice[0], options.slice[1]);
72
- return lines.slice(0, options.slice);
73
- }
74
- return lines;
75
- };
76
-
77
- export const parseV8OrIeString = (stack: string, options?: ParseOptions): StackFrame[] => {
78
- const filteredLines = applySlice(
79
- stack.split("\n").filter((line) => {
80
- return !!line.match(CHROME_IE_STACK_REGEXP);
81
- }),
82
- options,
83
- );
66
+ export const parseV8OrIeString = (stack: string): StackFrame[] => {
67
+ const filteredLines = stack.split("\n").filter((line) => {
68
+ return !!line.match(CHROME_IE_STACK_REGEXP);
69
+ });
84
70
 
85
71
  return filteredLines.map((line): StackFrame => {
86
72
  let currentLine = line;
@@ -100,7 +86,7 @@ export const parseV8OrIeString = (stack: string, options?: ParseOptions): StackF
100
86
 
101
87
  const locationParts = extractLocation(locationMatch ? locationMatch[1] : sanitizedLine);
102
88
  const functionName = (locationMatch && sanitizedLine) || undefined;
103
- const fileName = ["eval", "<anonymous>"].includes(locationParts[0])
89
+ const fileName = ["eval", "<anonymous>", "(native)"].includes(locationParts[0])
104
90
  ? undefined
105
91
  : locationParts[0];
106
92
 
@@ -114,13 +100,10 @@ export const parseV8OrIeString = (stack: string, options?: ParseOptions): StackF
114
100
  });
115
101
  };
116
102
 
117
- export const parseFFOrSafariString = (stack: string, options?: ParseOptions): StackFrame[] => {
118
- const filteredLines = applySlice(
119
- stack.split("\n").filter((line) => {
120
- return !line.match(SAFARI_NATIVE_CODE_REGEXP);
121
- }),
122
- options,
123
- );
103
+ export const parseFFOrSafariString = (stack: string): StackFrame[] => {
104
+ const filteredLines = stack.split("\n").filter((line) => {
105
+ return !line.match(SAFARI_NATIVE_CODE_REGEXP);
106
+ });
124
107
 
125
108
  return filteredLines.map((line): StackFrame => {
126
109
  let currentLine = line;
@@ -0,0 +1,30 @@
1
+ import type { RendererDispatcherRef } from "../react-internals/index.js";
2
+ import { _renderers, getRDTHook } from "../rdt-hook.js";
3
+
4
+ export const getRendererDispatcherRefs = (): RendererDispatcherRef[] => {
5
+ const rdtHook = getRDTHook();
6
+ const renderers = new Set([..._renderers, ...rdtHook.renderers.values()]);
7
+ const currentDispatcherRefs: RendererDispatcherRef[] = [];
8
+ const seenCurrentDispatcherRefs = new Set<object>();
9
+ for (const renderer of renderers) {
10
+ const currentDispatcherRef = renderer.currentDispatcherRef;
11
+ if (!currentDispatcherRef || seenCurrentDispatcherRefs.has(currentDispatcherRef)) continue;
12
+ seenCurrentDispatcherRefs.add(currentDispatcherRef);
13
+ currentDispatcherRefs.push(currentDispatcherRef);
14
+ }
15
+ return currentDispatcherRefs;
16
+ };
17
+
18
+ export const readDispatcher = (currentDispatcherRef: RendererDispatcherRef): unknown =>
19
+ "H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
20
+
21
+ export const writeDispatcher = (
22
+ currentDispatcherRef: RendererDispatcherRef,
23
+ dispatcher: unknown,
24
+ ): void => {
25
+ if ("H" in currentDispatcherRef) {
26
+ currentDispatcherRef.H = dispatcher;
27
+ } else {
28
+ currentDispatcherRef.current = dispatcher;
29
+ }
30
+ };