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.
- package/LICENSE +1 -1
- package/README.md +26 -1
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +39 -42
- package/dist/core.d.ts +39 -42
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +1 -1
- package/dist/{types.d.cts → errors.d.cts} +187 -68
- package/dist/{types.d.ts → errors.d.ts} +187 -68
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.iife.js +1 -1
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +8 -0
- package/dist/install-hook-only.d.ts +8 -0
- package/dist/install-hook-only.iife.js +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +13 -14
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +13 -14
- package/package.json +10 -6
- package/src/core.ts +246 -293
- package/src/errors.ts +34 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +152 -110
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/{types.ts → react-internals/types.ts} +13 -86
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +42 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +86 -128
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +703 -135
- package/src/generated/react-work-tags.ts +0 -169
- package/src/react-internals.ts +0 -67
- package/src/unsubscribe.ts +0 -17
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
import { getDisplayName, getType, traverseFiber } from "../core.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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 =
|
|
164
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
318
|
+
case workTags.ActivityComponent:
|
|
346
319
|
stackFrame = describeBuiltInComponentFrame("Activity");
|
|
347
320
|
break;
|
|
348
|
-
case
|
|
321
|
+
case workTags.ClassComponent:
|
|
349
322
|
stackFrame = describeNativeComponentFrame(fiber.type, true);
|
|
350
323
|
break;
|
|
351
|
-
case
|
|
324
|
+
case workTags.ForwardRef:
|
|
352
325
|
stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
|
|
353
326
|
break;
|
|
354
|
-
case
|
|
355
|
-
case
|
|
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
|
|
359
|
-
case
|
|
360
|
-
case
|
|
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
|
|
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
|
|
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
|
|
350
|
+
case workTags.SuspenseListComponent:
|
|
378
351
|
stackFrame = describeBuiltInComponentFrame("SuspenseList");
|
|
379
352
|
break;
|
|
380
|
-
case
|
|
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
|
|
410
|
-
const debugEntry = debugInfo[
|
|
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 `\
|
|
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(
|
|
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
|
-
|
|
465
|
+
debugStackFrames: StackFrame[],
|
|
503
466
|
): Map<string, StackFrame[]> => {
|
|
504
467
|
const functionNameToRscFrames = new Map<string, StackFrame[]>();
|
|
505
468
|
|
|
506
|
-
for (const
|
|
507
|
-
|
|
508
|
-
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
469
|
+
for (const stackFrame of debugStackFrames) {
|
|
470
|
+
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
509
471
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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 =
|
|
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
|
|
605
|
-
const
|
|
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
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
|
581
|
+
return debugStackFrames;
|
|
626
582
|
};
|
|
627
583
|
|
|
628
584
|
/**
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
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
|
|
636
|
-
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(
|
|
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
|
-
|
|
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
|
-
|
|
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?:
|
|
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 =
|
|
121
|
-
|
|
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
|
-
|
|
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 {
|
|
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?:
|
|
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 {
|
|
93
|
+
const { sourceContentCache, fetchFn } = context;
|
|
124
94
|
|
|
125
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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?:
|
|
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
|
|
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
|
|
37
|
+
const parsed = parseFFOrSafariString(rawLine)[0];
|
|
41
38
|
if (parsed) frames.push(parsed);
|
|
42
39
|
}
|
|
43
40
|
}
|
|
44
|
-
return
|
|
41
|
+
return frames;
|
|
45
42
|
}
|
|
46
43
|
if (stackString.match(CHROME_IE_STACK_REGEXP)) {
|
|
47
|
-
return parseV8OrIeString(stackString
|
|
44
|
+
return parseV8OrIeString(stackString);
|
|
48
45
|
}
|
|
49
|
-
return parseFFOrSafariString(stackString
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
118
|
-
const filteredLines =
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
};
|