bippy 0.5.27 → 0.5.29

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 (43) hide show
  1. package/README.md +0 -2
  2. package/dist/{core-BDWE7M7e.d.ts → core-Cjoce0EW.d.ts} +2 -2
  3. package/dist/core-DBBh-FTl.js +9 -0
  4. package/dist/{core-CEUgwvkw.d.cts → core-Y1ecSyti.d.cts} +2 -2
  5. package/dist/core-xjGqMMEY.cjs +9 -0
  6. package/dist/core.cjs +1 -1
  7. package/dist/core.d.cts +1 -1
  8. package/dist/core.d.ts +1 -1
  9. package/dist/core.js +1 -1
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.iife.js +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/{install-hook-only-BOBPiBkc.js → install-hook-only-CRye53Kz.cjs} +1 -1
  16. package/dist/{install-hook-only-aVNdwrnj.cjs → install-hook-only-DfpahZZO.js} +1 -1
  17. package/dist/install-hook-only.cjs +1 -1
  18. package/dist/install-hook-only.iife.js +1 -1
  19. package/dist/install-hook-only.js +1 -1
  20. package/dist/rdt-hook-Czn6qzdx.js +9 -0
  21. package/dist/rdt-hook-DnMMBqZs.cjs +9 -0
  22. package/dist/source.cjs +7 -7
  23. package/dist/source.d.cts +1 -1
  24. package/dist/source.d.ts +1 -1
  25. package/dist/source.js +12 -12
  26. package/package.json +3 -1
  27. package/src/core.ts +1325 -0
  28. package/src/index.ts +3 -0
  29. package/src/install-hook-only.ts +3 -0
  30. package/src/rdt-hook.ts +237 -0
  31. package/src/source/constants.ts +26 -0
  32. package/src/source/get-display-name-from-source.ts +103 -0
  33. package/src/source/get-source.ts +218 -0
  34. package/src/source/index.ts +6 -0
  35. package/src/source/owner-stack.ts +598 -0
  36. package/src/source/parse-stack.ts +295 -0
  37. package/src/source/symbolication.ts +410 -0
  38. package/src/source/types.ts +6 -0
  39. package/src/types.ts +244 -0
  40. package/dist/core-D8j-0_U5.cjs +0 -9
  41. package/dist/core-coQbWNwP.js +0 -9
  42. package/dist/rdt-hook-3SlCAu5p.cjs +0 -9
  43. package/dist/rdt-hook-BZMdLD7S.js +0 -9
