bippy 0.6.1 → 0.7.0

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 (67) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +223 -463
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/errors.d.cts +410 -0
  6. package/dist/errors.d.ts +410 -0
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +153 -3
  9. package/dist/index.d.ts +153 -3
  10. package/dist/index.js +1 -1
  11. package/dist/install-hook-only.cjs +1 -1
  12. package/dist/install-hook-only.d.cts +9 -1
  13. package/dist/install-hook-only.d.ts +9 -1
  14. package/dist/install-hook-only.js +1 -1
  15. package/dist/rdt-hook.cjs +1 -1
  16. package/dist/rdt-hook.js +1 -1
  17. package/dist/source.cjs +14 -5
  18. package/dist/source.d.cts +86 -69
  19. package/dist/source.d.ts +86 -69
  20. package/dist/source.js +14 -5
  21. package/package.json +26 -27
  22. package/src/core.ts +353 -685
  23. package/src/errors.ts +34 -0
  24. package/src/index.ts +1 -0
  25. package/src/install-hook-only.ts +2 -2
  26. package/src/rdt-hook.ts +160 -161
  27. package/src/react-internals/generated/react-work-tags.ts +318 -0
  28. package/src/react-internals/index.ts +67 -0
  29. package/src/react-internals/semver.ts +80 -0
  30. package/src/react-internals/types.ts +186 -0
  31. package/src/react.ts +76 -0
  32. package/src/source/constants.ts +1 -1
  33. package/src/source/error-stack.ts +11 -0
  34. package/src/source/get-display-name-from-source.ts +44 -40
  35. package/src/source/get-source.ts +43 -26
  36. package/src/source/index.ts +11 -1
  37. package/src/source/inspect-hooks.ts +220 -207
  38. package/src/source/owner-stack.ts +119 -174
  39. package/src/source/parse-debug-stack.ts +4 -4
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +709 -120
  44. package/dist/core.d.cts +0 -214
  45. package/dist/core.d.ts +0 -214
  46. package/dist/core2.d.cts +0 -3
  47. package/dist/core2.d.ts +0 -3
  48. package/dist/get-source.cjs +0 -19
  49. package/dist/get-source.js +0 -19
  50. package/dist/index.iife.js +0 -9
  51. package/dist/install-hook-only.iife.js +0 -9
  52. package/dist/react-refresh.cjs +0 -9
  53. package/dist/react-refresh.d.cts +0 -66
  54. package/dist/react-refresh.d.ts +0 -66
  55. package/dist/react-refresh.js +0 -9
  56. package/dist/unsubscribe.d.cts +0 -298
  57. package/dist/unsubscribe.d.ts +0 -298
  58. package/src/react-refresh/constants.ts +0 -9
  59. package/src/react-refresh/detect-hmr-transport.ts +0 -33
  60. package/src/react-refresh/index.ts +0 -173
  61. package/src/react-refresh/metro-hmr-transport.ts +0 -188
  62. package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
  63. package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
  64. package/src/react-refresh/types.ts +0 -7
  65. package/src/react-refresh/vite-hmr-transport.ts +0 -116
  66. package/src/types.ts +0 -438
  67. package/src/unsubscribe.ts +0 -17
@@ -1,15 +1,26 @@
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
- import { getRDTHook, _renderers } from "../rdt-hook.js";
15
+ import {
16
+ getRendererDispatcherRefs,
17
+ readDispatcher,
18
+ writeDispatcher,
19
+ } from "./renderer-dispatchers.js";
4
20
 
5
21
  const REACT_CONTEXT_TYPE = Symbol.for("react.context");
6
22
  const REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
