bippy 0.6.1-dev.3a24abf → 0.6.1-dev.a3b3942

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 (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -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 -58
  12. package/dist/{types.d.ts → errors.d.ts} +187 -58
  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 +27 -11
  27. package/dist/source.d.ts +27 -11
  28. package/dist/source.js +13 -14
  29. package/package.json +10 -6
  30. package/src/core.ts +244 -274
  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 -75
  38. package/src/source/error-stack.ts +11 -0
  39. package/src/source/get-display-name-from-source.ts +32 -25
  40. package/src/source/get-source.ts +3 -4
  41. package/src/source/index.ts +9 -1
  42. package/src/source/inspect-hooks.ts +215 -182
  43. package/src/source/owner-stack.ts +70 -96
  44. package/src/source/parse-debug-stack.ts +4 -4
  45. package/src/source/parse-hook-names.ts +1 -1
  46. package/src/source/parse-stack.ts +0 -2
  47. package/src/source/symbolication.ts +483 -104
  48. package/src/generated/react-work-tags.ts +0 -169
  49. package/src/react-internals.ts +0 -67
  50. package/src/unsubscribe.ts +0 -17
@@ -1,26 +1,18 @@
1
1
  import { getDisplayName, getType, traverseFiber } from "../core.js";
2
2
  import { _renderers, getRDTHook } from "../rdt-hook.js";
3
+ import { getReactWorkTagsForFiber } from "../react-internals/index.js";
4
+ import type {
5
+ Fiber,
6
+ RendererDispatcherRef,
7
+ ServerComponentInfo,
8
+ } from "../react-internals/index.js";
3
9
  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";
18
- import {
10
+ REACT_STACK_BOTTOM_FRAME_PATTERNS,
19
11
  SERVER_FRAME_MARKER,
20
12
  SERVER_ENV_PATTERN,
21
13
  SERVER_COMPONENT_URL_PREFIXES,
22
14
  } from "./constants.js";
23
-
15
+ import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
24
16
  import { parseDebugStack } from "./parse-debug-stack.js";
25
17
  import { parseStack, StackFrame } from "./parse-stack.js";
26
18
  import { symbolicateStack } from "./symbolication.js";
@@ -41,9 +33,6 @@ export const hasDebugStack = (
41
33
  const isFiberOwner = (owner: Fiber | ServerComponentInfo): owner is Fiber =>
42
34
  "tag" in owner && typeof owner.tag === "number";
43
35
 
44
- const getDebugOwner = (fiber: Fiber): Fiber | ServerComponentInfo | undefined =>
45
- fiber._debugOwner ?? undefined;
46
-
47
36
  /**
48
37
  * Locates a frame inside the fiber's own function body without invoking it:
49
38
  * any child fiber owned by this fiber was created by JSX inside its body, so
@@ -160,9 +149,8 @@ const describeNativeComponentFrame = (
160
149
  return cachedFrame;
161
150
  }
162
151
 
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;
152
+ const previousPrepareStackTrace = getPrepareStackTrace();
153
+ setPrepareStackTrace(undefined);
166
154
  reEntry = true;
167
155
 
168
156
  const dispatcherSnapshots = clearRendererDispatchers();
@@ -173,7 +161,7 @@ const describeNativeComponentFrame = (
173
161
  try {
174
162
  // HACK: Run the control and sample under the same property name so their stacks share a frame.
175
163
  const RunInRootFrame = {
176
- DetermineComponentFrameRoot() {
164
+ DetermineComponentFrameRoot(this: void) {
177
165
  let control: unknown;
178
166
  try {
179
167
  if (construct) {
@@ -325,14 +313,14 @@ const describeNativeComponentFrame = (
325
313
  } finally {
326
314
  reEntry = false;
327
315
 
328
- Error.prepareStackTrace = previousPrepareStackTrace;
316
+ setPrepareStackTrace(previousPrepareStackTrace);
329
317
 
330
318
  restoreRendererDispatchers(dispatcherSnapshots);
331
319
  console.error = previousConsoleError;
332
320
  console.warn = previousConsoleWarn;
333
321
  }
334
322
 
335
- const componentName = component ? getDisplayName(component) : "";
323
+ const componentName = getDisplayName(component);
336
324
  const syntheticFrame = componentName ? describeBuiltInComponentFrame(componentName) : "";
337
325
  componentFrameCache.set(component, syntheticFrame);
338
326
  return syntheticFrame;
@@ -341,32 +329,33 @@ const describeNativeComponentFrame = (
341
329
  // https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
342
330
  export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
343
331
  let stackFrame = "";
332
+ const workTags = getReactWorkTagsForFiber(fiber);
344
333
  switch (fiber.tag) {
345
- case ActivityComponentTag:
334
+ case workTags.ActivityComponent:
346
335
  stackFrame = describeBuiltInComponentFrame("Activity");
347
336
  break;
348
- case ClassComponentTag:
337
+ case workTags.ClassComponent:
349
338
  stackFrame = describeNativeComponentFrame(fiber.type, true);
350
339
  break;
351
- case ForwardRefTag:
340
+ case workTags.ForwardRef:
352
341
  stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
353
342
  break;
354
- case FunctionComponentTag:
355
- case SimpleMemoComponentTag:
343
+ case workTags.FunctionComponent:
344
+ case workTags.SimpleMemoComponent:
356
345
  stackFrame = describeNativeComponentFrame(fiber.type, false);
357
346
  break;
358
- case HostComponentTag:
359
- case HostHoistableTag:
360
- case HostSingletonTag:
347
+ case workTags.HostComponent:
348
+ case workTags.HostHoistable:
349
+ case workTags.HostSingleton:
361
350
  if (typeof fiber.type === "string") {
362
351
  stackFrame = describeBuiltInComponentFrame(fiber.type);
363
352
  }
364
353
  break;
365
- case LazyComponentTag:
354
+ case workTags.LazyComponent:
366
355
  // TODO: When we support Thenables as component types we should rename this.
367
356
  stackFrame = describeBuiltInComponentFrame("Lazy");
368
357
  break;
369
- case SuspenseComponentTag:
358
+ case workTags.SuspenseComponent:
370
359
  if (fiber.child !== childFiber && childFiber !== null) {
371
360
  // If we came from the second Fiber then we're in the Suspense Fallback.
372
361
  stackFrame = describeBuiltInComponentFrame("Suspense Fallback");
@@ -374,10 +363,10 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
374
363
  stackFrame = describeBuiltInComponentFrame("Suspense");
375
364
  }
376
365
  break;
377
- case SuspenseListComponentTag:
366
+ case workTags.SuspenseListComponent:
378
367
  stackFrame = describeBuiltInComponentFrame("SuspenseList");
379
368
  break;
380
- case ViewTransitionComponentTag:
369
+ case workTags.ViewTransitionComponent:
381
370
  // Note: enableViewTransition feature flag is not available in this codebase,
382
371
  // so we'll always include ViewTransition
383
372
  stackFrame = describeBuiltInComponentFrame("ViewTransition");
@@ -406,8 +395,8 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
406
395
  // Since we don't have __DEV__ in this codebase, we'll check for _debugInfo
407
396
  const debugInfo = currentFiber._debugInfo;
408
397
  if (debugInfo && Array.isArray(debugInfo)) {
409
- for (let i = debugInfo.length - 1; i >= 0; i--) {
410
- const debugEntry = debugInfo[i];
398
+ for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
399
+ const debugEntry = debugInfo[debugInfoIndex];
411
400
  if (typeof debugEntry.name === "string") {
412
401
  componentStack += describeDebugInfoFrame(debugEntry.name, debugEntry.env);
413
402
  }
@@ -420,7 +409,7 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
420
409
  return componentStack;
421
410
  } catch (error) {
422
411
  if (error instanceof Error) {
423
- return `\nError generating stack: ${error.message}\n${error.stack}`;
412
+ return `\nBippy couldn’t generate the stack: ${error.message}\n${error.stack}`;
424
413
  }
425
414
  return "";
426
415
  }
@@ -445,14 +434,10 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
445
434
  * @see https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js#L12
446
435
  */
447
436
  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
437
  let formattedStack = stack;
452
438
  if (!formattedStack) {
453
439
  return "";
454
440
  }
455
- Error.prepareStackTrace = prevPrepareStackTrace;
456
441
 
457
442
  if (formattedStack.startsWith("Error: react-stack-top-frame\n")) {
458
443
  // V8's default formatting prefixes with the error message which we
@@ -465,8 +450,7 @@ export const formatOwnerStack = (stack: string): string => {
465
450
  formattedStack = formattedStack.slice(firstNewlineIndex + 1);
466
451
  }
467
452
  let bottomFrameIndex = Math.max(
468
- formattedStack.indexOf("react_stack_bottom_frame"),
469
- formattedStack.indexOf("react-stack-bottom-frame"),
453
+ ...REACT_STACK_BOTTOM_FRAME_PATTERNS.map((pattern) => formattedStack.indexOf(pattern)),
470
454
  );
471
455
  if (bottomFrameIndex !== -1) {
472
456
  bottomFrameIndex = formattedStack.lastIndexOf("\n", bottomFrameIndex);
@@ -483,11 +467,6 @@ export const formatOwnerStack = (stack: string): string => {
483
467
  return formattedStack;
484
468
  };
485
469
 
486
- interface DebugStackEntry {
487
- componentName: string;
488
- stackFrames: StackFrame[];
489
- }
490
-
491
470
  const isReactServerComponentFrame = (stackFrame: StackFrame): boolean =>
492
471
  Boolean(
493
472
  stackFrame.functionName && stackFrame.fileName && isServerComponentUrl(stackFrame.fileName),
@@ -499,24 +478,22 @@ const areStackFramesEqual = (firstFrame: StackFrame, secondFrame: StackFrame): b
499
478
  firstFrame.columnNumber === secondFrame.columnNumber;
500
479
 
501
480
  const buildFunctionNameToRscFramesMap = (
502
- debugStackEntries: DebugStackEntry[],
481
+ debugStackFrames: StackFrame[],
503
482
  ): Map<string, StackFrame[]> => {
504
483
  const functionNameToRscFrames = new Map<string, StackFrame[]>();
505
484
 
506
- for (const debugStackEntry of debugStackEntries) {
507
- for (const stackFrame of debugStackEntry.stackFrames) {
508
- if (!isReactServerComponentFrame(stackFrame)) continue;
485
+ for (const stackFrame of debugStackFrames) {
486
+ if (!isReactServerComponentFrame(stackFrame)) continue;
509
487
 
510
- const functionName = stackFrame.functionName!;
511
- const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
512
- const isDuplicateFrame = framesForFunction.some((existingFrame) =>
513
- areStackFramesEqual(existingFrame, stackFrame),
514
- );
488
+ const functionName = stackFrame.functionName!;
489
+ const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
490
+ const isDuplicateFrame = framesForFunction.some((existingFrame) =>
491
+ areStackFramesEqual(existingFrame, stackFrame),
492
+ );
515
493
 
516
- if (!isDuplicateFrame) {
517
- framesForFunction.push(stackFrame);
518
- functionNameToRscFrames.set(functionName, framesForFunction);
519
- }
494
+ if (!isDuplicateFrame) {
495
+ framesForFunction.push(stackFrame);
496
+ functionNameToRscFrames.set(functionName, framesForFunction);
520
497
  }
521
498
  }
522
499
 
@@ -577,7 +554,7 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
577
554
  while (owner) {
578
555
  if (isFiberOwner(owner)) {
579
556
  const ownerFiber: Fiber = owner;
580
- owner = getDebugOwner(ownerFiber);
557
+ owner = ownerFiber._debugOwner;
581
558
  if (owner && hasDebugStack(ownerFiber)) {
582
559
  const { frames, isTrusted } = parseDebugStack(ownerFiber._debugStack);
583
560
  if (isTrusted) {
@@ -601,53 +578,39 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
601
578
  return ownerStackFrames;
602
579
  };
603
580
 
604
- const getDebugStackEntries = (rootFiber: Fiber): DebugStackEntry[] => {
605
- const debugStackEntries: DebugStackEntry[] = [];
581
+ const getDebugStackFrames = (rootFiber: Fiber): StackFrame[] => {
582
+ const debugStackFrames: StackFrame[] = [];
606
583
 
607
584
  traverseFiber(
608
585
  rootFiber,
609
586
  (currentFiber) => {
610
587
  if (!hasDebugStack(currentFiber)) return;
611
588
 
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
- });
589
+ const { frames, isTrusted } = parseDebugStack(currentFiber._debugStack);
590
+ if (isTrusted) {
591
+ debugStackFrames.push(...frames);
592
+ }
621
593
  },
622
594
  true,
623
595
  );
624
596
 
625
- return debugStackEntries;
597
+ return debugStackFrames;
626
598
  };
627
599
 
628
600
  /**
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.
601
+ * The unsymbolicated frames behind {@link getParentStack}: bundle-space
602
+ * locations from re-invoking each component with a throwing dispatcher,
603
+ * with server frames enriched from debug stacks by name matching.
634
604
  */
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);
605
+ export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
606
+ const debugStackFrames = getDebugStackFrames(fiber);
641
607
  const fallbackStackFrames = parseStack(getFallbackParentStack(fiber));
642
- const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackEntries);
608
+ const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackFrames);
643
609
  const functionNameToUsageIndex = new Map<string, number>();
644
610
 
645
611
  const enrichedStackFrames = fallbackStackFrames.map((stackFrame): StackFrame => {
646
612
  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));
613
+ stackFrame.source !== undefined && SERVER_ENV_PATTERN.test(stackFrame.source);
651
614
 
652
615
  if (isServerFrame) {
653
616
  return getEnrichedServerStackFrame(
@@ -660,15 +623,26 @@ export const getParentStack = async (
660
623
  return stackFrame;
661
624
  });
662
625
 
663
- const deduplicatedStackFrames = enrichedStackFrames.filter((stackFrame, index, frames) => {
626
+ return enrichedStackFrames.filter((stackFrame, index, frames) => {
664
627
  if (index === 0) return true;
665
628
  const previousFrame = frames[index - 1];
666
629
  return stackFrame.functionName !== previousFrame.functionName;
667
630
  });
668
-
669
- return symbolicateStack(deduplicatedStackFrames, shouldCache, fetchFunction);
670
631
  };
671
632
 
633
+ /**
634
+ * Returns a stack of ALL ancestor components in the render tree (the fiber's
635
+ * `return` chain), including wrappers that render `{children}` without having
636
+ * created this fiber's JSX. Locations come from re-invoking each component
637
+ * with a throwing dispatcher; server frames are enriched from debug stacks by
638
+ * name matching. Works on every React version.
639
+ */
640
+ export const getParentStack = async (
641
+ fiber: Fiber,
642
+ shouldCache = true,
643
+ fetchFunction?: (url: string) => Promise<Response>,
644
+ ): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
645
+
672
646
  // an owner frame is only actionable if it can point an editor somewhere:
673
647
  // it needs a file location and must not be ignore-listed bundler/framework code
674
648
  const isLocatableFrame = (stackFrame: StackFrame): boolean =>
@@ -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);
@@ -35,7 +35,7 @@ const flattenHooksTree = (hooksTree: HooksTree): HooksNode[] => {
35
35
 
36
36
  const findSourceContentByFileName = (
37
37
  sources: string[],
38
- sourcesContent: string[] | undefined,
38
+ sourcesContent: Array<string | null> | undefined,
39
39
  fileName: string,
40
40
  ): string | null => {
41
41
  if (!sourcesContent) return null;
@@ -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);
@@ -17,7 +16,6 @@ export interface StackFrame {
17
16
 
18
17
  export interface ParseOptions {
19
18
  slice?: number | [number, number];
20
- allowEmpty?: boolean;
21
19
  includeInElement?: boolean;
22
20
  }
23
21