bippy 0.6.0-dev.8609c2d → 0.6.1-dev.03b829a

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