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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -1
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +39 -41
  5. package/dist/core.d.ts +39 -41
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +1 -1
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +1 -1
  11. package/dist/{types.d.cts → errors.d.cts} +205 -31
  12. package/dist/{types.d.ts → errors.d.ts} +205 -31
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +11 -3
  15. package/dist/index.d.ts +11 -3
  16. package/dist/index.iife.js +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/install-hook-only.cjs +1 -1
  19. package/dist/install-hook-only.d.cts +8 -0
  20. package/dist/install-hook-only.d.ts +8 -0
  21. package/dist/install-hook-only.iife.js +1 -1
  22. package/dist/install-hook-only.js +1 -1
  23. package/dist/rdt-hook.cjs +1 -1
  24. package/dist/rdt-hook.js +1 -1
  25. package/dist/source.cjs +13 -13
  26. package/dist/source.d.cts +27 -9
  27. package/dist/source.d.ts +27 -9
  28. package/dist/source.js +13 -13
  29. package/package.json +10 -6
  30. package/src/core.ts +269 -224
  31. package/src/errors.ts +99 -0
  32. package/src/rdt-hook.ts +154 -99
  33. package/src/react-internals/generated/react-work-tags.ts +314 -0
  34. package/src/react-internals/index.ts +67 -0
  35. package/src/react-internals/semver.ts +78 -0
  36. package/src/{types.ts → react-internals/types.ts} +13 -3
  37. package/src/source/error-stack.ts +11 -0
  38. package/src/source/get-display-name-from-source.ts +1 -1
  39. package/src/source/get-source.ts +2 -2
  40. package/src/source/index.ts +9 -1
  41. package/src/source/inspect-hooks.ts +199 -165
  42. package/src/source/owner-stack.ts +26 -39
  43. package/src/source/parse-debug-stack.ts +4 -4
  44. package/src/source/parse-hook-names.ts +1 -1
  45. package/src/source/symbolication.ts +468 -101
  46. package/src/generated/react-work-tags.ts +0 -169
  47. package/src/react-internals.ts +0 -67
  48. package/src/unsubscribe.ts +0 -17
@@ -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 "../react-internals/index.js";
8
+ import {
9
+ BippyHookInspectionError,
10
+ BippyHookRenderError,
11
+ BippyUnsupportedHookError,
12
+ } from "../errors.js";
13
+ import { getReactWorkTagsForFiber } from "../react-internals/index.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,41 @@ 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);
465
+ dispatcher.useDebugValue(null);
466
+ dispatcher.useCallback(() => {});
467
+ dispatcher.useTransition();
468
+ dispatcher.useSyncExternalStore(
434
469
  () => () => {},
435
470
  () => null,
436
- () => null,
437
471
  );
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 });
472
+ dispatcher.useDeferredValue(null);
473
+ dispatcher.useMemo(() => null);
474
+ dispatcher.useOptimistic(null);
475
+ dispatcher.useFormState((state: unknown) => state, null);
476
+ dispatcher.useActionState((state: unknown) => state, null);
477
+ dispatcher.useHostTransitionStatus();
478
+ if (typeof dispatcher.useMemoCache === "function") dispatcher.useMemoCache(0);
479
+ if (typeof dispatcher.use === "function") {
480
+ dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
481
+ const fulfilledPromise = Promise.resolve(null);
482
+ Reflect.set(fulfilledPromise, "status", "fulfilled");
483
+ Reflect.set(fulfilledPromise, "value", null);
484
+ dispatcher.use(fulfilledPromise);
448
485
  try {
449
- Dispatcher.use({ then() {} });
450
- } catch {
451
- /* noop */
452
- }
486
+ dispatcher.use(new Promise<never>(() => {}));
487
+ } catch {}
453
488
  }
