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

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.
@@ -1,15 +1,22 @@
1
- import type { Fiber, ContextDependency, MemoizedState, ReactContext } from "../types.js";
1
+ import type {
2
+ Fiber,
3
+ ContextDependency,
4
+ MemoizedState,
5
+ ReactContext,
6
+ RendererDispatcherRef,
7
+ } from "../types.js";
8
+ import {
9
+ BippyHookInspectionError,
10
+ BippyHookRenderError,
11
+ BippyUnsupportedHookError,
12
+ } from "../errors.js";
13
+ import { getReactWorkTagsForFiber } from "../react-internals.js";
2
14
  import { parseStack, type StackFrame } from "./parse-stack.js";
3
15
  import { getRDTHook, _renderers } from "../rdt-hook.js";
4
16
 
5
17
  const REACT_CONTEXT_TYPE = Symbol.for("react.context");
6
18
  const REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
7
19
 
8
- const FUNCTION_COMPONENT_TAG = 0;
9
- const CONTEXT_PROVIDER_TAG = 10;
10
- const FORWARD_REF_TAG = 11;
11
- const SIMPLE_MEMO_COMPONENT_TAG = 15;
12
-
13
20
  export interface HookSource {
14
21
  lineNumber: number | null;
15
22
  columnNumber: number | null;
@@ -26,8 +33,7 @@ export interface HooksNode {
26
33
  hookSource: HookSource | null;
27
34
  }
28
35
 
29
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
30
- export interface HooksTree extends Array<HooksNode> {}
36
+ export type HooksTree = HooksNode[];
31
37
 
32
38
  interface HookLogEntry {
33
39
  displayName: string | null;
@@ -37,10 +43,29 @@ interface HookLogEntry {
37
43
  dispatcherHookName: string;
38
44
  }
39
45
 
40
- interface DispatcherRefContainer {
41
- H?: unknown;
42
- current?: unknown;
43
- [key: string]: unknown;
46
+ interface InspectableReactContext {
47
+ _currentValue: unknown;
48
+ displayName?: string;
49
+ }
50
+
51
+ interface InspectableRef {
52
+ current: unknown;
53
+ }
54
+
55
+ interface InspectableThenable {
56
+ reason?: unknown;
57
+ status?: unknown;
58
+ then: (...arguments_: unknown[]) => unknown;
59
+ value?: unknown;
60
+ }
61
+
62
+ interface ForwardRefRenderType {
63
+ render: (props: Record<string, unknown>, ref: unknown) => unknown;
64
+ }
65
+
66
+ interface InspectedActionState {
67
+ error: unknown;
68
+ value: unknown;
44
69
  }
45
70
 
46
71
  let hookLog: HookLogEntry[] = [];
@@ -49,8 +74,7 @@ let currentFiber: Fiber | null = null;
49
74
  let currentHook: MemoizedState | null = null;
50
75
  let currentContextDependency: ContextDependency<unknown> | null = null;
51
76
  let currentThenableIndex = 0;
52
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
- let currentThenableState: any[] | null = null;
77
+ let currentThenableState: unknown[] | null = null;
54
78
 
55
79
  const SuspenseException: unknown = new Error(
56
80
  "Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render.",
@@ -65,33 +89,51 @@ const nextHook = (): MemoizedState | null => {
65
89
  return hook;
66
90
  };
67
91
 
68
- const readContext = <T>(context: ReactContext<T>): T => {
92
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
93
+ typeof value === "object" && value !== null;
94
+
95
+ const isInspectableThenable = (value: unknown): value is InspectableThenable =>
96
+ isObjectRecord(value) && typeof value.then === "function";
97
+
98
+ const isInspectableRef = (value: unknown): value is InspectableRef =>
99
+ isObjectRecord(value) && "current" in value;
100
+
101
+ const isReactContext = (value: unknown): value is ReactContext<unknown> =>
102
+ isObjectRecord(value) && "_currentValue" in value;
103
+
104
+ const isContextDependency = (value: unknown): value is ContextDependency<unknown> =>
105
+ isObjectRecord(value) && "context" in value && "next" in value;
106
+
107
+ const isForwardRefRenderType = (value: unknown): value is ForwardRefRenderType =>
108
+ isObjectRecord(value) && typeof value.render === "function";
109
+
110
+ const readContext = (context: InspectableReactContext): unknown => {
69
111
  if (currentFiber === null) return context._currentValue;
70
112
  if (currentContextDependency === null) {
71
- throw new Error("Context reads do not line up with context dependencies.");
113
+ throw new BippyHookInspectionError("Context reads do not line up with context dependencies.");
72
114
  }
73
- if (Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")) {
74
- const value = currentContextDependency.memoizedValue as T;
115
+ if (Object.hasOwn(currentContextDependency, "memoizedValue")) {
116
+ const value = currentContextDependency.memoizedValue;
75
117
  currentContextDependency = currentContextDependency.next;
76
118
  return value;
77
119
  }
78
120
  return context._currentValue;
79
121
  };
80
122
 
81
- const getDispatcherRef = (): DispatcherRefContainer | null => {
123
+ const getDispatcherRef = (): RendererDispatcherRef | null => {
82
124
  const rdtHook = getRDTHook();
83
125
  const allRenderers = [..._renderers, ...rdtHook.renderers.values()];
84
126
  for (const renderer of allRenderers) {
85
127
  const ref = renderer.currentDispatcherRef;
86
- if (ref && typeof ref === "object") return ref as DispatcherRefContainer;
128
+ if (ref) return ref;
87
129
  }
88
130
  return null;
89
131
  };
90
132
 
91
- const getDispatcherFromRef = (ref: DispatcherRefContainer): unknown =>
133
+ const getDispatcherFromRef = (ref: RendererDispatcherRef): unknown =>
92
134
  "H" in ref ? ref.H : ref.current;
93
135
 
94
- const setDispatcherOnRef = (ref: DispatcherRefContainer, dispatcher: unknown): void => {
136
+ const setDispatcherOnRef = (ref: RendererDispatcherRef, dispatcher: unknown): void => {
95
137
  if ("H" in ref) {
96
138
  ref.H = dispatcher;
97
139
  } else {
@@ -115,14 +157,13 @@ const pushHookLogEntry = (
115
157
  };
116
158
 
117
159
  const dispatcherUse = (usable: unknown): unknown => {
118
- if (usable !== null && typeof usable === "object") {
119
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
- const asThenable = usable as any;
121
- if (typeof asThenable.then === "function") {
122
- const thenable =
160
+ if (isObjectRecord(usable)) {
161
+ if (isInspectableThenable(usable)) {
162
+ const cachedThenable =
123
163
  currentThenableState !== null && currentThenableIndex < currentThenableState.length
124
164
  ? currentThenableState[currentThenableIndex++]
125
- : asThenable;
165
+ : usable;
166
+ const thenable = isInspectableThenable(cachedThenable) ? cachedThenable : usable;
126
167
 
127
168
  switch (thenable.status) {
128
169
  case "fulfilled": {
@@ -135,19 +176,22 @@ const dispatcherUse = (usable: unknown): unknown => {
135
176
  pushHookLogEntry("Unresolved", thenable, "Use");
136
177
  throw SuspenseException;
137
178
  }
138
- if (asThenable.$$typeof === REACT_CONTEXT_TYPE && "_currentValue" in asThenable) {
139
- const context: ReactContext<unknown> = asThenable;
179
+ if (usable.$$typeof === REACT_CONTEXT_TYPE && "_currentValue" in usable) {
180
+ const context: InspectableReactContext = {
181
+ _currentValue: usable._currentValue,
182
+ displayName: typeof usable.displayName === "string" ? usable.displayName : undefined,
183
+ };
140
184
  const value = readContext(context);
141
- pushHookLogEntry("Context (use)", value, "Use", context.displayName || "Context");
185
+ pushHookLogEntry("Context (use)", value, "Use", context.displayName ?? "Context");
142
186
  return value;
143
187
  }
144
188
  }
145
- throw new Error("An unsupported type was passed to use(): " + String(usable));
189
+ throw new BippyHookInspectionError("An unsupported type was passed to use(): " + String(usable));
146
190
  };
147
191
 
148
- const dispatcherUseContext = (context: ReactContext<unknown>): unknown => {
192
+ const dispatcherUseContext = (context: InspectableReactContext): unknown => {
149
193
  const value = readContext(context);
150
- pushHookLogEntry("Context", value, "Context", context.displayName || null);
194
+ pushHookLogEntry("Context", value, "Context", context.displayName ?? null);
151
195
  return value;
152
196
  };
153
197
 
@@ -157,7 +201,7 @@ const dispatcherUseState = (initialState: unknown): [unknown, () => void] => {
157
201
  hook !== null
158
202
  ? hook.memoizedState
159
203
  : typeof initialState === "function"
160
- ? (initialState as () => unknown)()
204
+ ? initialState()
161
205
  : initialState;
162
206
  pushHookLogEntry("State", state, "State");
163
207
  return [state, () => {}];
@@ -175,11 +219,13 @@ const dispatcherUseReducer = (
175
219
  return [state, () => {}];
176
220
  };
177
221
 
178
- const dispatcherUseRef = (initialValue: unknown): { current: unknown } => {
222
+ const dispatcherUseRef = (initialValue: unknown): InspectableRef => {
179
223
  const hook = nextHook();
180
- const ref = hook !== null ? hook.memoizedState : { current: initialValue };
181
- pushHookLogEntry("Ref", (ref as { current: unknown }).current, "Ref");
182
- return ref as { current: unknown };
224
+ const ref = isInspectableRef(hook?.memoizedState)
225
+ ? hook.memoizedState
226
+ : { current: initialValue };
227
+ pushHookLogEntry("Ref", ref.current, "Ref");
228
+ return ref;
183
229
  };
184
230
 
185
231
  const dispatcherUseCacheRefresh = (): (() => void) => {
@@ -227,7 +273,7 @@ const dispatcherUseCallback = (callback: unknown): unknown => {
227
273
  const hook = nextHook();
228
274
  pushHookLogEntry(
229
275
  "Callback",
230
- hook !== null ? (hook.memoizedState as unknown[])[0] : callback,
276
+ Array.isArray(hook?.memoizedState) ? hook.memoizedState[0] : callback,
231
277
  "Callback",
232
278
  );
233
279
  return callback;
@@ -235,7 +281,7 @@ const dispatcherUseCallback = (callback: unknown): unknown => {
235
281
 
236
282
  const dispatcherUseMemo = (nextCreate: () => unknown): unknown => {
237
283
  const hook = nextHook();
238
- const value = hook !== null ? (hook.memoizedState as unknown[])[0] : nextCreate();
284
+ const value = Array.isArray(hook?.memoizedState) ? hook.memoizedState[0] : nextCreate();
239
285
  pushHookLogEntry("Memo", value, "Memo");
240
286
  return value;
241
287
  };
@@ -254,7 +300,7 @@ const dispatcherUseSyncExternalStore = (
254
300
  const dispatcherUseTransition = (): [boolean, () => void] => {
255
301
  const stateHook = nextHook();
256
302
  nextHook();
257
- const isPending = stateHook !== null ? (stateHook.memoizedState as boolean) : false;
303
+ const isPending = stateHook?.memoizedState === true;
258
304
  pushHookLogEntry("Transition", isPending, "Transition");
259
305
  return [isPending, () => {}];
260
306
  };
@@ -268,7 +314,7 @@ const dispatcherUseDeferredValue = (value: unknown): unknown => {
268
314
 
269
315
  const dispatcherUseId = (): string => {
270
316
  const hook = nextHook();
271
- const identifier = hook !== null ? (hook.memoizedState as string) : "";
317
+ const identifier = typeof hook?.memoizedState === "string" ? hook.memoizedState : "";
272
318
  pushHookLogEntry("Id", identifier, "Id");
273
319
  return identifier;
274
320
  };
@@ -277,9 +323,7 @@ const dispatcherUseMemoCache = (size: number): unknown[] => {
277
323
  const fiber = currentFiber;
278
324
  if (fiber === null || fiber === undefined) return [];
279
325
 
280
- const memoCache = (
281
- fiber.updateQueue as { memoCache?: { data: unknown[][]; index: number } } | null
282
- )?.memoCache;
326
+ const memoCache = fiber.updateQueue?.memoCache;
283
327
  if (memoCache === null || memoCache === undefined) return [];
284
328
 
285
329
  let memoCacheSlots = memoCache.data[memoCache.index];
@@ -304,28 +348,22 @@ const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] =>
304
348
  const inspectActionStateHook = (
305
349
  hook: MemoizedState | null,
306
350
  initialState: unknown,
307
- ): { value: unknown; error: unknown } => {
351
+ ): InspectedActionState => {
308
352
  let value: unknown;
309
353
  let error: unknown = null;
310
354
  if (hook !== null) {
311
355
  const actionResult = hook.memoizedState;
312
- if (
313
- typeof actionResult === "object" &&
314
- actionResult !== null &&
315
- "then" in actionResult &&
316
- typeof actionResult.then === "function"
317
- ) {
318
- const thenable = actionResult as { status?: string; value?: unknown; reason?: unknown };
319
- switch (thenable.status) {
356
+ if (isInspectableThenable(actionResult)) {
357
+ switch (actionResult.status) {
320
358
  case "fulfilled":
321
- value = thenable.value;
359
+ value = actionResult.value;
322
360
  break;
323
361
  case "rejected":
324
- error = thenable.reason;
362
+ error = actionResult.reason;
325
363
  break;
326
364
  default:
327
365
  error = SuspenseException;
328
- value = thenable;
366
+ value = actionResult;
329
367
  }
330
368
  } else {
331
369
  value = actionResult;
@@ -360,7 +398,7 @@ const dispatcherUseFormState = createActionStateDispatcher("FormState");
360
398
 
361
399
  const dispatcherUseHostTransitionStatus = (): unknown => {
362
400
  // HACK: creating a minimal fake context because useHostTransitionStatus reads from an internal context not available outside React
363
- const status = readContext({ _currentValue: null } as unknown as ReactContext<unknown>);
401
+ const status = readContext({ _currentValue: null });
364
402
  pushHookLogEntry("HostTransitionStatus", status, "HostTransitionStatus");
365
403
  return status;
366
404
  };
@@ -371,8 +409,7 @@ const dispatcherUseEffectEvent = (callback: (...args: unknown[]) => unknown): ty
371
409
  return callback;
372
410
  };
373
411
 
374
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
375
- const Dispatcher: Record<string, (...args: any[]) => any> = {
412
+ const dispatcher = {
376
413
  readContext,
377
414
  use: dispatcherUse,
378
415
  useCallback: dispatcherUseCallback,
@@ -399,15 +436,13 @@ const Dispatcher: Record<string, (...args: any[]) => any> = {
399
436
  useEffectEvent: dispatcherUseEffectEvent,
400
437
  };
401
438
 
402
- const DispatcherProxy =
439
+ const dispatcherProxy =
403
440
  typeof Proxy === "undefined"
404
- ? Dispatcher
405
- : new Proxy(Dispatcher, {
406
- get(target, prop: string) {
407
- if (Object.prototype.hasOwnProperty.call(target, prop)) return target[prop];
408
- const error = new Error("Missing method in Dispatcher: " + prop);
409
- error.name = "ReactDebugToolsUnsupportedHookError";
410
- throw error;
441
+ ? dispatcher
442
+ : new Proxy(dispatcher, {
443
+ get(target, propertyName: string) {
444
+ if (Object.hasOwn(target, propertyName)) return Reflect.get(target, propertyName);
445
+ throw new BippyUnsupportedHookError("Missing method in Dispatcher: " + propertyName);
411
446
  },
412
447
  });
413
448
 
@@ -418,41 +453,42 @@ const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
418
453
  let capturedHookLog: HookLogEntry[];
419
454
 
420
455
  try {
421
- Dispatcher.useContext({ _currentValue: null });
422
- Dispatcher.useState(null);
423
- Dispatcher.useReducer((state: unknown) => state, null);
424
- Dispatcher.useRef(null);
425
- if (typeof Dispatcher.useCacheRefresh === "function") Dispatcher.useCacheRefresh();
426
- Dispatcher.useLayoutEffect(() => {});
427
- Dispatcher.useInsertionEffect(() => {});
428
- Dispatcher.useEffect(() => {});
429
- Dispatcher.useImperativeHandle(undefined, () => null);
430
- Dispatcher.useDebugValue(null);
431
- Dispatcher.useCallback(() => {});
432
- Dispatcher.useTransition();
433
- Dispatcher.useSyncExternalStore(
456
+ dispatcher.useContext({ _currentValue: null });
457
+ dispatcher.useState(null);
458
+ dispatcher.useReducer((state: unknown) => state, null);
459
+ dispatcher.useRef(null);
460
+ if (typeof dispatcher.useCacheRefresh === "function") dispatcher.useCacheRefresh();
461
+ dispatcher.useLayoutEffect(() => {});
462
+ dispatcher.useInsertionEffect(() => {});
463
+ dispatcher.useEffect(() => {});
464
+ dispatcher.useImperativeHandle(undefined, () => null);
465
+ dispatcher.useDebugValue(null);
466
+ dispatcher.useCallback(() => {});
467
+ dispatcher.useTransition();
468
+ dispatcher.useSyncExternalStore(
434
469
  () => () => {},
435
470
  () => null,
436
471
  () => null,
437
472
  );
438
- Dispatcher.useDeferredValue(null);
439
- Dispatcher.useMemo(() => null);
440
- Dispatcher.useOptimistic(null, (state: unknown) => state);
441
- Dispatcher.useFormState((state: unknown) => state, null);
442
- Dispatcher.useActionState((state: unknown) => state, null);
443
- Dispatcher.useHostTransitionStatus();
444
- if (typeof Dispatcher.useMemoCache === "function") Dispatcher.useMemoCache(0);
445
- if (typeof Dispatcher.use === "function") {
446
- Dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
447
- Dispatcher.use({ then() {}, status: "fulfilled", value: null });
473
+ dispatcher.useDeferredValue(null);
474
+ dispatcher.useMemo(() => null);
475
+ dispatcher.useOptimistic(null, (state: unknown) => state);
476
+ dispatcher.useFormState((state: unknown) => state, null);
477
+ dispatcher.useActionState((state: unknown) => state, null);
478
+ dispatcher.useHostTransitionStatus();
479
+ if (typeof dispatcher.useMemoCache === "function") dispatcher.useMemoCache(0);
480
+ if (typeof dispatcher.use === "function") {
481
+ dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
482
+ const fulfilledPromise = Promise.resolve(null);
483
+ Reflect.set(fulfilledPromise, "status", "fulfilled");
484
+ Reflect.set(fulfilledPromise, "value", null);
485
+ dispatcher.use(fulfilledPromise);
448
486
  try {
449
- Dispatcher.use({ then() {} });
450
- } catch {
451
- /* noop */
452
- }
487
+ dispatcher.use(new Promise<never>(() => {}));
488
+ } catch {}
453
489
  }
454
- Dispatcher.useId();
455
- if (typeof Dispatcher.useEffectEvent === "function") Dispatcher.useEffectEvent(() => {});
490
+ dispatcher.useId();
491
+ if (typeof dispatcher.useEffectEvent === "function") dispatcher.useEffectEvent(() => {});
456
492
  } finally {
457
493
  capturedHookLog = hookLog;
458
494
  hookLog = [];
@@ -681,14 +717,17 @@ const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | n
681
717
  const setupContexts = (contextMap: Map<ReactContext<unknown>, unknown>, fiber: Fiber): void => {
682
718
  let current: Fiber | null = fiber;
683
719
  while (current) {
684
- if (current.tag === CONTEXT_PROVIDER_TAG) {
685
- let context = current.type as ReactContext<unknown>;
686
- if ("_context" in context && context._context !== undefined) {
687
- context = context._context as ReactContext<unknown>;
688
- }
689
- if (!contextMap.has(context)) {
720
+ if (current.tag === getReactWorkTagsForFiber(current).ContextProvider) {
721
+ const providerType = current.type;
722
+ const nestedContext = isObjectRecord(providerType) ? providerType._context : undefined;
723
+ const context = isReactContext(nestedContext)
724
+ ? nestedContext
725
+ : isReactContext(providerType)
726
+ ? providerType
727
+ : null;
728
+ if (context && !contextMap.has(context)) {
690
729
  contextMap.set(context, context._currentValue);
691
- context._currentValue = (current.memoizedProps as { value: unknown }).value;
730
+ context._currentValue = current.memoizedProps.value;
692
731
  }
693
732
  }
694
733
  current = current.return;
@@ -703,25 +742,22 @@ const restoreContexts = (contextMap: Map<ReactContext<unknown>, unknown>): void
703
742
 
704
743
  const handleRenderFunctionError = (error: unknown): void => {
705
744
  if (error === SuspenseException) return;
706
- if (error instanceof Error && error.name === "ReactDebugToolsUnsupportedHookError") throw error;
707
- const wrapperError = new Error("Error rendering inspected component", { cause: error });
708
- wrapperError.name = "ReactDebugToolsRenderError";
709
- (wrapperError as { cause: unknown }).cause = error;
710
- throw wrapperError;
745
+ if (error instanceof BippyUnsupportedHookError) throw error;
746
+ throw new BippyHookRenderError("Error rendering inspected component", error);
711
747
  };
712
748
 
713
749
  const resolveDefaultProps = (
714
- Component: unknown,
750
+ component: unknown,
715
751
  baseProps: Record<string, unknown>,
716
752
  ): Record<string, unknown> => {
717
753
  if (
718
- Component &&
719
- typeof Component === "object" &&
720
- "defaultProps" in Component &&
721
- Component.defaultProps
754
+ component &&
755
+ typeof component === "object" &&
756
+ "defaultProps" in component &&
757
+ isObjectRecord(component.defaultProps)
722
758
  ) {
723
759
  const props = { ...baseProps };
724
- const defaultProps = Component.defaultProps as Record<string, unknown>;
760
+ const defaultProps = component.defaultProps;
725
761
  for (const propName in defaultProps) {
726
762
  if (props[propName] === undefined) {
727
763
  props[propName] = defaultProps[propName];
@@ -736,11 +772,9 @@ const suppressConsole = (): Record<string, unknown> => {
736
772
  const originalMethods: Record<string, unknown> = {};
737
773
  for (const method in console) {
738
774
  try {
739
- originalMethods[method] = (console as Record<string, unknown>)[method];
740
- (console as Record<string, unknown>)[method] = () => {};
741
- } catch {
742
- /* noop */
743
- }
775
+ originalMethods[method] = Reflect.get(console, method);
776
+ Reflect.set(console, method, () => {});
777
+ } catch {}
744
778
  }
745
779
  return originalMethods;
746
780
  };
@@ -748,19 +782,17 @@ const suppressConsole = (): Record<string, unknown> => {
748
782
  const restoreConsole = (originalMethods: Record<string, unknown>): void => {
749
783
  for (const method in originalMethods) {
750
784
  try {
751
- (console as Record<string, unknown>)[method] = originalMethods[method];
752
- } catch {
753
- /* noop */
754
- }
785
+ Reflect.set(console, method, originalMethods[method]);
786
+ } catch {}
755
787
  }
756
788
  };
757
789
 
758
790
  const performDispatcherInspection = (
759
- dispatcherRef: DispatcherRefContainer,
791
+ dispatcherRef: RendererDispatcherRef,
760
792
  renderFn: () => void,
761
793
  ): HooksTree => {
762
794
  const previousDispatcher = getDispatcherFromRef(dispatcherRef);
763
- setDispatcherOnRef(dispatcherRef, DispatcherProxy);
795
+ setDispatcherOnRef(dispatcherRef, dispatcherProxy);
764
796
 
765
797
  let capturedHookLog: HookLogEntry[] = [];
766
798
  let ancestorStackError: Error | undefined;
@@ -780,10 +812,10 @@ const performDispatcherInspection = (
780
812
  return buildTree(rootStack, capturedHookLog);
781
813
  };
782
814
 
783
- const requireDispatcherRef = (): DispatcherRefContainer => {
815
+ const requireDispatcherRef = (): RendererDispatcherRef => {
784
816
  const dispatcherRef = getDispatcherRef();
785
817
  if (!dispatcherRef) {
786
- throw new Error(
818
+ throw new BippyHookInspectionError(
787
819
  "No React renderer found. Make sure React is loaded and bippy's hook is installed.",
788
820
  );
789
821
  }
@@ -791,38 +823,38 @@ const requireDispatcherRef = (): DispatcherRefContainer => {
791
823
  };
792
824
 
793
825
  const resolveContextDependency = (fiber: Fiber): void => {
794
- if (Object.prototype.hasOwnProperty.call(fiber, "dependencies")) {
826
+ if (Object.hasOwn(fiber, "dependencies")) {
795
827
  const dependencies = fiber.dependencies;
796
828
  currentContextDependency = dependencies !== null ? dependencies.firstContext : null;
797
- } else if (Object.prototype.hasOwnProperty.call(fiber, "dependencies_old")) {
798
- const dependencies = (fiber as unknown as { dependencies_old: typeof fiber.dependencies })
799
- .dependencies_old;
800
- currentContextDependency = dependencies !== null ? dependencies!.firstContext : null;
801
- } else if (Object.prototype.hasOwnProperty.call(fiber, "dependencies_new")) {
802
- const dependencies = (fiber as unknown as { dependencies_new: typeof fiber.dependencies })
803
- .dependencies_new;
804
- currentContextDependency = dependencies !== null ? dependencies!.firstContext : null;
805
- } else if (Object.prototype.hasOwnProperty.call(fiber, "contextDependencies")) {
806
- const contextDependencies = (
807
- fiber as unknown as {
808
- contextDependencies: { first: ContextDependency<unknown> | null } | null;
809
- }
810
- ).contextDependencies;
811
- currentContextDependency = contextDependencies !== null ? contextDependencies.first : null;
829
+ } else if (Object.hasOwn(fiber, "dependencies_old")) {
830
+ const dependencies: unknown = Reflect.get(fiber, "dependencies_old");
831
+ const firstContext = isObjectRecord(dependencies) ? dependencies.firstContext : null;
832
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
833
+ } else if (Object.hasOwn(fiber, "dependencies_new")) {
834
+ const dependencies: unknown = Reflect.get(fiber, "dependencies_new");
835
+ const firstContext = isObjectRecord(dependencies) ? dependencies.firstContext : null;
836
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
837
+ } else if (Object.hasOwn(fiber, "contextDependencies")) {
838
+ const contextDependencies: unknown = Reflect.get(fiber, "contextDependencies");
839
+ const firstContext = isObjectRecord(contextDependencies) ? contextDependencies.first : null;
840
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
812
841
  } else {
813
- throw new Error("Unsupported React version.");
842
+ throw new BippyHookInspectionError("Unsupported React version.");
814
843
  }
815
844
  };
816
845
 
817
846
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
818
847
  const dispatcherRef = requireDispatcherRef();
848
+ const workTags = getReactWorkTagsForFiber(fiber);
819
849
 
820
850
  if (
821
- fiber.tag !== FUNCTION_COMPONENT_TAG &&
822
- fiber.tag !== SIMPLE_MEMO_COMPONENT_TAG &&
823
- fiber.tag !== FORWARD_REF_TAG
851
+ fiber.tag !== workTags.FunctionComponent &&
852
+ fiber.tag !== workTags.SimpleMemoComponent &&
853
+ fiber.tag !== workTags.ForwardRef
824
854
  ) {
825
- throw new Error("Unknown Fiber. Needs to be a function component to inspect hooks.");
855
+ throw new BippyHookInspectionError(
856
+ "Unknown Fiber. Needs to be a function component to inspect hooks.",
857
+ );
826
858
  }
827
859
 
828
860
  getPrimitiveStackCache();
@@ -830,19 +862,17 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
830
862
  currentHook = fiber.memoizedState;
831
863
  currentFiber = fiber;
832
864
 
833
- const debugThenableState =
834
- fiber.dependencies &&
835
- (fiber.dependencies as { _debugThenableState?: { thenables?: unknown[] } })._debugThenableState;
836
- const usedThenables = debugThenableState
837
- ? debugThenableState.thenables || debugThenableState
838
- : null;
865
+ const debugThenableState = fiber.dependencies?._debugThenableState;
866
+ const usedThenables = Array.isArray(debugThenableState)
867
+ ? debugThenableState
868
+ : debugThenableState?.thenables;
839
869
  currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
840
870
  currentThenableIndex = 0;
841
871
 
842
872
  resolveContextDependency(fiber);
843
873
 
844
874
  const type = fiber.type;
845
- let props = fiber.memoizedProps as Record<string, unknown>;
875
+ let props = fiber.memoizedProps;
846
876
  if (type !== fiber.elementType) {
847
877
  props = resolveDefaultProps(type, props);
848
878
  }
@@ -853,22 +883,27 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
853
883
  try {
854
884
  if (
855
885
  currentContextDependency !== null &&
856
- !Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")
886
+ !Object.hasOwn(currentContextDependency, "memoizedValue")
857
887
  ) {
858
888
  setupContexts(contextMap, fiber);
859
889
  }
860
890
 
861
- if (fiber.tag === FORWARD_REF_TAG) {
891
+ if (fiber.tag === workTags.ForwardRef) {
892
+ if (!isForwardRefRenderType(type)) {
893
+ throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
894
+ }
862
895
  return performDispatcherInspection(dispatcherRef, () => {
863
- (type as { render: (props: Record<string, unknown>, ref: unknown) => unknown }).render(
864
- props,
865
- fiber.ref,
866
- );
896
+ type.render(props, fiber.ref);
867
897
  });
868
898
  }
869
899
 
900
+ if (typeof type !== "function") {
901
+ throw new BippyHookInspectionError(
902
+ "Function component fiber is missing its component function.",
903
+ );
904
+ }
870
905
  return performDispatcherInspection(dispatcherRef, () => {
871
- (type as (props: Record<string, unknown>) => unknown)(props);
906
+ type(props);
872
907
  });
873
908
  } finally {
874
909
  currentFiber = null;