7
23
 
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
24
  export interface HookSource {
14
25
  lineNumber: number | null;
15
26
  columnNumber: number | null;
@@ -26,8 +37,7 @@ export interface HooksNode {
26
37
  hookSource: HookSource | null;
27
38
  }
28
39
 
29
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
30
- export interface HooksTree extends Array<HooksNode> {}
40
+ export type HooksTree = HooksNode[];
31
41
 
32
42
  interface HookLogEntry {
33
43
  displayName: string | null;
@@ -37,10 +47,29 @@ interface HookLogEntry {
37
47
  dispatcherHookName: string;
38
48
  }
39
49
 
40
- interface DispatcherRefContainer {
41
- H?: unknown;
42
- current?: unknown;
43
- [key: string]: unknown;
50
+ interface InspectableReactContext {
51
+ _currentValue: unknown;
52
+ displayName?: string;
53
+ }
54
+
55
+ interface InspectableRef {
56
+ current: unknown;
57
+ }
58
+
59
+ interface InspectableThenable {
60
+ reason?: unknown;
61
+ status?: unknown;
62
+ then: (...arguments_: unknown[]) => unknown;
63
+ value?: unknown;
64
+ }
65
+
66
+ interface ForwardRefRenderType {
67
+ render: (props: Record<string, unknown>, ref: unknown) => unknown;
68
+ }
69
+
70
+ interface InspectedActionState {
71
+ error: unknown;
72
+ value: unknown;
44
73
  }
45
74
 
46
75
  let hookLog: HookLogEntry[] = [];
@@ -49,11 +78,10 @@ let currentFiber: Fiber | null = null;
49
78
  let currentHook: MemoizedState | null = null;
50
79
  let currentContextDependency: ContextDependency<unknown> | null = null;
51
80
  let currentThenableIndex = 0;
52
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
- let currentThenableState: any[] | null = null;
81
+ let currentThenableState: unknown[] | null = null;
54
82
 
55
83
  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.",
84
+ "Suspense interrupted this render. This error is an internal implementation detail of `use`.",
57
85
  );
58
86
 
59
87
  const parseErrorStack = (error: Error): StackFrame[] =>
@@ -65,40 +93,40 @@ const nextHook = (): MemoizedState | null => {
65
93
  return hook;
66
94
  };
67
95
 
68
- const readContext = <T>(context: ReactContext<T>): T => {
96
+ const isInspectableThenable = (value: unknown): value is InspectableThenable =>
97
+ typeof value === "object" &&
98
+ value !== null &&
99
+ "then" in value &&
100
+ typeof value.then === "function";
101
+
102
+ const isInspectableRef = (value: unknown): value is InspectableRef =>
103
+ typeof value === "object" && value !== null && "current" in value;
104
+
105
+ const isReactContext = (value: unknown): value is ReactContext<unknown> =>
106
+ typeof value === "object" && value !== null && "_currentValue" in value;
107
+
108
+ const isContextDependency = (value: unknown): value is ContextDependency<unknown> =>
109
+ typeof value === "object" && value !== null && "context" in value && "next" in value;
110
+
111
+ const isForwardRefRenderType = (value: unknown): value is ForwardRefRenderType =>
112
+ typeof value === "object" &&
113
+ value !== null &&
114
+ "render" in value &&
115
+ typeof value.render === "function";
116
+
117
+ const readContext = (context: InspectableReactContext): unknown => {
69
118
  if (currentFiber === null) return context._currentValue;
70
119
  if (currentContextDependency === null) {
71
- throw new Error("Context reads do not line up with context dependencies.");
120
+ throw new BippyHookInspectionError("Context reads don’t match context dependencies.");
72
121
  }
73
- if (Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")) {
74
- const value = currentContextDependency.memoizedValue as T;
122
+ if (Object.hasOwn(currentContextDependency, "memoizedValue")) {
123
+ const value = currentContextDependency.memoizedValue;
75
124
  currentContextDependency = currentContextDependency.next;
76
125
  return value;
77
126
  }
78
127
  return context._currentValue;
79
128
  };
80
129
 
81
- const getDispatcherRef = (): DispatcherRefContainer | null => {
82
- const rdtHook = getRDTHook();
83
- const allRenderers = [..._renderers, ...rdtHook.renderers.values()];
84
- for (const renderer of allRenderers) {
85
- const ref = renderer.currentDispatcherRef;
86
- if (ref && typeof ref === "object") return ref as DispatcherRefContainer;
87
- }
88
- return null;
89
- };
90
-
91
- const getDispatcherFromRef = (ref: DispatcherRefContainer): unknown =>
92
- "H" in ref ? ref.H : ref.current;
93
-
94
- const setDispatcherOnRef = (ref: DispatcherRefContainer, dispatcher: unknown): void => {
95
- if ("H" in ref) {
96
- ref.H = dispatcher;
97
- } else {
98
- ref.current = dispatcher;
99
- }
100
- };
101
-
102
130
  const pushHookLogEntry = (
103
131
  primitive: string,
104
132
  value: unknown,
@@ -115,14 +143,13 @@ const pushHookLogEntry = (
115
143
  };
116
144
 
117
145
  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 =
146
+ if (typeof usable === "object" && usable !== null) {
147
+ if (isInspectableThenable(usable)) {
148
+ const cachedThenable =
123
149
  currentThenableState !== null && currentThenableIndex < currentThenableState.length
124
150
  ? currentThenableState[currentThenableIndex++]
125
- : asThenable;
151
+ : usable;
152
+ const thenable = isInspectableThenable(cachedThenable) ? cachedThenable : usable;
126
153
 
127
154
  switch (thenable.status) {
128
155
  case "fulfilled": {
@@ -135,19 +162,22 @@ const dispatcherUse = (usable: unknown): unknown => {
135
162
  pushHookLogEntry("Unresolved", thenable, "Use");
136
163
  throw SuspenseException;
137
164
  }
138
- if (asThenable.$$typeof === REACT_CONTEXT_TYPE && "_currentValue" in asThenable) {
139
- const context: ReactContext<unknown> = asThenable;
165
+ if ("$$typeof" in usable && usable.$$typeof === REACT_CONTEXT_TYPE && isReactContext(usable)) {
166
+ const context: InspectableReactContext = {
167
+ _currentValue: usable._currentValue,
168
+ displayName: typeof usable.displayName === "string" ? usable.displayName : undefined,
169
+ };
140
170
  const value = readContext(context);
141
- pushHookLogEntry("Context (use)", value, "Use", context.displayName || "Context");
171
+ pushHookLogEntry("Context (use)", value, "Use", context.displayName ?? "Context");
142
172
  return value;
143
173
  }
144
174
  }
145
- throw new Error("An unsupported type was passed to use(): " + String(usable));
175
+ throw new BippyHookInspectionError("use() received an unsupported value: " + String(usable));
146
176
  };
147
177
 
148
- const dispatcherUseContext = (context: ReactContext<unknown>): unknown => {
178
+ const dispatcherUseContext = (context: InspectableReactContext): unknown => {
149
179
  const value = readContext(context);
150
- pushHookLogEntry("Context", value, "Context", context.displayName || null);
180
+ pushHookLogEntry("Context", value, "Context", context.displayName ?? null);
151
181
  return value;
152
182
  };
153
183
 
@@ -157,7 +187,7 @@ const dispatcherUseState = (initialState: unknown): [unknown, () => void] => {
157
187
  hook !== null
158
188
  ? hook.memoizedState
159
189
  : typeof initialState === "function"
160
- ? (initialState as () => unknown)()
190
+ ? initialState()
161
191
  : initialState;
162
192
  pushHookLogEntry("State", state, "State");
163
193
  return [state, () => {}];
@@ -175,11 +205,13 @@ const dispatcherUseReducer = (
175
205
  return [state, () => {}];
176
206
  };
177
207
 
178
- const dispatcherUseRef = (initialValue: unknown): { current: unknown } => {
208
+ const dispatcherUseRef = (initialValue: unknown): InspectableRef => {
179
209
  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 };
210
+ const ref = isInspectableRef(hook?.memoizedState)
211
+ ? hook.memoizedState
212
+ : { current: initialValue };
213
+ pushHookLogEntry("Ref", ref.current, "Ref");
214
+ return ref;
183
215
  };
184
216
 
185
217
  const dispatcherUseCacheRefresh = (): (() => void) => {
@@ -205,10 +237,7 @@ const dispatcherUseEffect = (create: () => void): void => {
205
237
 
206
238
  const dispatcherUseImperativeHandle = (ref: unknown): void => {
207
239
  nextHook();
208
- let instance: unknown;
209
- if (ref !== null && typeof ref === "object" && "current" in ref) {
210
- instance = ref.current;
211
- }
240
+ const instance = isInspectableRef(ref) ? ref.current : undefined;
212
241
  pushHookLogEntry("ImperativeHandle", instance, "ImperativeHandle");
213
242
  };
214
243
 
@@ -227,7 +256,7 @@ const dispatcherUseCallback = (callback: unknown): unknown => {
227
256
  const hook = nextHook();
228
257
  pushHookLogEntry(
229
258
  "Callback",
230
- hook !== null ? (hook.memoizedState as unknown[])[0] : callback,
259
+ Array.isArray(hook?.memoizedState) ? hook.memoizedState[0] : callback,
231
260
  "Callback",
232
261
  );
233
262
  return callback;
@@ -235,7 +264,7 @@ const dispatcherUseCallback = (callback: unknown): unknown => {
235
264
 
236
265
  const dispatcherUseMemo = (nextCreate: () => unknown): unknown => {
237
266
  const hook = nextHook();
238
- const value = hook !== null ? (hook.memoizedState as unknown[])[0] : nextCreate();
267
+ const value = Array.isArray(hook?.memoizedState) ? hook.memoizedState[0] : nextCreate();
239
268
  pushHookLogEntry("Memo", value, "Memo");
240
269
  return value;
241
270
  };
@@ -254,7 +283,7 @@ const dispatcherUseSyncExternalStore = (
254
283
  const dispatcherUseTransition = (): [boolean, () => void] => {
255
284
  const stateHook = nextHook();
256
285
  nextHook();
257
- const isPending = stateHook !== null ? (stateHook.memoizedState as boolean) : false;
286
+ const isPending = stateHook?.memoizedState === true;
258
287
  pushHookLogEntry("Transition", isPending, "Transition");
259
288
  return [isPending, () => {}];
260
289
  };
@@ -268,7 +297,7 @@ const dispatcherUseDeferredValue = (value: unknown): unknown => {
268
297
 
269
298
  const dispatcherUseId = (): string => {
270
299
  const hook = nextHook();
271
- const identifier = hook !== null ? (hook.memoizedState as string) : "";
300
+ const identifier = typeof hook?.memoizedState === "string" ? hook.memoizedState : "";
272
301
  pushHookLogEntry("Id", identifier, "Id");
273
302
  return identifier;
274
303
  };
@@ -277,9 +306,7 @@ const dispatcherUseMemoCache = (size: number): unknown[] => {
277
306
  const fiber = currentFiber;
278
307
  if (fiber === null || fiber === undefined) return [];
279
308
 
280
- const memoCache = (
281
- fiber.updateQueue as { memoCache?: { data: unknown[][]; index: number } } | null
282
- )?.memoCache;
309
+ const memoCache = fiber.updateQueue?.memoCache;
283
310
  if (memoCache === null || memoCache === undefined) return [];
284
311
 
285
312
  let memoCacheSlots = memoCache.data[memoCache.index];
@@ -304,28 +331,22 @@ const dispatcherUseOptimistic = (passthrough: unknown): [unknown, () => void] =>
304
331
  const inspectActionStateHook = (
305
332
  hook: MemoizedState | null,
306
333
  initialState: unknown,
307
- ): { value: unknown; error: unknown } => {
334
+ ): InspectedActionState => {
308
335
  let value: unknown;
309
336
  let error: unknown = null;
310
337
  if (hook !== null) {
311
338
  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) {
339
+ if (isInspectableThenable(actionResult)) {
340
+ switch (actionResult.status) {
320
341
  case "fulfilled":
321
- value = thenable.value;
342
+ value = actionResult.value;
322
343
  break;
323
344
  case "rejected":
324
- error = thenable.reason;
345
+ error = actionResult.reason;
325
346
  break;
326
347
  default:
327
348
  error = SuspenseException;
328
- value = thenable;
349
+ value = actionResult;
329
350
  }
330
351
  } else {
331
352
  value = actionResult;
@@ -342,15 +363,8 @@ const createActionStateDispatcher =
342
363
  const hook = nextHook();
343
364
  nextHook();
344
365
  nextHook();
345
- const stackError = new Error();
346
366
  const { value, error } = inspectActionStateHook(hook, initialState);
347
- hookLog.push({
348
- displayName: null,
349
- primitive,
350
- stackError,
351
- value,
352
- dispatcherHookName: primitive,
353
- });
367
+ pushHookLogEntry(primitive, value, primitive);
354
368
  if (error !== null) throw error;
355
369
  return [value, () => {}, false];
356
370
  };
@@ -360,7 +374,7 @@ const dispatcherUseFormState = createActionStateDispatcher("FormState");
360
374
 
361
375
  const dispatcherUseHostTransitionStatus = (): unknown => {
362
376
  // 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>);
377
+ const status = readContext({ _currentValue: null });
364
378
  pushHookLogEntry("HostTransitionStatus", status, "HostTransitionStatus");
365
379
  return status;
366
380
  };
@@ -371,8 +385,7 @@ const dispatcherUseEffectEvent = (callback: (...args: unknown[]) => unknown): ty
371
385
  return callback;
372
386
  };
373
387
 
374
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
375
- const Dispatcher: Record<string, (...args: any[]) => any> = {
388
+ const dispatcher = {
376
389
  readContext,
377
390
  use: dispatcherUse,
378
391
  useCallback: dispatcherUseCallback,
@@ -399,17 +412,12 @@ const Dispatcher: Record<string, (...args: any[]) => any> = {
399
412
  useEffectEvent: dispatcherUseEffectEvent,
400
413
  };
401
414
 
402
- const DispatcherProxy =
403
- 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;
411
- },
412
- });
415
+ const dispatcherProxy = new Proxy(dispatcher, {
416
+ get(target, propertyName: string) {
417
+ if (Object.hasOwn(target, propertyName)) return Reflect.get(target, propertyName);
418
+ throw new BippyUnsupportedHookError("The React dispatcher is missing method " + propertyName);
419
+ },
420
+ });
413
421
 
414
422
  const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
415
423
  if (primitiveStackCache !== null) return primitiveStackCache;
@@ -418,41 +426,38 @@ const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
418
426
  let capturedHookLog: HookLogEntry[];
419
427
 
420
428
  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(
429
+ dispatcher.useContext({ _currentValue: null });
430
+ dispatcher.useState(null);
431
+ dispatcher.useReducer((state: unknown) => state, null);
432
+ dispatcher.useRef(null);
433
+ dispatcher.useCacheRefresh();
434
+ dispatcher.useLayoutEffect(() => {});
435
+ dispatcher.useInsertionEffect(() => {});
436
+ dispatcher.useEffect(() => {});
437
+ dispatcher.useImperativeHandle(undefined);
438
+ dispatcher.useDebugValue(null);
439
+ dispatcher.useCallback(() => {});
440
+ dispatcher.useTransition();
441
+ dispatcher.useSyncExternalStore(
434
442
  () => () => {},
435
443
  () => null,
436
- () => null,
437
444
  );
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 });
448
- try {
449
- Dispatcher.use({ then() {} });
450
- } catch {
451
- /* noop */
452
- }
453
- }
454
- Dispatcher.useId();
455
- if (typeof Dispatcher.useEffectEvent === "function") Dispatcher.useEffectEvent(() => {});
445
+ dispatcher.useDeferredValue(null);
446
+ dispatcher.useMemo(() => null);
447
+ dispatcher.useOptimistic(null);
448
+ dispatcher.useFormState((state: unknown) => state, null);
449
+ dispatcher.useActionState((state: unknown) => state, null);
450
+ dispatcher.useHostTransitionStatus();
451
+ dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
452
+ const fulfilledPromise = Promise.resolve(null);
453
+ Reflect.set(fulfilledPromise, "status", "fulfilled");
454
+ Reflect.set(fulfilledPromise, "value", null);
455
+ dispatcher.use(fulfilledPromise);
456
+ try {
457
+ dispatcher.use(new Promise<never>(() => {}));
458
+ } catch {}
459
+ dispatcher.useId();
460
+ dispatcher.useEffectEvent(() => {});
456
461
  } finally {
457
462
  capturedHookLog = hookLog;
458
463
  hookLog = [];
@@ -681,14 +686,20 @@ const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | n
681
686
  const setupContexts = (contextMap: Map<ReactContext<unknown>, unknown>, fiber: Fiber): void => {
682
687
  let current: Fiber | null = fiber;
683
688
  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)) {
689
+ if (current.tag === getReactWorkTagsForFiber(current).ContextProvider) {
690
+ const providerType = current.type;
691
+ const nestedContext =
692
+ typeof providerType === "object" && providerType !== null && "_context" in providerType
693
+ ? providerType._context
694
+ : undefined;
695
+ const context = isReactContext(nestedContext)
696
+ ? nestedContext
697
+ : isReactContext(providerType)
698
+ ? providerType
699
+ : null;
700
+ if (context && !contextMap.has(context)) {
690
701
  contextMap.set(context, context._currentValue);
691
- context._currentValue = (current.memoizedProps as { value: unknown }).value;
702
+ context._currentValue = current.memoizedProps.value;
692
703
  }
693
704
  }
694
705
  current = current.return;
@@ -703,28 +714,26 @@ const restoreContexts = (contextMap: Map<ReactContext<unknown>, unknown>): void
703
714
 
704
715
  const handleRenderFunctionError = (error: unknown): void => {
705
716
  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;
717
+ if (error instanceof BippyUnsupportedHookError) throw error;
718
+ throw new BippyHookRenderError("Bippy couldn’t render the inspected component", error);
711
719
  };
712
720
 
713
721
  const resolveDefaultProps = (
714
- Component: unknown,
722
+ component: unknown,
715
723
  baseProps: Record<string, unknown>,
716
724
  ): Record<string, unknown> => {
717
725
  if (
718
- Component &&
719
- typeof Component === "object" &&
720
- "defaultProps" in Component &&
721
- Component.defaultProps
726
+ component &&
727
+ typeof component === "object" &&
728
+ "defaultProps" in component &&
729
+ typeof component.defaultProps === "object" &&
730
+ component.defaultProps !== null
722
731
  ) {
723
732
  const props = { ...baseProps };
724
- const defaultProps = Component.defaultProps as Record<string, unknown>;
725
- for (const propName in defaultProps) {
733
+ const defaultProps = component.defaultProps;
734
+ for (const [propName, value] of Object.entries(defaultProps)) {
726
735
  if (props[propName] === undefined) {
727
- props[propName] = defaultProps[propName];
736
+ props[propName] = value;
728
737
  }
729
738
  }
730
739
  return props;
@@ -736,11 +745,9 @@ const suppressConsole = (): Record<string, unknown> => {
736
745
  const originalMethods: Record<string, unknown> = {};
737
746
  for (const method in console) {
738
747
  try {
739
- originalMethods[method] = (console as Record<string, unknown>)[method];
740
- (console as Record<string, unknown>)[method] = () => {};
741
- } catch {
742
- /* noop */
743
- }
748
+ originalMethods[method] = Reflect.get(console, method);
749
+ Reflect.set(console, method, () => {});
750
+ } catch {}
744
751
  }
745
752
  return originalMethods;
746
753
  };
@@ -748,19 +755,17 @@ const suppressConsole = (): Record<string, unknown> => {
748
755
  const restoreConsole = (originalMethods: Record<string, unknown>): void => {
749
756
  for (const method in originalMethods) {
750
757
  try {
751
- (console as Record<string, unknown>)[method] = originalMethods[method];
752
- } catch {
753
- /* noop */
754
- }
758
+ Reflect.set(console, method, originalMethods[method]);
759
+ } catch {}
755
760
  }
756
761
  };
757
762
 
758
763
  const performDispatcherInspection = (
759
- dispatcherRef: DispatcherRefContainer,
764
+ dispatcherRef: RendererDispatcherRef,
760
765
  renderFn: () => void,
761
766
  ): HooksTree => {
762
- const previousDispatcher = getDispatcherFromRef(dispatcherRef);
763
- setDispatcherOnRef(dispatcherRef, DispatcherProxy);
767
+ const previousDispatcher = readDispatcher(dispatcherRef);
768
+ writeDispatcher(dispatcherRef, dispatcherProxy);
764
769
 
765
770
  let capturedHookLog: HookLogEntry[] = [];
766
771
  let ancestorStackError: Error | undefined;
@@ -773,56 +778,61 @@ const performDispatcherInspection = (
773
778
  } finally {
774
779
  capturedHookLog = hookLog;
775
780
  hookLog = [];
776
- setDispatcherOnRef(dispatcherRef, previousDispatcher);
781
+ writeDispatcher(dispatcherRef, previousDispatcher);
777
782
  }
778
783
 
779
784
  const rootStack = ancestorStackError !== undefined ? parseErrorStack(ancestorStackError) : [];
780
785
  return buildTree(rootStack, capturedHookLog);
781
786
  };
782
787
 
783
- const requireDispatcherRef = (): DispatcherRefContainer => {
784
- const dispatcherRef = getDispatcherRef();
788
+ const requireDispatcherRef = (): RendererDispatcherRef => {
789
+ const dispatcherRef = getRendererDispatcherRefs()[0];
785
790
  if (!dispatcherRef) {
786
- throw new Error(
787
- "No React renderer found. Make sure React is loaded and bippy's hook is installed.",
791
+ throw new BippyHookInspectionError(
792
+ "Bippy couldn’t find a React renderer. Load React and install Bippy’s hook.",
788
793
  );
789
794
  }
790
795
  return dispatcherRef;
791
796
  };
792
797
 
793
798
  const resolveContextDependency = (fiber: Fiber): void => {
794
- if (Object.prototype.hasOwnProperty.call(fiber, "dependencies")) {
799
+ const legacyDependenciesKey = ["dependencies_old", "dependencies_new"].find((key) =>
800
+ Object.hasOwn(fiber, key),
801
+ );
802
+ if (Object.hasOwn(fiber, "dependencies")) {
795
803
  const dependencies = fiber.dependencies;
796
804
  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;
805
+ } else if (legacyDependenciesKey) {
806
+ const dependencies: unknown = Reflect.get(fiber, legacyDependenciesKey);
807
+ const firstContext =
808
+ typeof dependencies === "object" && dependencies !== null && "firstContext" in dependencies
809
+ ? dependencies.firstContext
810
+ : null;
811
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
812
+ } else if (Object.hasOwn(fiber, "contextDependencies")) {
813
+ const contextDependencies: unknown = Reflect.get(fiber, "contextDependencies");
814
+ const firstContext =
815
+ typeof contextDependencies === "object" &&
816
+ contextDependencies !== null &&
817
+ "first" in contextDependencies
818
+ ? contextDependencies.first
819
+ : null;
820
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
812
821
  } else {
813
- throw new Error("Unsupported React version.");
822
+ throw new BippyHookInspectionError("Bippy doesn’t support this React version.");
814
823
  }
815
824
  };
816
825
 
817
826
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
818
827
  const dispatcherRef = requireDispatcherRef();
828
+ const workTags = getReactWorkTagsForFiber(fiber);
819
829
 
820
830
  if (
821
- fiber.tag !== FUNCTION_COMPONENT_TAG &&
822
- fiber.tag !== SIMPLE_MEMO_COMPONENT_TAG &&
823
- fiber.tag !== FORWARD_REF_TAG
831
+ fiber.tag !== workTags.FunctionComponent &&
832
+ fiber.tag !== workTags.SimpleMemoComponent &&
833
+ fiber.tag !== workTags.ForwardRef
824
834
  ) {
825
- throw new Error("Unknown Fiber. Needs to be a function component to inspect hooks.");
835
+ throw new BippyHookInspectionError("Hook inspection requires a function component Fiber.");
826
836
  }
827
837
 
828
838
  getPrimitiveStackCache();
@@ -830,19 +840,17 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
830
840
  currentHook = fiber.memoizedState;
831
841
  currentFiber = fiber;
832
842
 
833
- const debugThenableState =
834
- fiber.dependencies &&
835
- (fiber.dependencies as { _debugThenableState?: { thenables?: unknown[] } })._debugThenableState;
836
- const usedThenables = debugThenableState
837
- ? debugThenableState.thenables || debugThenableState
838
- : null;
843
+ const debugThenableState = fiber.dependencies?._debugThenableState;
844
+ const usedThenables = Array.isArray(debugThenableState)
845
+ ? debugThenableState
846
+ : debugThenableState?.thenables;
839
847
  currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
840
848
  currentThenableIndex = 0;
841
849
 
842
850
  resolveContextDependency(fiber);
843
851
 
844
852
  const type = fiber.type;
845
- let props = fiber.memoizedProps as Record<string, unknown>;
853
+ let props = fiber.memoizedProps;
846
854
  if (type !== fiber.elementType) {
847
855
  props = resolveDefaultProps(type, props);
848
856
  }
@@ -853,22 +861,27 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
853
861
  try {
854
862
  if (
855
863
  currentContextDependency !== null &&
856
- !Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")
864
+ !Object.hasOwn(currentContextDependency, "memoizedValue")
857
865
  ) {
858
866
  setupContexts(contextMap, fiber);
859
867
  }
860
868
 
861
- if (fiber.tag === FORWARD_REF_TAG) {
869
+ if (fiber.tag === workTags.ForwardRef) {
870
+ if (!isForwardRefRenderType(type)) {
871
+ throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
872
+ }
862
873
  return performDispatcherInspection(dispatcherRef, () => {
863
- (type as { render: (props: Record<string, unknown>, ref: unknown) => unknown }).render(
864
- props,
865
- fiber.ref,
866
- );
874
+ type.render(props, fiber.ref);
867
875
  });
868
876
  }
869
877
 
878
+ if (typeof type !== "function") {
879
+ throw new BippyHookInspectionError(
880
+ "Function component fiber is missing its component function.",
881
+ );
882
+ }
870
883
  return performDispatcherInspection(dispatcherRef, () => {
871
- (type as (props: Record<string, unknown>) => unknown)(props);
884
+ type(props);
872
885
  });
873
886
  } finally {
874
887
  currentFiber = null;