454
- Dispatcher.useId();
455
- if (typeof Dispatcher.useEffectEvent === "function") Dispatcher.useEffectEvent(() => {});
489
+ dispatcher.useId();
490
+ if (typeof dispatcher.useEffectEvent === "function") dispatcher.useEffectEvent(() => {});
456
491
  } finally {
457
492
  capturedHookLog = hookLog;
458
493
  hookLog = [];
@@ -681,14 +716,17 @@ const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | n
681
716
  const setupContexts = (contextMap: Map<ReactContext<unknown>, unknown>, fiber: Fiber): void => {
682
717
  let current: Fiber | null = fiber;
683
718
  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)) {
719
+ if (current.tag === getReactWorkTagsForFiber(current).ContextProvider) {
720
+ const providerType = current.type;
721
+ const nestedContext = isObjectRecord(providerType) ? providerType._context : undefined;
722
+ const context = isReactContext(nestedContext)
723
+ ? nestedContext
724
+ : isReactContext(providerType)
725
+ ? providerType
726
+ : null;
727
+ if (context && !contextMap.has(context)) {
690
728
  contextMap.set(context, context._currentValue);
691
- context._currentValue = (current.memoizedProps as { value: unknown }).value;
729
+ context._currentValue = current.memoizedProps.value;
692
730
  }
693
731
  }
694
732
  current = current.return;
@@ -703,25 +741,22 @@ const restoreContexts = (contextMap: Map<ReactContext<unknown>, unknown>): void
703
741
 
704
742
  const handleRenderFunctionError = (error: unknown): void => {
705
743
  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;
744
+ if (error instanceof BippyUnsupportedHookError) throw error;
745
+ throw new BippyHookRenderError("Error rendering inspected component", error);
711
746
  };
712
747
 
713
748
  const resolveDefaultProps = (
714
- Component: unknown,
749
+ component: unknown,
715
750
  baseProps: Record<string, unknown>,
716
751
  ): Record<string, unknown> => {
717
752
  if (
718
- Component &&
719
- typeof Component === "object" &&
720
- "defaultProps" in Component &&
721
- Component.defaultProps
753
+ component &&
754
+ typeof component === "object" &&
755
+ "defaultProps" in component &&
756
+ isObjectRecord(component.defaultProps)
722
757
  ) {
723
758
  const props = { ...baseProps };
724
- const defaultProps = Component.defaultProps as Record<string, unknown>;
759
+ const defaultProps = component.defaultProps;
725
760
  for (const propName in defaultProps) {
726
761
  if (props[propName] === undefined) {
727
762
  props[propName] = defaultProps[propName];
@@ -736,11 +771,9 @@ const suppressConsole = (): Record<string, unknown> => {
736
771
  const originalMethods: Record<string, unknown> = {};
737
772
  for (const method in console) {
738
773
  try {
739
- originalMethods[method] = (console as Record<string, unknown>)[method];
740
- (console as Record<string, unknown>)[method] = () => {};
741
- } catch {
742
- /* noop */
743
- }
774
+ originalMethods[method] = Reflect.get(console, method);
775
+ Reflect.set(console, method, () => {});
776
+ } catch {}
744
777
  }
745
778
  return originalMethods;
746
779
  };
@@ -748,19 +781,17 @@ const suppressConsole = (): Record<string, unknown> => {
748
781
  const restoreConsole = (originalMethods: Record<string, unknown>): void => {
749
782
  for (const method in originalMethods) {
750
783
  try {
751
- (console as Record<string, unknown>)[method] = originalMethods[method];
752
- } catch {
753
- /* noop */
754
- }
784
+ Reflect.set(console, method, originalMethods[method]);
785
+ } catch {}
755
786
  }
756
787
  };
757
788
 
758
789
  const performDispatcherInspection = (
759
- dispatcherRef: DispatcherRefContainer,
790
+ dispatcherRef: RendererDispatcherRef,
760
791
  renderFn: () => void,
761
792
  ): HooksTree => {
762
793
  const previousDispatcher = getDispatcherFromRef(dispatcherRef);
763
- setDispatcherOnRef(dispatcherRef, DispatcherProxy);
794
+ setDispatcherOnRef(dispatcherRef, dispatcherProxy);
764
795
 
765
796
  let capturedHookLog: HookLogEntry[] = [];
766
797
  let ancestorStackError: Error | undefined;
@@ -780,10 +811,10 @@ const performDispatcherInspection = (
780
811
  return buildTree(rootStack, capturedHookLog);
781
812
  };
782
813
 
783
- const requireDispatcherRef = (): DispatcherRefContainer => {
814
+ const requireDispatcherRef = (): RendererDispatcherRef => {
784
815
  const dispatcherRef = getDispatcherRef();
785
816
  if (!dispatcherRef) {
786
- throw new Error(
817
+ throw new BippyHookInspectionError(
787
818
  "No React renderer found. Make sure React is loaded and bippy's hook is installed.",
788
819
  );
789
820
  }
@@ -791,38 +822,38 @@ const requireDispatcherRef = (): DispatcherRefContainer => {
791
822
  };
792
823
 
793
824
  const resolveContextDependency = (fiber: Fiber): void => {
794
- if (Object.prototype.hasOwnProperty.call(fiber, "dependencies")) {
825
+ if (Object.hasOwn(fiber, "dependencies")) {
795
826
  const dependencies = fiber.dependencies;
796
827
  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;
828
+ } else if (Object.hasOwn(fiber, "dependencies_old")) {
829
+ const dependencies: unknown = Reflect.get(fiber, "dependencies_old");
830
+ const firstContext = isObjectRecord(dependencies) ? dependencies.firstContext : null;
831
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
832
+ } else if (Object.hasOwn(fiber, "dependencies_new")) {
833
+ const dependencies: unknown = Reflect.get(fiber, "dependencies_new");
834
+ const firstContext = isObjectRecord(dependencies) ? dependencies.firstContext : null;
835
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
836
+ } else if (Object.hasOwn(fiber, "contextDependencies")) {
837
+ const contextDependencies: unknown = Reflect.get(fiber, "contextDependencies");
838
+ const firstContext = isObjectRecord(contextDependencies) ? contextDependencies.first : null;
839
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
812
840
  } else {
813
- throw new Error("Unsupported React version.");
841
+ throw new BippyHookInspectionError("Unsupported React version.");
814
842
  }
815
843
  };
816
844
 
817
845
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
818
846
  const dispatcherRef = requireDispatcherRef();
847
+ const workTags = getReactWorkTagsForFiber(fiber);
819
848
 
820
849
  if (
821
- fiber.tag !== FUNCTION_COMPONENT_TAG &&
822
- fiber.tag !== SIMPLE_MEMO_COMPONENT_TAG &&
823
- fiber.tag !== FORWARD_REF_TAG
850
+ fiber.tag !== workTags.FunctionComponent &&
851
+ fiber.tag !== workTags.SimpleMemoComponent &&
852
+ fiber.tag !== workTags.ForwardRef
824
853
  ) {
825
- throw new Error("Unknown Fiber. Needs to be a function component to inspect hooks.");
854
+ throw new BippyHookInspectionError(
855
+ "Unknown Fiber. Needs to be a function component to inspect hooks.",
856
+ );
826
857
  }
827
858
 
828
859
  getPrimitiveStackCache();
@@ -830,19 +861,17 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
830
861
  currentHook = fiber.memoizedState;
831
862
  currentFiber = fiber;
832
863
 
833
- const debugThenableState =
834
- fiber.dependencies &&
835
- (fiber.dependencies as { _debugThenableState?: { thenables?: unknown[] } })._debugThenableState;
836
- const usedThenables = debugThenableState
837
- ? debugThenableState.thenables || debugThenableState
838
- : null;
864
+ const debugThenableState = fiber.dependencies?._debugThenableState;
865
+ const usedThenables = Array.isArray(debugThenableState)
866
+ ? debugThenableState
867
+ : debugThenableState?.thenables;
839
868
  currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
840
869
  currentThenableIndex = 0;
841
870
 
842
871
  resolveContextDependency(fiber);
843
872
 
844
873
  const type = fiber.type;
845
- let props = fiber.memoizedProps as Record<string, unknown>;
874
+ let props = fiber.memoizedProps;
846
875
  if (type !== fiber.elementType) {
847
876
  props = resolveDefaultProps(type, props);
848
877
  }
@@ -853,22 +882,27 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
853
882
  try {
854
883
  if (
855
884
  currentContextDependency !== null &&
856
- !Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")
885
+ !Object.hasOwn(currentContextDependency, "memoizedValue")
857
886
  ) {
858
887
  setupContexts(contextMap, fiber);
859
888
  }
860
889
 
861
- if (fiber.tag === FORWARD_REF_TAG) {
890
+ if (fiber.tag === workTags.ForwardRef) {
891
+ if (!isForwardRefRenderType(type)) {
892
+ throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
893
+ }
862
894
  return performDispatcherInspection(dispatcherRef, () => {
863
- (type as { render: (props: Record<string, unknown>, ref: unknown) => unknown }).render(
864
- props,
865
- fiber.ref,
866
- );
895
+ type.render(props, fiber.ref);
867
896
  });
868
897
  }
869
898
 
899
+ if (typeof type !== "function") {
900
+ throw new BippyHookInspectionError(
901
+ "Function component fiber is missing its component function.",
902
+ );
903
+ }
870
904
  return performDispatcherInspection(dispatcherRef, () => {
871
- (type as (props: Record<string, unknown>) => unknown)(props);
905
+ type(props);
872
906
  });
873
907
  } finally {
874
908
  currentFiber = null;