bippy 0.6.1-dev.61475ed → 0.6.1-dev.a3b3942

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 +420 -0
  12. package/dist/errors.d.ts +420 -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 -5
  26. package/dist/source.d.cts +27 -11
  27. package/dist/source.d.ts +27 -11
  28. package/dist/source.js +14 -5
  29. package/package.json +22 -16
  30. package/src/core.ts +349 -328
  31. package/src/errors.ts +34 -0
  32. package/src/install-hook-only.ts +2 -2
  33. package/src/rdt-hook.ts +160 -161
  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 +80 -0
  37. package/src/react-internals/types.ts +197 -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 +32 -25
  41. package/src/source/get-source.ts +5 -5
  42. package/src/source/index.ts +9 -1
  43. package/src/source/inspect-hooks.ts +215 -182
  44. package/src/source/owner-stack.ts +127 -166
  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 -3
  48. package/src/source/symbolication.ts +490 -90
  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;
@@ -342,15 +383,8 @@ const createActionStateDispatcher =
342
383
  const hook = nextHook();
343
384
  nextHook();
344
385
  nextHook();
345
- const stackError = new Error();
346
386
  const { value, error } = inspectActionStateHook(hook, initialState);
347
- hookLog.push({
348
- displayName: null,
349
- primitive,
350
- stackError,
351
- value,
352
- dispatcherHookName: primitive,
353
- });
387
+ pushHookLogEntry(primitive, value, primitive);
354
388
  if (error !== null) throw error;
355
389
  return [value, () => {}, false];
356
390
  };
@@ -360,7 +394,7 @@ const dispatcherUseFormState = createActionStateDispatcher("FormState");
360
394
 
361
395
  const dispatcherUseHostTransitionStatus = (): unknown => {
362
396
  // 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>);
397
+ const status = readContext({ _currentValue: null });
364
398
  pushHookLogEntry("HostTransitionStatus", status, "HostTransitionStatus");
365
399
  return status;
366
400
  };
@@ -371,8 +405,7 @@ const dispatcherUseEffectEvent = (callback: (...args: unknown[]) => unknown): ty
371
405
  return callback;
372
406
  };
373
407
 
374
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
375
- const Dispatcher: Record<string, (...args: any[]) => any> = {
408
+ const dispatcher = {
376
409
  readContext,
377
410
  use: dispatcherUse,
378
411
  useCallback: dispatcherUseCallback,
@@ -399,17 +432,12 @@ const Dispatcher: Record<string, (...args: any[]) => any> = {
399
432
  useEffectEvent: dispatcherUseEffectEvent,
400
433
  };
401
434
 
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
- });
435
+ const dispatcherProxy = new Proxy(dispatcher, {
436
+ get(target, propertyName: string) {
437
+ if (Object.hasOwn(target, propertyName)) return Reflect.get(target, propertyName);
438
+ throw new BippyUnsupportedHookError("The React dispatcher is missing method " + propertyName);
439
+ },
440
+ });
413
441
 
414
442
  const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
415
443
  if (primitiveStackCache !== null) return primitiveStackCache;