@@ -0,0 +1,598 @@
1
+ import {
2
+ _renderers,
3
+ ActivityComponentTag,
4
+ ClassComponentTag,
5
+ Fiber,
6
+ ForwardRefTag,
7
+ FunctionComponentTag,
8
+ getRDTHook,
9
+ HostComponentTag,
10
+ HostHoistableTag,
11
+ HostSingletonTag,
12
+ LazyComponentTag,
13
+ SimpleMemoComponentTag,
14
+ SuspenseComponentTag,
15
+ SuspenseListComponentTag,
16
+ ViewTransitionComponentTag,
17
+ getDisplayName,
18
+ traverseFiber,
19
+ } from '../core.js';
20
+ import { SERVER_FRAME_MARKER } from './constants.js';
21
+
22
+ import { parseStack, StackFrame } from './parse-stack.js';
23
+ import { symbolicateStack } from './symbolication.js';
24
+
25
+ export const hasDebugStack = (
26
+ fiber: Fiber,
27
+ ): fiber is Fiber & {
28
+ _debugStack: NonNullable<Fiber['_debugStack']>;
29
+ } => {
30
+ return (
31
+ fiber._debugStack instanceof Error &&
32
+ typeof fiber._debugStack?.stack === 'string'
33
+ );
34
+ };
35
+
36
+ const getCurrentDispatcher = (): null | React.RefObject<unknown> => {
37
+ const rdtHook = getRDTHook();
38
+ for (const renderer of [
39
+ ...Array.from(_renderers),
40
+ ...Array.from(rdtHook.renderers.values()),
41
+ ]) {
42
+ const currentDispatcherRef = renderer.currentDispatcherRef;
43
+ if (currentDispatcherRef && typeof currentDispatcherRef === 'object') {
44
+ return 'H' in currentDispatcherRef
45
+ ? currentDispatcherRef.H
46
+ : currentDispatcherRef.current;
47
+ }
48
+ }
49
+ return null;
50
+ };
51
+
52
+ const setCurrentDispatcher = (value: null | React.RefObject<unknown>): void => {
53
+ for (const renderer of _renderers) {
54
+ const currentDispatcherRef = renderer.currentDispatcherRef;
55
+ if (currentDispatcherRef && typeof currentDispatcherRef === 'object') {
56
+ if ('H' in currentDispatcherRef) {
57
+ currentDispatcherRef.H = value;
58
+ } else {
59
+ currentDispatcherRef.current = value;
60
+ }
61
+ }
62
+ }
63
+ };
64
+
65
+ const describeBuiltInComponentFrame = (name: string): string => {
66
+ return `\n in ${name}`;
67
+ };
68
+
69
+ export const describeDebugInfoFrame = (name: string, env?: string): string => {
70
+ let frameDescription = describeBuiltInComponentFrame(name);
71
+ if (env) {
72
+ frameDescription += ` (at ${env})`;
73
+ }
74
+ return frameDescription;
75
+ };
76
+
77
+ let reEntry = false;
78
+
79
+ // https://github.com/facebook/react/blob/f739642745577a8e4dcb9753836ac3589b9c590a/packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js#L22
80
+ const describeNativeComponentFrame = (
81
+ component: React.ComponentType<unknown>,
82
+ construct: boolean,
83
+ ): string => {
84
+ if (!component || reEntry) {
85
+ return '';
86
+ }
87
+
88
+ const previousPrepareStackTrace = Error.prepareStackTrace;
89
+ Error.prepareStackTrace = undefined;
90
+ reEntry = true;
91
+
92
+ const previousDispatcher = getCurrentDispatcher();
93
+ setCurrentDispatcher(null);
94
+ const previousConsoleError = console.error;
95
+ const previousConsoleWarn = console.warn;
96
+ console.error = () => {};
97
+ console.warn = () => {};
98
+ try {
99
+ /**
100
+ * Finding a common stack frame between sample and control errors can be
101
+ * tricky given the different types and levels of stack trace truncation from
102
+ * different JS VMs. So instead we'll attempt to control what that common
103
+ * frame should be through this object method:
104
+ * Having both the sample and control errors be in the function under the
105
+ * `DescribeNativeComponentFrameRoot` property, + setting the `name` and
106
+ * `displayName` properties of the function ensures that a stack
107
+ * frame exists that has the method name `DescribeNativeComponentFrameRoot` in
108
+ * it for both control and sample stacks.
109
+ */
110
+ const RunInRootFrame = {
111
+ DetermineComponentFrameRoot() {
112
+ let control: unknown;
113
+ try {
114
+ // This should throw.
115
+ if (construct) {
116
+ // Something should be setting the props in the constructor.
117
+ const ThrowingConstructor = function () {
118
+ throw Error();
119
+ };
120
+ Object.defineProperty(ThrowingConstructor.prototype, 'props', {
121
+ set: function () {
122
+ // We use a throwing setter instead of frozen or non-writable props
123
+ // because that won't throw in a non-strict mode function.
124
+ throw Error();
125
+ },
126
+ });
127
+ if (typeof Reflect === 'object' && Reflect.construct) {
128
+ // We construct a different control for this case to include any extra
129
+ // frames added by the construct call.
130
+ try {
131
+ Reflect.construct(ThrowingConstructor, []);
132
+ } catch (caughtError) {
133
+ control = caughtError;
134
+ }
135
+ Reflect.construct(component, [], ThrowingConstructor);
136
+ } else {
137
+ try {
138
+ // @ts-expect-error -- ThrowingConstructor is a constructor function
139
+ ThrowingConstructor.call();
140
+ } catch (caughtError) {
141
+ control = caughtError;
142
+ }
143
+ // @ts-expect-error -- ThrowingConstructor is a constructor function
144
+ component.call(ThrowingConstructor.prototype);
145
+ }
146
+ } else {
147
+ try {
148
+ throw Error();
149
+ } catch (caughtError) {
150
+ control = caughtError;
151
+ }
152
+ // TODO(luna): This will currently only throw if the function component
153
+ // tries to access React/ReactDOM/props. We should probably make this throw
154
+ // in simple components too
155
+ const maybePromise = (component as () => Promise<unknown>)();
156
+
157
+ // If the function component returns a promise, it's likely an async
158
+ // component, which we don't yet support. Attach a noop catch handler to
159
+ // silence the error.
160
+ // TODO: Implement component stacks for async client components?
161
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises -- we literally check if this is a promise here
162
+ if (maybePromise && typeof maybePromise.catch === 'function') {
163
+ maybePromise.catch(() => {});
164
+ }
165
+ }
166
+ } catch (sample: unknown) {
167
+ // This is inlined manually because closure doesn't do it for us.
168
+ if (
169
+ sample instanceof Error &&
170
+ control instanceof Error &&
171
+ typeof sample.stack === 'string'
172
+ ) {
173
+ return [sample.stack, control.stack];
174
+ }
175
+ }
176
+ return [null, null];
177
+ },
178
+ };
179
+
180
+ // @ts-expect-error --- displayName is not a property of the function
181
+ RunInRootFrame.DetermineComponentFrameRoot.displayName =
182
+ 'DetermineComponentFrameRoot';
183
+ const namePropDescriptor = Object.getOwnPropertyDescriptor(
184
+ // eslint-disable-next-line @typescript-eslint/unbound-method
185
+ RunInRootFrame.DetermineComponentFrameRoot,
186
+ 'name',
187
+ );
188
+ // Before ES6, the `name` property was not configurable.
189
+ if (namePropDescriptor?.configurable) {
190
+ // V8 utilizes a function's `name` property when generating a stack trace.
191
+ Object.defineProperty(
192
+ // eslint-disable-next-line @typescript-eslint/unbound-method
193
+ RunInRootFrame.DetermineComponentFrameRoot,
194
+ // Configurable properties can be updated even if its writable descriptor
195
+ // is set to `false`.
196
+ 'name',
197
+ { value: 'DetermineComponentFrameRoot' },
198
+ );
199
+ }
200
+
201
+ const [sampleStack, controlStack] =
202
+ RunInRootFrame.DetermineComponentFrameRoot();
203
+ if (sampleStack && controlStack) {
204
+ // This extracts the first frame from the sample that isn't also in the control.
205
+ // Skipping one frame that we assume is the frame that calls the two.
206
+ const sampleLines = sampleStack.split('\n');
207
+ const controlLines = controlStack.split('\n');
208
+ let sampleIndex = 0;
209
+ let controlIndex = 0;
210
+ while (
211
+ sampleIndex < sampleLines.length &&
212
+ !sampleLines[sampleIndex].includes('DetermineComponentFrameRoot')
213
+ ) {
214
+ sampleIndex++;
215
+ }
216
+ while (
217
+ controlIndex < controlLines.length &&
218
+ !controlLines[controlIndex].includes('DetermineComponentFrameRoot')
219
+ ) {
220
+ controlIndex++;
221
+ }
222
+ // We couldn't find our intentionally injected common root frame, attempt
223
+ // to find another common root frame by search from the bottom of the
224
+ // control stack...
225
+ if (
226
+ sampleIndex === sampleLines.length ||
227
+ controlIndex === controlLines.length
228
+ ) {
229
+ sampleIndex = sampleLines.length - 1;
230
+ controlIndex = controlLines.length - 1;
231
+ while (
232
+ sampleIndex >= 1 &&
233
+ controlIndex >= 0 &&
234
+ sampleLines[sampleIndex] !== controlLines[controlIndex]
235
+ ) {
236
+ // We expect at least one stack frame to be shared.
237
+ // Typically this will be the root most one. However, stack frames may be
238
+ // cut off due to maximum stack limits. In this case, one maybe cut off
239
+ // earlier than the other. We assume that the sample is longer or the same
240
+ // and there for cut off earlier. So we should find the root most frame in
241
+ // the sample somewhere in the control.
242
+ controlIndex--;
243
+ }
244
+ }
245
+ for (
246
+ ;
247
+ sampleIndex >= 1 && controlIndex >= 0;
248
+ sampleIndex--, controlIndex--
249
+ ) {
250
+ // Next we find the first one that isn't the same which should be the
251
+ // frame that called our sample function and the control.
252
+ if (sampleLines[sampleIndex] !== controlLines[controlIndex]) {
253
+ // In V8, the first line is describing the message but other VMs don't.
254
+ // If we're about to return the first line, and the control is also on the same
255
+ // line, that's a pretty good indicator that our sample threw at same line as
256
+ // the control. I.e. before we entered the sample frame. So we ignore this result.
257
+ // This can happen if you passed a class to function component, or non-function.
258
+ if (sampleIndex !== 1 || controlIndex !== 1) {
259
+ do {
260
+ sampleIndex--;
261
+ controlIndex--;
262
+ // We may still have similar intermediate frames from the construct call.
263
+ // The next one that isn't the same should be our match though.
264
+ if (
265
+ controlIndex < 0 ||
266
+ sampleLines[sampleIndex] !== controlLines[controlIndex]
267
+ ) {
268
+ // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
269
+ let stackFrame = `\n${sampleLines[sampleIndex].replace(
270
+ ' at new ',
271
+ ' at ',
272
+ )}`;
273
+
274
+ const displayName = getDisplayName(component);
275
+ // If our component frame is labeled "<anonymous>"
276
+ // but we have a user-provided "displayName"
277
+ // splice it in to make the stack more readable.
278
+ if (displayName && stackFrame.includes('<anonymous>')) {
279
+ stackFrame = stackFrame.replace('<anonymous>', displayName);
280
+ }
281
+ // Return the line we found.
282
+ return stackFrame;
283
+ }
284
+ } while (sampleIndex >= 1 && controlIndex >= 0);
285
+ }
286
+ break;
287
+ }
288
+ }
289
+ }
290
+ } finally {
291
+ reEntry = false;
292
+
293
+ Error.prepareStackTrace = previousPrepareStackTrace;
294
+
295
+ setCurrentDispatcher(previousDispatcher);
296
+ console.error = previousConsoleError;
297
+ console.warn = previousConsoleWarn;
298
+ }
299
+
300
+ const componentName = component ? getDisplayName(component) : '';
301
+ const syntheticFrame = componentName
302
+ ? describeBuiltInComponentFrame(componentName)
303
+ : '';
304
+ return syntheticFrame;
305
+ };
306
+
307
+ // https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
308
+ export const describeFiber = (
309
+ fiber: Fiber,
310
+ childFiber: Fiber | null,
311
+ ): string => {
312
+ const tag = fiber.tag as number;
313
+ let stackFrame = '';
314
+ switch (tag) {
315
+ case ActivityComponentTag:
316
+ stackFrame = describeBuiltInComponentFrame('Activity');
317
+ break;
318
+ case ClassComponentTag:
319
+ stackFrame = describeNativeComponentFrame(fiber.type, true);
320
+ break;
321
+ case ForwardRefTag:
322
+ stackFrame = describeNativeComponentFrame(
323
+ (fiber.type as { render: React.ComponentType<unknown> }).render,
324
+ false,
325
+ );
326
+ break;
327
+ case FunctionComponentTag:
328
+ case SimpleMemoComponentTag:
329
+ stackFrame = describeNativeComponentFrame(fiber.type, false);
330
+ break;
331
+ case HostComponentTag:
332
+ case HostHoistableTag:
333
+ case HostSingletonTag:
334
+ stackFrame = describeBuiltInComponentFrame(fiber.type as string);
335
+ break;
336
+ case LazyComponentTag:
337
+ // TODO: When we support Thenables as component types we should rename this.
338
+ stackFrame = describeBuiltInComponentFrame('Lazy');
339
+ break;
340
+ case SuspenseComponentTag:
341
+ if (fiber.child !== childFiber && childFiber !== null) {
342
+ // If we came from the second Fiber then we're in the Suspense Fallback.
343
+ stackFrame = describeBuiltInComponentFrame('Suspense Fallback');
344
+ } else {
345
+ stackFrame = describeBuiltInComponentFrame('Suspense');
346
+ }
347
+ break;
348
+ case SuspenseListComponentTag:
349
+ stackFrame = describeBuiltInComponentFrame('SuspenseList');
350
+ break;
351
+ case ViewTransitionComponentTag:
352
+ // Note: enableViewTransition feature flag is not available in this codebase,
353
+ // so we'll always include ViewTransition
354
+ stackFrame = describeBuiltInComponentFrame('ViewTransition');
355
+ break;
356
+ default:
357
+ return '';
358
+ }
359
+
360
+ return stackFrame;
361
+ };
362
+
363
+ /**
364
+ * react 19 introduces the _debugStack property, which we can use to grab the stack.
365
+ * however, for versions that don't have this property, we need to construct
366
+ * a "fake" version of the owner stack
367
+ */
368
+ export const getFallbackOwnerStack = (thisFiber: Fiber): string => {
369
+ try {
370
+ let componentStack = '';
371
+ let currentFiber: Fiber | null = thisFiber;
372
+ let previousFiber: Fiber | null = null;
373
+ do {
374
+ componentStack += describeFiber(currentFiber, previousFiber);
375
+
376
+ // Add any Server Component stack frames in reverse order (dev only).
377
+ // Since we don't have __DEV__ in this codebase, we'll check for _debugInfo
378
+ const debugInfo = currentFiber._debugInfo;
379
+ if (debugInfo && Array.isArray(debugInfo)) {
380
+ for (let i = debugInfo.length - 1; i >= 0; i--) {
381
+ const debugEntry = debugInfo[i];
382
+ if (typeof debugEntry.name === 'string') {
383
+ componentStack += describeDebugInfoFrame(
384
+ debugEntry.name,
385
+ debugEntry.env,
386
+ );
387
+ }
388
+ }
389
+ }
390
+
391
+ previousFiber = currentFiber;
392
+ currentFiber = currentFiber.return;
393
+ } while (currentFiber);
394
+ return componentStack;
395
+ } catch (error) {
396
+ if (error instanceof Error) {
397
+ return `\nError generating stack: ${error.message}\n${error.stack}`;
398
+ }
399
+ return '';
400
+ }
401
+ };
402
+
403
+ /**
404
+ * takes Error.stack and formats it to only the React owner stack
405
+ *
406
+ * before:
407
+ * ```
408
+ * Error: react-stack-top-frame
409
+ * at fakeJSXCallSite (http://localhost:3000/_next/static/chunks/<chunk-name>._.js:17665:16)
410
+ * at TodoItem (rsc://React/Server/file:///path/to/project/.next/server/chunks/ssr/<chunk-name>._.js)
411
+ * at react-stack-bottom-frame (http://localhost:3000/_next/static/chunks/<chunk-name>._.js:17984:89)
412
+ * ```
413
+ *
414
+ * after:
415
+ * ```
416
+ * at TodoItem (rsc://React/Server/file:///path/to/project/.next/server/chunks/ssr/<chunk-name>._.js)
417
+ * ```
418
+ *
419
+ * @see https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js#L12
420
+ */
421
+ export const formatOwnerStack = (stack: string): string => {
422
+ const prevPrepareStackTrace = Error.prepareStackTrace;
423
+ Error.prepareStackTrace = undefined;
424
+ let formattedStack = stack;
425
+ if (!formattedStack) {
426
+ return '';
427
+ }
428
+ Error.prepareStackTrace = prevPrepareStackTrace;
429
+
430
+ if (formattedStack.startsWith('Error: react-stack-top-frame\n')) {
431
+ // V8's default formatting prefixes with the error message which we
432
+ // don't want/need
433
+ formattedStack = formattedStack.slice(29);
434
+ }
435
+ let idx = formattedStack.indexOf('\n');
436
+ if (idx !== -1) {
437
+ // pop the JSX frame
438
+ formattedStack = formattedStack.slice(idx + 1);
439
+ }
440
+ idx = Math.max(
441
+ formattedStack.indexOf('react_stack_bottom_frame'),
442
+ formattedStack.indexOf('react-stack-bottom-frame'),
443
+ );
444
+ if (idx !== -1) {
445
+ idx = formattedStack.lastIndexOf('\n', idx);
446
+ }
447
+ if (idx !== -1) {
448
+ // cut off everything after the bottom frame since it'll be internals.
449
+ formattedStack = formattedStack.slice(0, idx);
450
+ } else {
451
+ // we didn't find any internal callsite out to user space.
452
+ // This means that this was called outside an owner or the owner is fully internal.
453
+ // to keep things light we exclude the entire trace in this case.
454
+ return '';
455
+ }
456
+ return formattedStack;
457
+ };
458
+
459
+ interface OwnerStackEntry {
460
+ componentName: string;
461
+ stackFrames: StackFrame[];
462
+ }
463
+
464
+ const isReactServerComponentFrame = (stackFrame: StackFrame): boolean =>
465
+ Boolean(stackFrame.fileName?.startsWith('rsc://') && stackFrame.functionName);
466
+
467
+ const areStackFramesEqual = (
468
+ firstFrame: StackFrame,
469
+ secondFrame: StackFrame,
470
+ ): boolean =>
471
+ firstFrame.fileName === secondFrame.fileName &&
472
+ firstFrame.lineNumber === secondFrame.lineNumber &&
473
+ firstFrame.columnNumber === secondFrame.columnNumber;
474
+
475
+ const buildFunctionNameToRscFramesMap = (
476
+ ownerStackEntries: OwnerStackEntry[],
477
+ ): Map<string, StackFrame[]> => {
478
+ const functionNameToRscFrames = new Map<string, StackFrame[]>();
479
+
480
+ for (const ownerEntry of ownerStackEntries) {
481
+ for (const stackFrame of ownerEntry.stackFrames) {
482
+ if (!isReactServerComponentFrame(stackFrame)) continue;
483
+
484
+ const functionName = stackFrame.functionName!;
485
+ const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
486
+ const isDuplicateFrame = framesForFunction.some((existingFrame) =>
487
+ areStackFramesEqual(existingFrame, stackFrame),
488
+ );
489
+
490
+ if (!isDuplicateFrame) {
491
+ framesForFunction.push(stackFrame);
492
+ functionNameToRscFrames.set(functionName, framesForFunction);
493
+ }
494
+ }
495
+ }
496
+
497
+ return functionNameToRscFrames;
498
+ };
499
+
500
+ const getEnrichedServerStackFrame = (
501
+ serverFrame: StackFrame,
502
+ functionNameToRscFrames: Map<string, StackFrame[]>,
503
+ functionNameToUsageIndex: Map<string, number>,
504
+ ): StackFrame => {
505
+ if (!serverFrame.functionName) {
506
+ return { ...serverFrame, isServer: true };
507
+ }
508
+
509
+ const availableRscFrames = functionNameToRscFrames.get(
510
+ serverFrame.functionName,
511
+ );
512
+ if (!availableRscFrames || availableRscFrames.length === 0) {
513
+ return { ...serverFrame, isServer: true };
514
+ }
515
+
516
+ const currentUsageIndex =
517
+ functionNameToUsageIndex.get(serverFrame.functionName) ?? 0;
518
+ const resolvedRscFrame =
519
+ availableRscFrames[currentUsageIndex % availableRscFrames.length];
520
+ functionNameToUsageIndex.set(serverFrame.functionName, currentUsageIndex + 1);
521
+
522
+ return {
523
+ ...serverFrame,
524
+ isServer: true,
525
+ fileName: resolvedRscFrame.fileName,
526
+ lineNumber: resolvedRscFrame.lineNumber,
527
+ columnNumber: resolvedRscFrame.columnNumber,
528
+ source: serverFrame.source?.replace(
529
+ SERVER_FRAME_MARKER,
530
+ `(${resolvedRscFrame.fileName}:${resolvedRscFrame.lineNumber}:${resolvedRscFrame.columnNumber})`,
531
+ ),
532
+ };
533
+ };
534
+
535
+ const getOwnerStackEntries = (rootFiber: Fiber): OwnerStackEntry[] => {
536
+ const ownerStackEntries: OwnerStackEntry[] = [];
537
+
538
+ traverseFiber(
539
+ rootFiber,
540
+ (currentFiber) => {
541
+ if (!hasDebugStack(currentFiber)) return;
542
+
543
+ const componentName =
544
+ typeof currentFiber.type !== 'string'
545
+ ? getDisplayName(currentFiber.type) || '<anonymous>'
546
+ : currentFiber.type;
547
+
548
+ ownerStackEntries.push({
549
+ componentName,
550
+ stackFrames: parseStack(
551
+ formatOwnerStack(currentFiber._debugStack?.stack),
552
+ ),
553
+ });
554
+ },
555
+ true,
556
+ );
557
+
558
+ return ownerStackEntries;
559
+ };
560
+
561
+ export const getOwnerStack = async (
562
+ fiber: Fiber,
563
+ shouldCache = true,
564
+ fetchFunction?: (url: string) => Promise<Response>,
565
+ ): Promise<StackFrame[]> => {
566
+ const ownerStackEntries = getOwnerStackEntries(fiber);
567
+ const fallbackStackFrames = parseStack(getFallbackOwnerStack(fiber));
568
+ const functionNameToRscFrames =
569
+ buildFunctionNameToRscFramesMap(ownerStackEntries);
570
+ const functionNameToUsageIndex = new Map<string, number>();
571
+
572
+ const enrichedStackFrames = fallbackStackFrames.map(
573
+ (stackFrame): StackFrame => {
574
+ const isServerFrame =
575
+ stackFrame.source?.includes(SERVER_FRAME_MARKER) ?? false;
576
+
577
+ if (isServerFrame) {
578
+ return getEnrichedServerStackFrame(
579
+ stackFrame,
580
+ functionNameToRscFrames,
581
+ functionNameToUsageIndex,
582
+ );
583
+ }
584
+
585
+ return stackFrame;
586
+ },
587
+ );
588
+
589
+ const deduplicatedStackFrames = enrichedStackFrames.filter(
590
+ (stackFrame, index, frames) => {
591
+ if (index === 0) return true;
592
+ const previousFrame = frames[index - 1];
593
+ return stackFrame.functionName !== previousFrame.functionName;
594
+ },
595
+ );
596
+
597
+ return symbolicateStack(deduplicatedStackFrames, shouldCache, fetchFunction);
598
+ };