bippy 0.7.2 → 0.7.3-dev.0c7e504
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 -0
- package/README.md +8 -68
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +56 -9
- package/dist/errors.d.ts +56 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -38
- package/dist/index.d.ts +18 -42
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +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 +12 -11
- package/dist/source.d.cts +36 -9
- package/dist/source.d.ts +36 -9
- package/dist/source.js +12 -11
- package/package.json +5 -33
- package/src/core.ts +200 -120
- package/src/rdt-hook.ts +128 -70
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/index.ts +6 -1
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +131 -55
- package/src/source/constants.ts +1 -3
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +39 -19
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +193 -46
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +3 -1
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +53 -32
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { getRenderer } from "../core.js";
|
|
2
|
+
import type { ReactDevToolsTarget } from "../rdt-hook.js";
|
|
1
3
|
import type {
|
|
2
4
|
Fiber,
|
|
3
5
|
ContextDependency,
|
|
4
6
|
MemoizedState,
|
|
5
7
|
ReactContext,
|
|
8
|
+
ReactDebugInfo,
|
|
6
9
|
RendererDispatcherRef,
|
|
7
10
|
} from "../react-internals/index.js";
|
|
8
11
|
import {
|
|
@@ -20,6 +23,7 @@ import {
|
|
|
20
23
|
|
|
21
24
|
const REACT_CONTEXT_TYPE = Symbol.for("react.context");
|
|
22
25
|
const REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
|
|
26
|
+
const REACT_RECOVERABLE_TYPE = Symbol.for("react.recoverable");
|
|
23
27
|
|
|
24
28
|
export interface HookSource {
|
|
25
29
|
lineNumber: number | null;
|
|
@@ -34,6 +38,7 @@ export interface HooksNode {
|
|
|
34
38
|
name: string;
|
|
35
39
|
value: unknown;
|
|
36
40
|
subHooks: HooksNode[];
|
|
41
|
+
debugInfo: ReactDebugInfo[] | null;
|
|
37
42
|
hookSource: HookSource | null;
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -44,6 +49,7 @@ interface HookLogEntry {
|
|
|
44
49
|
primitive: string;
|
|
45
50
|
stackError: Error;
|
|
46
51
|
value: unknown;
|
|
52
|
+
debugInfo: ReactDebugInfo[] | null;
|
|
47
53
|
dispatcherHookName: string;
|
|
48
54
|
}
|
|
49
55
|
|
|
@@ -61,6 +67,7 @@ interface InspectableThenable {
|
|
|
61
67
|
status?: unknown;
|
|
62
68
|
then: (...arguments_: unknown[]) => unknown;
|
|
63
69
|
value?: unknown;
|
|
70
|
+
_debugInfo?: ReactDebugInfo[];
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
interface ForwardRefRenderType {
|
|
@@ -68,6 +75,7 @@ interface ForwardRefRenderType {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
interface InspectedActionState {
|
|
78
|
+
debugInfo: ReactDebugInfo[] | null;
|
|
71
79
|
error: unknown;
|
|
72
80
|
value: unknown;
|
|
73
81
|
}
|
|
@@ -79,6 +87,16 @@ let currentHook: MemoizedState | null = null;
|
|
|
79
87
|
let currentContextDependency: ContextDependency<unknown> | null = null;
|
|
80
88
|
let currentThenableIndex = 0;
|
|
81
89
|
let currentThenableState: unknown[] | null = null;
|
|
90
|
+
let currentMemoCacheIndex = 0;
|
|
91
|
+
let isInspectingHooks = false;
|
|
92
|
+
|
|
93
|
+
const assertNotInspectingHooks = (): void => {
|
|
94
|
+
if (isInspectingHooks) {
|
|
95
|
+
throw new BippyHookInspectionError(
|
|
96
|
+
"Hook inspection cannot be called during another inspection.",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
82
100
|
|
|
83
101
|
const SuspenseException: unknown = new Error(
|
|
84
102
|
"Suspense interrupted this render. This error is an internal implementation detail of `use`.",
|
|
@@ -132,12 +150,14 @@ const pushHookLogEntry = (
|
|
|
132
150
|
value: unknown,
|
|
133
151
|
dispatcherHookName: string,
|
|
134
152
|
displayName: string | null = null,
|
|
153
|
+
debugInfo: ReactDebugInfo[] | null = null,
|
|
135
154
|
): void => {
|
|
136
155
|
hookLog.push({
|
|
137
156
|
displayName,
|
|
138
157
|
primitive,
|
|
139
158
|
stackError: new Error(),
|
|
140
159
|
value,
|
|
160
|
+
debugInfo,
|
|
141
161
|
dispatcherHookName,
|
|
142
162
|
});
|
|
143
163
|
};
|
|
@@ -153,22 +173,26 @@ const dispatcherUse = (usable: unknown): unknown => {
|
|
|
153
173
|
|
|
154
174
|
switch (thenable.status) {
|
|
155
175
|
case "fulfilled": {
|
|
156
|
-
pushHookLogEntry("Promise", thenable.value, "Use");
|
|
176
|
+
pushHookLogEntry("Promise", thenable.value, "Use", null, thenable._debugInfo ?? null);
|
|
157
177
|
return thenable.value;
|
|
158
178
|
}
|
|
159
179
|
case "rejected":
|
|
160
180
|
throw thenable.reason;
|
|
161
181
|
}
|
|
162
|
-
pushHookLogEntry("Unresolved", thenable, "Use");
|
|
182
|
+
pushHookLogEntry("Unresolved", thenable, "Use", null, thenable._debugInfo ?? null);
|
|
163
183
|
throw SuspenseException;
|
|
164
184
|
}
|
|
185
|
+
if ("$$typeof" in usable && usable.$$typeof === REACT_RECOVERABLE_TYPE) {
|
|
186
|
+
pushHookLogEntry("Recoverable", undefined, "Use");
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
165
189
|
if ("$$typeof" in usable && usable.$$typeof === REACT_CONTEXT_TYPE && isReactContext(usable)) {
|
|
166
190
|
const context: InspectableReactContext = {
|
|
167
191
|
_currentValue: usable._currentValue,
|
|
168
192
|
displayName: typeof usable.displayName === "string" ? usable.displayName : undefined,
|
|
169
193
|
};
|
|
170
194
|
const value = readContext(context);
|
|
171
|
-
pushHookLogEntry("Context (use)", value, "Use", context.displayName
|
|
195
|
+
pushHookLogEntry("Context (use)", value, "Use", context.displayName || "Context");
|
|
172
196
|
return value;
|
|
173
197
|
}
|
|
174
198
|
}
|
|
@@ -177,7 +201,7 @@ const dispatcherUse = (usable: unknown): unknown => {
|
|
|
177
201
|
|
|
178
202
|
const dispatcherUseContext = (context: InspectableReactContext): unknown => {
|
|
179
203
|
const value = readContext(context);
|
|
180
|
-
pushHookLogEntry("Context", value, "Context", context.displayName
|
|
204
|
+
pushHookLogEntry("Context", value, "Context", context.displayName || null);
|
|
181
205
|
return value;
|
|
182
206
|
};
|
|
183
207
|
|
|
@@ -304,21 +328,13 @@ const dispatcherUseId = (): string => {
|
|
|
304
328
|
|
|
305
329
|
const dispatcherUseMemoCache = (size: number): unknown[] => {
|
|
306
330
|
const fiber = currentFiber;
|
|
307
|
-
if (fiber === null
|
|
331
|
+
if (fiber === null) return [];
|
|
308
332
|
|
|
309
333
|
const memoCache = fiber.updateQueue?.memoCache;
|
|
310
334
|
if (memoCache === null || memoCache === undefined) return [];
|
|
311
335
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
memoCacheSlots = memoCache.data[memoCache.index] = Array.from(
|
|
315
|
-
{ length: size },
|
|
316
|
-
() => REACT_MEMO_CACHE_SENTINEL,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
memoCache.index++;
|
|
321
|
-
return memoCacheSlots;
|
|
336
|
+
const memoCacheSlots = memoCache.data[currentMemoCacheIndex++];
|
|
337
|
+
return memoCacheSlots?.slice() ?? Array.from({ length: size }, () => REACT_MEMO_CACHE_SENTINEL);
|
|
322
338
|
};
|
|
323
339
|
|
|
324
340
|
const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] => {
|
|
@@ -333,6 +349,7 @@ const inspectActionStateHook = (
|
|
|
333
349
|
initialState: unknown,
|
|
334
350
|
): InspectedActionState => {
|
|
335
351
|
let value: unknown;
|
|
352
|
+
let debugInfo: ReactDebugInfo[] | null = null;
|
|
336
353
|
let error: unknown = null;
|
|
337
354
|
if (hook !== null) {
|
|
338
355
|
const actionResult = hook.memoizedState;
|
|
@@ -340,6 +357,7 @@ const inspectActionStateHook = (
|
|
|
340
357
|
switch (actionResult.status) {
|
|
341
358
|
case "fulfilled":
|
|
342
359
|
value = actionResult.value;
|
|
360
|
+
debugInfo = actionResult._debugInfo ?? null;
|
|
343
361
|
break;
|
|
344
362
|
case "rejected":
|
|
345
363
|
error = actionResult.reason;
|
|
@@ -347,6 +365,7 @@ const inspectActionStateHook = (
|
|
|
347
365
|
default:
|
|
348
366
|
error = SuspenseException;
|
|
349
367
|
value = actionResult;
|
|
368
|
+
debugInfo = actionResult._debugInfo ?? null;
|
|
350
369
|
}
|
|
351
370
|
} else {
|
|
352
371
|
value = actionResult;
|
|
@@ -354,7 +373,7 @@ const inspectActionStateHook = (
|
|
|
354
373
|
} else {
|
|
355
374
|
value = initialState;
|
|
356
375
|
}
|
|
357
|
-
return { value, error };
|
|
376
|
+
return { value, debugInfo, error };
|
|
358
377
|
};
|
|
359
378
|
|
|
360
379
|
const createActionStateDispatcher =
|
|
@@ -363,8 +382,8 @@ const createActionStateDispatcher =
|
|
|
363
382
|
const hook = nextHook();
|
|
364
383
|
nextHook();
|
|
365
384
|
nextHook();
|
|
366
|
-
const { value, error } = inspectActionStateHook(hook, initialState);
|
|
367
|
-
pushHookLogEntry(primitive, value, primitive);
|
|
385
|
+
const { value, debugInfo, error } = inspectActionStateHook(hook, initialState);
|
|
386
|
+
pushHookLogEntry(primitive, value, primitive, null, debugInfo);
|
|
368
387
|
if (error !== null) throw error;
|
|
369
388
|
return [value, () => {}, false];
|
|
370
389
|
};
|
|
@@ -449,6 +468,7 @@ const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
|
|
|
449
468
|
dispatcher.useActionState((state: unknown) => state, null);
|
|
450
469
|
dispatcher.useHostTransitionStatus();
|
|
451
470
|
dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
|
|
471
|
+
dispatcher.use({ $$typeof: REACT_RECOVERABLE_TYPE });
|
|
452
472
|
const fulfilledPromise = Promise.resolve(null);
|
|
453
473
|
Reflect.set(fulfilledPromise, "status", "fulfilled");
|
|
454
474
|
Reflect.set(fulfilledPromise, "value", null);
|
|
@@ -532,6 +552,19 @@ const parseHookName = (functionName: string | undefined): string => {
|
|
|
532
552
|
return functionName.slice(startIndex);
|
|
533
553
|
};
|
|
534
554
|
|
|
555
|
+
// Unlike upstream, bippy routes every dispatcher method through a shared log helper, so those
|
|
556
|
+
// frames appear in captured stacks and must not become tree levels. Names are read off the
|
|
557
|
+
// live functions rather than matched by prefix, so user hooks cannot collide and minified
|
|
558
|
+
// builds stay accurate.
|
|
559
|
+
const INTERNAL_HOOK_FRAME_NAMES = new Set(
|
|
560
|
+
[pushHookLogEntry, ...Object.values(dispatcher)].map((internalFunction) =>
|
|
561
|
+
parseHookName(internalFunction.name),
|
|
562
|
+
),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const isInternalHookFrame = (hookName: string): boolean =>
|
|
566
|
+
hookName !== "" && INTERNAL_HOOK_FRAME_NAMES.has(hookName);
|
|
567
|
+
|
|
535
568
|
const isReactWrapper = (functionName: string | undefined, wrapperName: string): boolean => {
|
|
536
569
|
const hookName = parseHookName(functionName);
|
|
537
570
|
if (wrapperName === "HostTransitionStatus") {
|
|
@@ -602,8 +635,10 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
|
|
|
602
635
|
const [primitiveFrame, stack] = parseTrimmedStack(rootStack, hook);
|
|
603
636
|
let displayName = hook.displayName;
|
|
604
637
|
if (displayName === null && primitiveFrame !== null) {
|
|
605
|
-
|
|
606
|
-
|
|
638
|
+
const primitiveName = parseHookName(primitiveFrame.functionName);
|
|
639
|
+
displayName = isInternalHookFrame(primitiveName)
|
|
640
|
+
? null
|
|
641
|
+
: primitiveName || parseHookName(hook.dispatcherHookName);
|
|
607
642
|
}
|
|
608
643
|
|
|
609
644
|
if (stack !== null) {
|
|
@@ -628,6 +663,7 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
|
|
|
628
663
|
name: parseHookName(stack[stackIndex - 1].functionName),
|
|
629
664
|
value: undefined,
|
|
630
665
|
subHooks: children,
|
|
666
|
+
debugInfo: null,
|
|
631
667
|
hookSource: {
|
|
632
668
|
lineNumber: stackFrame.lineNumber ?? null,
|
|
633
669
|
columnNumber: stackFrame.columnNumber ?? null,
|
|
@@ -655,21 +691,52 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
|
|
|
655
691
|
fileName: firstStackFrame?.fileName ?? null,
|
|
656
692
|
};
|
|
657
693
|
|
|
658
|
-
levelChildren.push({
|
|
694
|
+
levelChildren.push({
|
|
695
|
+
id,
|
|
696
|
+
isStateEditable,
|
|
697
|
+
name,
|
|
698
|
+
value: hook.value,
|
|
699
|
+
subHooks: [],
|
|
700
|
+
debugInfo: hook.debugInfo,
|
|
701
|
+
hookSource,
|
|
702
|
+
});
|
|
659
703
|
}
|
|
660
704
|
|
|
661
705
|
processDebugValues(rootChildren, null);
|
|
706
|
+
removeInternalHookFrames(rootChildren, null);
|
|
662
707
|
return rootChildren;
|
|
663
708
|
};
|
|
664
709
|
|
|
710
|
+
const removeInternalHookFrames = (
|
|
711
|
+
hooksTree: HooksTree,
|
|
712
|
+
parentHooksNode: HooksNode | null,
|
|
713
|
+
): void => {
|
|
714
|
+
for (let nodeIndex = 0; nodeIndex < hooksTree.length; nodeIndex++) {
|
|
715
|
+
const hooksNode = hooksTree[nodeIndex];
|
|
716
|
+
if (isInternalHookFrame(hooksNode.name)) {
|
|
717
|
+
if (parentHooksNode !== null && hooksNode.value !== undefined) {
|
|
718
|
+
if (parentHooksNode.value === undefined) parentHooksNode.value = hooksNode.value;
|
|
719
|
+
else if (Array.isArray(parentHooksNode.value)) {
|
|
720
|
+
parentHooksNode.value = [...parentHooksNode.value, hooksNode.value];
|
|
721
|
+
} else parentHooksNode.value = [parentHooksNode.value, hooksNode.value];
|
|
722
|
+
}
|
|
723
|
+
hooksTree.splice(nodeIndex, 1, ...hooksNode.subHooks);
|
|
724
|
+
nodeIndex--;
|
|
725
|
+
} else {
|
|
726
|
+
removeInternalHookFrames(hooksNode.subHooks, hooksNode);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
665
731
|
const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | null): void => {
|
|
666
732
|
const debugValueNodes: HooksNode[] = [];
|
|
667
733
|
for (let nodeIndex = 0; nodeIndex < hooksTree.length; nodeIndex++) {
|
|
668
734
|
const hooksNode = hooksTree[nodeIndex];
|
|
669
|
-
if (hooksNode.name === "DebugValue"
|
|
670
|
-
hooksTree.splice(nodeIndex, 1);
|
|
735
|
+
if (hooksNode.name === "DebugValue") {
|
|
736
|
+
hooksTree.splice(nodeIndex, 1, ...hooksNode.subHooks);
|
|
671
737
|
nodeIndex--;
|
|
672
|
-
|
|
738
|
+
// Internal frames surface as valueless DebugValue levels; only real labels bubble up.
|
|
739
|
+
if (hooksNode.value !== undefined) debugValueNodes.push(hooksNode);
|
|
673
740
|
} else {
|
|
674
741
|
processDebugValues(hooksNode.subHooks, hooksNode);
|
|
675
742
|
}
|
|
@@ -723,8 +790,8 @@ const resolveDefaultProps = (
|
|
|
723
790
|
baseProps: Record<string, unknown>,
|
|
724
791
|
): Record<string, unknown> => {
|
|
725
792
|
if (
|
|
726
|
-
component &&
|
|
727
|
-
typeof component === "object" &&
|
|
793
|
+
component !== null &&
|
|
794
|
+
(typeof component === "object" || typeof component === "function") &&
|
|
728
795
|
"defaultProps" in component &&
|
|
729
796
|
typeof component.defaultProps === "object" &&
|
|
730
797
|
component.defaultProps !== null
|
|
@@ -760,24 +827,29 @@ const restoreConsole = (originalMethods: Record<string, unknown>): void => {
|
|
|
760
827
|
}
|
|
761
828
|
};
|
|
762
829
|
|
|
763
|
-
|
|
830
|
+
// The render call must happen in the same frame that captures the ancestor stack,
|
|
831
|
+
// otherwise an extra frame lands in every hook's trimmed stack and becomes a bogus tree level.
|
|
832
|
+
const performDispatcherInspection = <TArgs extends unknown[]>(
|
|
764
833
|
dispatcherRef: RendererDispatcherRef,
|
|
765
|
-
|
|
834
|
+
renderFunction: (...args: TArgs) => unknown,
|
|
835
|
+
...renderArgs: TArgs
|
|
766
836
|
): HooksTree => {
|
|
767
837
|
const previousDispatcher = readDispatcher(dispatcherRef);
|
|
768
838
|
writeDispatcher(dispatcherRef, dispatcherProxy);
|
|
839
|
+
isInspectingHooks = true;
|
|
769
840
|
|
|
770
841
|
let capturedHookLog: HookLogEntry[] = [];
|
|
771
842
|
let ancestorStackError: Error | undefined;
|
|
772
843
|
|
|
773
844
|
try {
|
|
774
845
|
ancestorStackError = new Error();
|
|
775
|
-
|
|
846
|
+
renderFunction(...renderArgs);
|
|
776
847
|
} catch (renderError) {
|
|
777
848
|
handleRenderFunctionError(renderError);
|
|
778
849
|
} finally {
|
|
779
850
|
capturedHookLog = hookLog;
|
|
780
851
|
hookLog = [];
|
|
852
|
+
isInspectingHooks = false;
|
|
781
853
|
writeDispatcher(dispatcherRef, previousDispatcher);
|
|
782
854
|
}
|
|
783
855
|
|
|
@@ -785,8 +857,12 @@ const performDispatcherInspection = (
|
|
|
785
857
|
return buildTree(rootStack, capturedHookLog);
|
|
786
858
|
};
|
|
787
859
|
|
|
788
|
-
const requireDispatcherRef = (
|
|
789
|
-
|
|
860
|
+
const requireDispatcherRef = (
|
|
861
|
+
fiber?: Fiber,
|
|
862
|
+
target: ReactDevToolsTarget = globalThis,
|
|
863
|
+
): RendererDispatcherRef => {
|
|
864
|
+
const rendererDispatcherRef = fiber ? getRenderer(fiber, target)?.currentDispatcherRef : null;
|
|
865
|
+
const dispatcherRef = rendererDispatcherRef ?? getRendererDispatcherRefs(target)[0];
|
|
790
866
|
if (!dispatcherRef) {
|
|
791
867
|
throw new BippyHookInspectionError(
|
|
792
868
|
"Bippy couldn’t find a React renderer. Load React and install Bippy’s hook.",
|
|
@@ -823,8 +899,75 @@ const resolveContextDependency = (fiber: Fiber): void => {
|
|
|
823
899
|
}
|
|
824
900
|
};
|
|
825
901
|
|
|
902
|
+
const normalizeStandaloneHooks = (hooksTree: HooksTree): HooksTree => {
|
|
903
|
+
const normalizeNode = (hooksNode: HooksNode): HooksNode => {
|
|
904
|
+
const subHooks = hooksNode.subHooks.map(normalizeNode);
|
|
905
|
+
if (subHooks.length === 1) {
|
|
906
|
+
const childHook = subHooks[0];
|
|
907
|
+
if (hooksNode.name === "Context" && childHook.id === null) {
|
|
908
|
+
return { ...childHook, hookSource: hooksNode.hookSource };
|
|
909
|
+
}
|
|
910
|
+
if (hooksNode.name === "FormStatus" && childHook.name === "HostTransitionStatus") {
|
|
911
|
+
return { ...hooksNode, subHooks: [], value: childHook.value };
|
|
912
|
+
}
|
|
913
|
+
if (hooksNode.name === "Use") {
|
|
914
|
+
if (childHook.name === "Context") {
|
|
915
|
+
return { ...childHook, hookSource: hooksNode.hookSource };
|
|
916
|
+
}
|
|
917
|
+
if (childHook.name === "Promise" || childHook.name === "Unresolved") {
|
|
918
|
+
return {
|
|
919
|
+
...hooksNode,
|
|
920
|
+
debugInfo: childHook.debugInfo,
|
|
921
|
+
subHooks: [],
|
|
922
|
+
value: childHook.value,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (
|
|
927
|
+
hooksNode.id === null &&
|
|
928
|
+
hooksNode.value === undefined &&
|
|
929
|
+
(hooksNode.name === childHook.name || hooksNode.name === "<anonymous>")
|
|
930
|
+
) {
|
|
931
|
+
return { ...childHook, hookSource: hooksNode.hookSource };
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return { ...hooksNode, subHooks };
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
return hooksTree.map(normalizeNode);
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
export const inspectHooks = (
|
|
941
|
+
renderFunction: (props: Record<string, unknown>) => unknown,
|
|
942
|
+
props: Record<string, unknown>,
|
|
943
|
+
target: ReactDevToolsTarget = globalThis,
|
|
944
|
+
): HooksTree => {
|
|
945
|
+
assertNotInspectingHooks();
|
|
946
|
+
const dispatcherRef = requireDispatcherRef(undefined, target);
|
|
947
|
+
getPrimitiveStackCache();
|
|
948
|
+
currentHook = null;
|
|
949
|
+
currentFiber = null;
|
|
950
|
+
currentContextDependency = null;
|
|
951
|
+
currentThenableState = null;
|
|
952
|
+
currentThenableIndex = 0;
|
|
953
|
+
const originalConsoleMethods = suppressConsole();
|
|
954
|
+
try {
|
|
955
|
+
return normalizeStandaloneHooks(
|
|
956
|
+
performDispatcherInspection(dispatcherRef, renderFunction, props),
|
|
957
|
+
);
|
|
958
|
+
} finally {
|
|
959
|
+
currentHook = null;
|
|
960
|
+
currentFiber = null;
|
|
961
|
+
currentContextDependency = null;
|
|
962
|
+
currentThenableState = null;
|
|
963
|
+
currentThenableIndex = 0;
|
|
964
|
+
restoreConsole(originalConsoleMethods);
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
826
968
|
export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
827
|
-
|
|
969
|
+
assertNotInspectingHooks();
|
|
970
|
+
const dispatcherRef = requireDispatcherRef(fiber);
|
|
828
971
|
const workTags = getReactWorkTagsForFiber(fiber);
|
|
829
972
|
|
|
830
973
|
if (
|
|
@@ -839,6 +982,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
839
982
|
|
|
840
983
|
currentHook = fiber.memoizedState;
|
|
841
984
|
currentFiber = fiber;
|
|
985
|
+
currentMemoCacheIndex = 0;
|
|
842
986
|
|
|
843
987
|
const debugThenableState = fiber.dependencies?._debugThenableState;
|
|
844
988
|
const usedThenables = Array.isArray(debugThenableState)
|
|
@@ -847,18 +991,16 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
847
991
|
currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
|
|
848
992
|
currentThenableIndex = 0;
|
|
849
993
|
|
|
850
|
-
resolveContextDependency(fiber);
|
|
851
|
-
|
|
852
|
-
const type = fiber.type;
|
|
853
|
-
let props = fiber.memoizedProps;
|
|
854
|
-
if (type !== fiber.elementType) {
|
|
855
|
-
props = resolveDefaultProps(type, props);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
994
|
const originalConsoleMethods = suppressConsole();
|
|
859
995
|
const contextMap = new Map<ReactContext<unknown>, unknown>();
|
|
860
996
|
|
|
861
997
|
try {
|
|
998
|
+
resolveContextDependency(fiber);
|
|
999
|
+
const type = fiber.type;
|
|
1000
|
+
const props =
|
|
1001
|
+
type !== fiber.elementType
|
|
1002
|
+
? resolveDefaultProps(type, fiber.memoizedProps)
|
|
1003
|
+
: fiber.memoizedProps;
|
|
862
1004
|
if (
|
|
863
1005
|
currentContextDependency !== null &&
|
|
864
1006
|
!Object.hasOwn(currentContextDependency, "memoizedValue")
|
|
@@ -870,9 +1012,15 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
870
1012
|
if (!isForwardRefRenderType(type)) {
|
|
871
1013
|
throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
|
|
872
1014
|
}
|
|
873
|
-
|
|
874
|
-
type.render
|
|
875
|
-
|
|
1015
|
+
const hooksTree = normalizeStandaloneHooks(
|
|
1016
|
+
performDispatcherInspection(dispatcherRef, type.render, props, fiber.ref),
|
|
1017
|
+
);
|
|
1018
|
+
for (const hooksNode of hooksTree) {
|
|
1019
|
+
if (hooksNode.hookSource?.functionName === "Object.render") {
|
|
1020
|
+
hooksNode.hookSource.functionName = null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return hooksTree;
|
|
876
1024
|
}
|
|
877
1025
|
|
|
878
1026
|
if (typeof type !== "function") {
|
|
@@ -880,15 +1028,14 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
|
|
|
880
1028
|
"Function component fiber is missing its component function.",
|
|
881
1029
|
);
|
|
882
1030
|
}
|
|
883
|
-
return performDispatcherInspection(dispatcherRef,
|
|
884
|
-
type(props);
|
|
885
|
-
});
|
|
1031
|
+
return normalizeStandaloneHooks(performDispatcherInspection(dispatcherRef, type, props));
|
|
886
1032
|
} finally {
|
|
887
1033
|
currentFiber = null;
|
|
888
1034
|
currentHook = null;
|
|
889
1035
|
currentContextDependency = null;
|
|
890
1036
|
currentThenableState = null;
|
|
891
1037
|
currentThenableIndex = 0;
|
|
1038
|
+
currentMemoCacheIndex = 0;
|
|
892
1039
|
restoreContexts(contextMap);
|
|
893
1040
|
restoreConsole(originalConsoleMethods);
|
|
894
1041
|
}
|
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
} from "../react-internals/index.js";
|
|
8
8
|
import {
|
|
9
9
|
REACT_STACK_BOTTOM_FRAME_PATTERNS,
|
|
10
|
-
SERVER_FRAME_MARKER,
|
|
11
10
|
SERVER_ENV_PATTERN,
|
|
12
11
|
SERVER_COMPONENT_URL_PREFIXES,
|
|
13
12
|
} from "./constants.js";
|
|
@@ -19,7 +18,11 @@ import {
|
|
|
19
18
|
readDispatcher,
|
|
20
19
|
writeDispatcher,
|
|
21
20
|
} from "./renderer-dispatchers.js";
|
|
22
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
symbolicateStack,
|
|
23
|
+
type SourceFetch,
|
|
24
|
+
type SourceMapRequestOptions,
|
|
25
|
+
} from "./symbolication.js";
|
|
23
26
|
|
|
24
27
|
interface RendererDispatcherSnapshot {
|
|
25
28
|
currentDispatcherRef: RendererDispatcherRef;
|
|
@@ -101,12 +104,18 @@ const describeBuiltInComponentFrame = (name: string): string => {
|
|
|
101
104
|
return `\n in ${name}`;
|
|
102
105
|
};
|
|
103
106
|
|
|
104
|
-
export const describeDebugInfoFrame = (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
export const describeDebugInfoFrame = (
|
|
108
|
+
name: string,
|
|
109
|
+
env?: string,
|
|
110
|
+
location?: Error | null,
|
|
111
|
+
): string => {
|
|
112
|
+
if (location) {
|
|
113
|
+
const childStack = formatOwnerStack(location.stack ?? "");
|
|
114
|
+
const lastNewlineIndex = childStack.lastIndexOf("\n");
|
|
115
|
+
const lastLine = lastNewlineIndex === -1 ? childStack : childStack.slice(lastNewlineIndex + 1);
|
|
116
|
+
if (lastLine.includes(name)) return `\n${lastLine}`;
|
|
108
117
|
}
|
|
109
|
-
return
|
|
118
|
+
return describeBuiltInComponentFrame(`${name}${env ? ` [${env}]` : ""}`);
|
|
110
119
|
};
|
|
111
120
|
|
|
112
121
|
let reEntry = false;
|
|
@@ -115,7 +124,7 @@ let reEntry = false;
|
|
|
115
124
|
// component type like React DevTools does.
|
|
116
125
|
const componentFrameCache = new WeakMap<React.ComponentType<unknown>, string>();
|
|
117
126
|
|
|
118
|
-
// https://github.com/facebook/react/blob/
|
|
127
|
+
// https://github.com/facebook/react/blob/eafeac0/packages/shared/ReactComponentStackFrame.js#L64
|
|
119
128
|
const describeNativeComponentFrame = (
|
|
120
129
|
component: React.ComponentType<unknown>,
|
|
121
130
|
construct: boolean,
|
|
@@ -153,21 +162,12 @@ const describeNativeComponentFrame = (
|
|
|
153
162
|
throw Error();
|
|
154
163
|
},
|
|
155
164
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
control = caughtError;
|
|
161
|
-
}
|
|
162
|
-
Reflect.construct(component, [], ThrowingConstructor);
|
|
163
|
-
} else {
|
|
164
|
-
try {
|
|
165
|
-
Function.prototype.apply.call(ThrowingConstructor, undefined, []);
|
|
166
|
-
} catch (caughtError) {
|
|
167
|
-
control = caughtError;
|
|
168
|
-
}
|
|
169
|
-
Function.prototype.apply.call(component, ThrowingConstructor.prototype, []);
|
|
165
|
+
try {
|
|
166
|
+
Reflect.construct(ThrowingConstructor, []);
|
|
167
|
+
} catch (caughtError) {
|
|
168
|
+
control = caughtError;
|
|
170
169
|
}
|
|
170
|
+
Reflect.construct(component, [], ThrowingConstructor);
|
|
171
171
|
} else {
|
|
172
172
|
try {
|
|
173
173
|
throw Error();
|
|
@@ -312,7 +312,7 @@ const describeNativeComponentFrame = (
|
|
|
312
312
|
return syntheticFrame;
|
|
313
313
|
};
|
|
314
314
|
|
|
315
|
-
// https://github.com/facebook/react/blob/
|
|
315
|
+
// https://github.com/facebook/react/blob/eafeac0/packages/react-reconciler/src/ReactFiberComponentStack.js#L37
|
|
316
316
|
export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
|
|
317
317
|
let stackFrame = "";
|
|
318
318
|
const workTags = getReactWorkTagsForFiber(fiber);
|
|
@@ -384,7 +384,11 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
384
384
|
for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
|
|
385
385
|
const debugEntry = debugInfo[debugInfoIndex];
|
|
386
386
|
if (typeof debugEntry.name === "string") {
|
|
387
|
-
componentStack += describeDebugInfoFrame(
|
|
387
|
+
componentStack += describeDebugInfoFrame(
|
|
388
|
+
debugEntry.name,
|
|
389
|
+
debugEntry.env,
|
|
390
|
+
debugEntry.debugLocation,
|
|
391
|
+
);
|
|
388
392
|
}
|
|
389
393
|
}
|
|
390
394
|
}
|
|
@@ -511,7 +515,7 @@ const getEnrichedServerStackFrame = (
|
|
|
511
515
|
lineNumber: resolvedRscFrame.lineNumber,
|
|
512
516
|
columnNumber: resolvedRscFrame.columnNumber,
|
|
513
517
|
source: serverFrame.source?.replace(
|
|
514
|
-
|
|
518
|
+
SERVER_ENV_PATTERN,
|
|
515
519
|
`(${resolvedRscFrame.fileName}:${resolvedRscFrame.lineNumber}:${resolvedRscFrame.columnNumber})`,
|
|
516
520
|
),
|
|
517
521
|
};
|
|
@@ -534,7 +538,7 @@ const markFlightServerFrame = (stackFrame: StackFrame): StackFrame =>
|
|
|
534
538
|
* carries `.debugStack`). This is exact - no re-invoking components, no
|
|
535
539
|
* name-matching heuristics - but requires React 19.
|
|
536
540
|
*/
|
|
537
|
-
const
|
|
541
|
+
export const getRawOwnerStack = (fiber: Fiber): StackFrame[] => {
|
|
538
542
|
const ownerStackFrames: StackFrame[] = [];
|
|
539
543
|
let owner: Fiber | ServerComponentInfo | null | undefined = fiber;
|
|
540
544
|
while (owner) {
|
|
@@ -625,9 +629,11 @@ export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
|
|
|
625
629
|
*/
|
|
626
630
|
export const getParentStack = async (
|
|
627
631
|
fiber: Fiber,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
632
|
+
shouldUseCache = true,
|
|
633
|
+
sourceFetch?: SourceFetch,
|
|
634
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
635
|
+
): Promise<StackFrame[]> =>
|
|
636
|
+
symbolicateStack(getRawParentStack(fiber), shouldUseCache, sourceFetch, requestOptions);
|
|
631
637
|
|
|
632
638
|
// an owner frame is only actionable if it can point an editor somewhere:
|
|
633
639
|
// it needs a file location and must not be ignore-listed bundler/framework code
|
|
@@ -646,10 +652,11 @@ const isLocatableFrame = (stackFrame: StackFrame): boolean =>
|
|
|
646
652
|
*/
|
|
647
653
|
export const getOwnerStack = async (
|
|
648
654
|
fiber: Fiber,
|
|
649
|
-
|
|
650
|
-
|
|
655
|
+
shouldUseCache = true,
|
|
656
|
+
sourceFetch?: SourceFetch,
|
|
657
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
651
658
|
): Promise<StackFrame[]> => {
|
|
652
|
-
const debugStackFrames =
|
|
659
|
+
const debugStackFrames = getRawOwnerStack(fiber);
|
|
653
660
|
if (debugStackFrames.length > 0) {
|
|
654
661
|
// the owner chain does not include the fiber itself, but bippy's stacks
|
|
655
662
|
// always start with the fiber's own frame
|
|
@@ -657,8 +664,9 @@ export const getOwnerStack = async (
|
|
|
657
664
|
selfFrame.functionName = getDisplayName(fiber.type) ?? selfFrame.functionName;
|
|
658
665
|
const symbolicatedFrames = await symbolicateStack(
|
|
659
666
|
[selfFrame, ...debugStackFrames],
|
|
660
|
-
|
|
661
|
-
|
|
667
|
+
shouldUseCache,
|
|
668
|
+
sourceFetch,
|
|
669
|
+
requestOptions,
|
|
662
670
|
);
|
|
663
671
|
const hasLocatableOwnerFrame = symbolicatedFrames.some(
|
|
664
672
|
(stackFrame, frameIndex) => frameIndex > 0 && isLocatableFrame(stackFrame),
|
|
@@ -668,5 +676,5 @@ export const getOwnerStack = async (
|
|
|
668
676
|
}
|
|
669
677
|
}
|
|
670
678
|
|
|
671
|
-
return getParentStack(fiber,
|
|
679
|
+
return getParentStack(fiber, shouldUseCache, sourceFetch, requestOptions);
|
|
672
680
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
|
|
2
2
|
import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
|
|
3
|
-
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
3
|
+
import { parseStack, type StackFrame } from "./parse-stack.js";
|
|
4
4
|
|
|
5
5
|
interface V8CallSite {
|
|
6
6
|
getFunctionName?: () => string | null;
|