@@ -418,41 +446,38 @@ const getPrimitiveStackCache = (): Map<string, StackFrame[]> => {
418
446
  let capturedHookLog: HookLogEntry[];
419
447
 
420
448
  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(
449
+ dispatcher.useContext({ _currentValue: null });
450
+ dispatcher.useState(null);
451
+ dispatcher.useReducer((state: unknown) => state, null);
452
+ dispatcher.useRef(null);
453
+ dispatcher.useCacheRefresh();
454
+ dispatcher.useLayoutEffect(() => {});
455
+ dispatcher.useInsertionEffect(() => {});
456
+ dispatcher.useEffect(() => {});
457
+ dispatcher.useImperativeHandle(undefined);
458
+ dispatcher.useDebugValue(null);
459
+ dispatcher.useCallback(() => {});
460
+ dispatcher.useTransition();
461
+ dispatcher.useSyncExternalStore(
434
462
  () => () => {},
435
463
  () => null,
436
- () => null,
437
464
  );
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(() => {});
465
+ dispatcher.useDeferredValue(null);
466
+ dispatcher.useMemo(() => null);
467
+ dispatcher.useOptimistic(null);
468
+ dispatcher.useFormState((state: unknown) => state, null);
469
+ dispatcher.useActionState((state: unknown) => state, null);
470
+ dispatcher.useHostTransitionStatus();
471
+ dispatcher.use({ $$typeof: REACT_CONTEXT_TYPE, _currentValue: null });
472
+ const fulfilledPromise = Promise.resolve(null);
473
+ Reflect.set(fulfilledPromise, "status", "fulfilled");
474
+ Reflect.set(fulfilledPromise, "value", null);
475
+ dispatcher.use(fulfilledPromise);
476
+ try {
477
+ dispatcher.use(new Promise<never>(() => {}));
478
+ } catch {}
479
+ dispatcher.useId();
480
+ dispatcher.useEffectEvent(() => {});
456
481
  } finally {
457
482
  capturedHookLog = hookLog;
458
483
  hookLog = [];
@@ -681,14 +706,20 @@ const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | n
681
706
  const setupContexts = (contextMap: Map<ReactContext<unknown>, unknown>, fiber: Fiber): void => {
682
707
  let current: Fiber | null = fiber;
683
708
  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)) {
709
+ if (current.tag === getReactWorkTagsForFiber(current).ContextProvider) {
710
+ const providerType = current.type;
711
+ const nestedContext =
712
+ typeof providerType === "object" && providerType !== null && "_context" in providerType
713
+ ? providerType._context
714
+ : undefined;
715
+ const context = isReactContext(nestedContext)
716
+ ? nestedContext
717
+ : isReactContext(providerType)
718
+ ? providerType
719
+ : null;
720
+ if (context && !contextMap.has(context)) {
690
721
  contextMap.set(context, context._currentValue);
691
- context._currentValue = (current.memoizedProps as { value: unknown }).value;
722
+ context._currentValue = current.memoizedProps.value;
692
723
  }
693
724
  }
694
725
  current = current.return;
@@ -703,28 +734,26 @@ const restoreContexts = (contextMap: Map<ReactContext<unknown>, unknown>): void
703
734
 
704
735
  const handleRenderFunctionError = (error: unknown): void => {
705
736
  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;
737
+ if (error instanceof BippyUnsupportedHookError) throw error;
738
+ throw new BippyHookRenderError("Bippy couldn’t render the inspected component", error);
711
739
  };
712
740
 
713
741
  const resolveDefaultProps = (
714
- Component: unknown,
742
+ component: unknown,
715
743
  baseProps: Record<string, unknown>,
716
744
  ): Record<string, unknown> => {
717
745
  if (
718
- Component &&
719
- typeof Component === "object" &&
720
- "defaultProps" in Component &&
721
- Component.defaultProps
746
+ component &&
747
+ typeof component === "object" &&
748
+ "defaultProps" in component &&
749
+ typeof component.defaultProps === "object" &&
750
+ component.defaultProps !== null
722
751
  ) {
723
752
  const props = { ...baseProps };
724
- const defaultProps = Component.defaultProps as Record<string, unknown>;
725
- for (const propName in defaultProps) {
753
+ const defaultProps = component.defaultProps;
754
+ for (const [propName, value] of Object.entries(defaultProps)) {
726
755
  if (props[propName] === undefined) {
727
- props[propName] = defaultProps[propName];
756
+ props[propName] = value;
728
757
  }
729
758
  }
730
759
  return props;
@@ -736,11 +765,9 @@ const suppressConsole = (): Record<string, unknown> => {
736
765
  const originalMethods: Record<string, unknown> = {};
737
766
  for (const method in console) {
738
767
  try {
739
- originalMethods[method] = (console as Record<string, unknown>)[method];
740
- (console as Record<string, unknown>)[method] = () => {};
741
- } catch {
742
- /* noop */
743
- }
768
+ originalMethods[method] = Reflect.get(console, method);
769
+ Reflect.set(console, method, () => {});
770
+ } catch {}
744
771
  }
745
772
  return originalMethods;
746
773
  };
@@ -748,19 +775,17 @@ const suppressConsole = (): Record<string, unknown> => {
748
775
  const restoreConsole = (originalMethods: Record<string, unknown>): void => {
749
776
  for (const method in originalMethods) {
750
777
  try {
751
- (console as Record<string, unknown>)[method] = originalMethods[method];
752
- } catch {
753
- /* noop */
754
- }
778
+ Reflect.set(console, method, originalMethods[method]);
779
+ } catch {}
755
780
  }
756
781
  };
757
782
 
758
783
  const performDispatcherInspection = (
759
- dispatcherRef: DispatcherRefContainer,
784
+ dispatcherRef: RendererDispatcherRef,
760
785
  renderFn: () => void,
761
786
  ): HooksTree => {
762
787
  const previousDispatcher = getDispatcherFromRef(dispatcherRef);
763
- setDispatcherOnRef(dispatcherRef, DispatcherProxy);
788
+ setDispatcherOnRef(dispatcherRef, dispatcherProxy);
764
789
 
765
790
  let capturedHookLog: HookLogEntry[] = [];
766
791
  let ancestorStackError: Error | undefined;
@@ -780,49 +805,54 @@ const performDispatcherInspection = (
780
805
  return buildTree(rootStack, capturedHookLog);
781
806
  };
782
807
 
783
- const requireDispatcherRef = (): DispatcherRefContainer => {
808
+ const requireDispatcherRef = (): RendererDispatcherRef => {
784
809
  const dispatcherRef = getDispatcherRef();
785
810
  if (!dispatcherRef) {
786
- throw new Error(
787
- "No React renderer found. Make sure React is loaded and bippy's hook is installed.",
811
+ throw new BippyHookInspectionError(
812
+ "Bippy couldn’t find a React renderer. Load React and install Bippy’s hook.",
788
813
  );
789
814
  }
790
815
  return dispatcherRef;
791
816
  };
792
817
 
793
818
  const resolveContextDependency = (fiber: Fiber): void => {
794
- if (Object.prototype.hasOwnProperty.call(fiber, "dependencies")) {
819
+ const legacyDependenciesKey = ["dependencies_old", "dependencies_new"].find((key) =>
820
+ Object.hasOwn(fiber, key),
821
+ );
822
+ if (Object.hasOwn(fiber, "dependencies")) {
795
823
  const dependencies = fiber.dependencies;
796
824
  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;
825
+ } else if (legacyDependenciesKey) {
826
+ const dependencies: unknown = Reflect.get(fiber, legacyDependenciesKey);
827
+ const firstContext =
828
+ typeof dependencies === "object" && dependencies !== null && "firstContext" in dependencies
829
+ ? dependencies.firstContext
830
+ : null;
831
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
832
+ } else if (Object.hasOwn(fiber, "contextDependencies")) {
833
+ const contextDependencies: unknown = Reflect.get(fiber, "contextDependencies");
834
+ const firstContext =
835
+ typeof contextDependencies === "object" &&
836
+ contextDependencies !== null &&
837
+ "first" in contextDependencies
838
+ ? contextDependencies.first
839
+ : null;
840
+ currentContextDependency = isContextDependency(firstContext) ? firstContext : null;
812
841
  } else {
813
- throw new Error("Unsupported React version.");
842
+ throw new BippyHookInspectionError("Bippy doesn’t support this React version.");
814
843
  }
815
844
  };
816
845
 
817
846
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
818
847
  const dispatcherRef = requireDispatcherRef();
848
+ const workTags = getReactWorkTagsForFiber(fiber);
819
849
 
820
850
  if (
821
- fiber.tag !== FUNCTION_COMPONENT_TAG &&
822
- fiber.tag !== SIMPLE_MEMO_COMPONENT_TAG &&
823
- fiber.tag !== FORWARD_REF_TAG
851
+ fiber.tag !== workTags.FunctionComponent &&
852
+ fiber.tag !== workTags.SimpleMemoComponent &&
853
+ fiber.tag !== workTags.ForwardRef
824
854
  ) {
825
- throw new Error("Unknown Fiber. Needs to be a function component to inspect hooks.");
855
+ throw new BippyHookInspectionError("Hook inspection requires a function component Fiber.");
826
856
  }
827
857
 
828
858
  getPrimitiveStackCache();
@@ -830,19 +860,17 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
830
860
  currentHook = fiber.memoizedState;
831
861
  currentFiber = fiber;
832
862
 
833
- const debugThenableState =
834
- fiber.dependencies &&
835
- (fiber.dependencies as { _debugThenableState?: { thenables?: unknown[] } })._debugThenableState;
836
- const usedThenables = debugThenableState
837
- ? debugThenableState.thenables || debugThenableState
838
- : null;
863
+ const debugThenableState = fiber.dependencies?._debugThenableState;
864
+ const usedThenables = Array.isArray(debugThenableState)
865
+ ? debugThenableState
866
+ : debugThenableState?.thenables;
839
867
  currentThenableState = Array.isArray(usedThenables) ? usedThenables : null;
840
868
  currentThenableIndex = 0;
841
869
 
842
870
  resolveContextDependency(fiber);
843
871
 
844
872
  const type = fiber.type;
845
- let props = fiber.memoizedProps as Record<string, unknown>;
873
+ let props = fiber.memoizedProps;
846
874
  if (type !== fiber.elementType) {
847
875
  props = resolveDefaultProps(type, props);
848
876
  }
@@ -853,22 +881,27 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
853
881
  try {
854
882
  if (
855
883
  currentContextDependency !== null &&
856
- !Object.prototype.hasOwnProperty.call(currentContextDependency, "memoizedValue")
884
+ !Object.hasOwn(currentContextDependency, "memoizedValue")
857
885
  ) {
858
886
  setupContexts(contextMap, fiber);
859
887
  }
860
888
 
861
- if (fiber.tag === FORWARD_REF_TAG) {
889
+ if (fiber.tag === workTags.ForwardRef) {
890
+ if (!isForwardRefRenderType(type)) {
891
+ throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
892
+ }
862
893
  return performDispatcherInspection(dispatcherRef, () => {
863
- (type as { render: (props: Record<string, unknown>, ref: unknown) => unknown }).render(
864
- props,
865
- fiber.ref,
866
- );
894
+ type.render(props, fiber.ref);
867
895
  });
868
896
  }
869
897
 
898
+ if (typeof type !== "function") {
899
+ throw new BippyHookInspectionError(
900
+ "Function component fiber is missing its component function.",
901
+ );
902
+ }
870
903
  return performDispatcherInspection(dispatcherRef, () => {
871
- (type as (props: Record<string, unknown>) => unknown)(props);
904
+ type(props);
872
905
  });
873
906
  } finally {
874
907
  currentFiber = null;