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.
- package/LICENSE +1 -1
- package/README.md +32 -48
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +45 -60
- package/dist/core.d.ts +45 -60
- package/dist/core.js +1 -1
- package/dist/core2.cjs +9 -0
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +9 -0
- package/dist/errors.d.cts +420 -0
- package/dist/errors.d.ts +420 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.iife.js +1 -1
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +9 -1
- package/dist/install-hook-only.d.ts +9 -1
- package/dist/install-hook-only.iife.js +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +14 -5
- package/dist/source.d.cts +27 -11
- package/dist/source.d.ts +27 -11
- package/dist/source.js +14 -5
- package/package.json +22 -16
- package/src/core.ts +349 -328
- package/src/errors.ts +34 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +160 -161
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/react-internals/types.ts +197 -0
- package/src/source/constants.ts +1 -1
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +32 -25
- package/src/source/get-source.ts +5 -5
- package/src/source/index.ts +9 -1
- package/src/source/inspect-hooks.ts +215 -182
- package/src/source/owner-stack.ts +127 -166
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +1 -1
- package/src/source/parse-stack.ts +1 -3
- package/src/source/symbolication.ts +490 -90
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/react-refresh.cjs +0 -9
- package/dist/react-refresh.d.cts +0 -66
- package/dist/react-refresh.d.ts +0 -66
- package/dist/react-refresh.js +0 -9
- package/dist/unsubscribe.d.cts +0 -298
- package/dist/unsubscribe.d.ts +0 -298
- package/src/react-refresh/constants.ts +0 -9
- package/src/react-refresh/detect-hmr-transport.ts +0 -33
- package/src/react-refresh/index.ts +0 -173
- package/src/react-refresh/metro-hmr-transport.ts +0 -188
- package/src/react-refresh/next-webpack-hmr-transport.ts +0 -72
- package/src/react-refresh/normalize-hmr-file-path.ts +0 -24
- package/src/react-refresh/types.ts +0 -7
- package/src/react-refresh/vite-hmr-transport.ts +0 -116
- package/src/types.ts +0 -438
- package/src/unsubscribe.ts +0 -17
|
@@ -1,33 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { getDisplayName, getType, traverseFiber } from "../core.js";
|
|
2
|
+
import { _renderers, getRDTHook } from "../rdt-hook.js";
|
|
3
|
+
import { getReactWorkTagsForFiber } from "../react-internals/index.js";
|
|
4
|
+
import type {
|
|
5
5
|
Fiber,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
HostComponentTag,
|
|
10
|
-
HostHoistableTag,
|
|
11
|
-
HostSingletonTag,
|
|
12
|
-
LazyComponentTag,
|
|
13
|
-
SimpleMemoComponentTag,
|
|
14
|
-
SuspenseComponentTag,
|
|
15
|
-
SuspenseListComponentTag,
|
|
16
|
-
ViewTransitionComponentTag,
|
|
17
|
-
getDisplayName,
|
|
18
|
-
traverseFiber,
|
|
19
|
-
} from "../core.js";
|
|
20
|
-
import { ServerComponentInfo } from "../types.js";
|
|
6
|
+
RendererDispatcherRef,
|
|
7
|
+
ServerComponentInfo,
|
|
8
|
+
} from "../react-internals/index.js";
|
|
21
9
|
import {
|
|
10
|
+
REACT_STACK_BOTTOM_FRAME_PATTERNS,
|
|
22
11
|
SERVER_FRAME_MARKER,
|
|
23
12
|
SERVER_ENV_PATTERN,
|
|
24
13
|
SERVER_COMPONENT_URL_PREFIXES,
|
|
25
14
|
} from "./constants.js";
|
|
26
|
-
|
|
15
|
+
import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
|
|
27
16
|
import { parseDebugStack } from "./parse-debug-stack.js";
|
|
28
17
|
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
29
18
|
import { symbolicateStack } from "./symbolication.js";
|
|
30
19
|
|
|
20
|
+
interface RendererDispatcherSnapshot {
|
|
21
|
+
currentDispatcherRef: RendererDispatcherRef;
|
|
22
|
+
value: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
31
25
|
export const hasDebugStack = (
|
|
32
26
|
fiber: Fiber,
|
|
33
27
|
): fiber is Fiber & {
|
|
@@ -37,13 +31,7 @@ export const hasDebugStack = (
|
|
|
37
31
|
};
|
|
38
32
|
|
|
39
33
|
const isFiberOwner = (owner: Fiber | ServerComponentInfo): owner is Fiber =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// react's typings (and bippy's Fiber, which mirrors them) declare _debugOwner
|
|
43
|
-
// as a Fiber, but react 19 flight sets a ReactComponentInfo object for server
|
|
44
|
-
// component owners
|
|
45
|
-
const getDebugOwner = (fiber: Fiber): Fiber | ServerComponentInfo | undefined =>
|
|
46
|
-
fiber._debugOwner as Fiber | ServerComponentInfo | undefined;
|
|
34
|
+
"tag" in owner && typeof owner.tag === "number";
|
|
47
35
|
|
|
48
36
|
/**
|
|
49
37
|
* Locates a frame inside the fiber's own function body without invoking it:
|
|
@@ -90,26 +78,41 @@ export const getDefinitionFrameFromOwnedChild = (fiber: Fiber): StackFrame | nul
|
|
|
90
78
|
return null;
|
|
91
79
|
};
|
|
92
80
|
|
|
93
|
-
const
|
|
81
|
+
const getRendererDispatcherRefs = (): RendererDispatcherRef[] => {
|
|
94
82
|
const rdtHook = getRDTHook();
|
|
95
|
-
|
|
83
|
+
const renderers = new Set([..._renderers, ...rdtHook.renderers.values()]);
|
|
84
|
+
const currentDispatcherRefs: RendererDispatcherRef[] = [];
|
|
85
|
+
const seenCurrentDispatcherRefs = new Set<object>();
|
|
86
|
+
for (const renderer of renderers) {
|
|
96
87
|
const currentDispatcherRef = renderer.currentDispatcherRef;
|
|
97
|
-
if (currentDispatcherRef
|
|
98
|
-
|
|
88
|
+
if (!currentDispatcherRef || seenCurrentDispatcherRefs.has(currentDispatcherRef)) continue;
|
|
89
|
+
seenCurrentDispatcherRefs.add(currentDispatcherRef);
|
|
90
|
+
currentDispatcherRefs.push(currentDispatcherRef);
|
|
91
|
+
}
|
|
92
|
+
return currentDispatcherRefs;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const clearRendererDispatchers = (): RendererDispatcherSnapshot[] => {
|
|
96
|
+
const dispatcherSnapshots: RendererDispatcherSnapshot[] = [];
|
|
97
|
+
for (const currentDispatcherRef of getRendererDispatcherRefs()) {
|
|
98
|
+
const value =
|
|
99
|
+
"H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
|
|
100
|
+
dispatcherSnapshots.push({ currentDispatcherRef, value });
|
|
101
|
+
if ("H" in currentDispatcherRef) {
|
|
102
|
+
currentDispatcherRef.H = null;
|
|
103
|
+
} else {
|
|
104
|
+
currentDispatcherRef.current = null;
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
|
-
return
|
|
107
|
+
return dispatcherSnapshots;
|
|
102
108
|
};
|
|
103
109
|
|
|
104
|
-
const
|
|
105
|
-
for (const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
} else {
|
|
111
|
-
currentDispatcherRef.current = value;
|
|
112
|
-
}
|
|
110
|
+
const restoreRendererDispatchers = (dispatcherSnapshots: RendererDispatcherSnapshot[]): void => {
|
|
111
|
+
for (const { currentDispatcherRef, value } of dispatcherSnapshots) {
|
|
112
|
+
if ("H" in currentDispatcherRef) {
|
|
113
|
+
currentDispatcherRef.H = value;
|
|
114
|
+
} else {
|
|
115
|
+
currentDispatcherRef.current = value;
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
};
|
|
@@ -146,49 +149,31 @@ const describeNativeComponentFrame = (
|
|
|
146
149
|
return cachedFrame;
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
const previousPrepareStackTrace =
|
|
150
|
-
|
|
151
|
-
(Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
|
|
152
|
+
const previousPrepareStackTrace = getPrepareStackTrace();
|
|
153
|
+
setPrepareStackTrace(undefined);
|
|
152
154
|
reEntry = true;
|
|
153
155
|
|
|
154
|
-
const
|
|
155
|
-
setCurrentDispatcher(null);
|
|
156
|
+
const dispatcherSnapshots = clearRendererDispatchers();
|
|
156
157
|
const previousConsoleError = console.error;
|
|
157
158
|
const previousConsoleWarn = console.warn;
|
|
158
159
|
console.error = () => {};
|
|
159
160
|
console.warn = () => {};
|
|
160
161
|
try {
|
|
161
|
-
|
|
162
|
-
* Finding a common stack frame between sample and control errors can be
|
|
163
|
-
* tricky given the different types and levels of stack trace truncation from
|
|
164
|
-
* different JS VMs. So instead we'll attempt to control what that common
|
|
165
|
-
* frame should be through this object method:
|
|
166
|
-
* Having both the sample and control errors be in the function under the
|
|
167
|
-
* `DescribeNativeComponentFrameRoot` property, + setting the `name` and
|
|
168
|
-
* `displayName` properties of the function ensures that a stack
|
|
169
|
-
* frame exists that has the method name `DescribeNativeComponentFrameRoot` in
|
|
170
|
-
* it for both control and sample stacks.
|
|
171
|
-
*/
|
|
162
|
+
// HACK: Run the control and sample under the same property name so their stacks share a frame.
|
|
172
163
|
const RunInRootFrame = {
|
|
173
|
-
DetermineComponentFrameRoot() {
|
|
164
|
+
DetermineComponentFrameRoot(this: void) {
|
|
174
165
|
let control: unknown;
|
|
175
166
|
try {
|
|
176
|
-
// This should throw.
|
|
177
167
|
if (construct) {
|
|
178
|
-
// Something should be setting the props in the constructor.
|
|
179
168
|
const ThrowingConstructor = function () {
|
|
180
169
|
throw Error();
|
|
181
170
|
};
|
|
182
171
|
Object.defineProperty(ThrowingConstructor.prototype, "props", {
|
|
183
172
|
set: function () {
|
|
184
|
-
// We use a throwing setter instead of frozen or non-writable props
|
|
185
|
-
// because that won't throw in a non-strict mode function.
|
|
186
173
|
throw Error();
|
|
187
174
|
},
|
|
188
175
|
});
|
|
189
176
|
if (typeof Reflect === "object" && Reflect.construct) {
|
|
190
|
-
// We construct a different control for this case to include any extra
|
|
191
|
-
// frames added by the construct call.
|
|
192
177
|
try {
|
|
193
178
|
Reflect.construct(ThrowingConstructor, []);
|
|
194
179
|
} catch (caughtError) {
|
|
@@ -197,13 +182,11 @@ const describeNativeComponentFrame = (
|
|
|
197
182
|
Reflect.construct(component, [], ThrowingConstructor);
|
|
198
183
|
} else {
|
|
199
184
|
try {
|
|
200
|
-
|
|
201
|
-
ThrowingConstructor.call();
|
|
185
|
+
Function.prototype.apply.call(ThrowingConstructor, undefined, []);
|
|
202
186
|
} catch (caughtError) {
|
|
203
187
|
control = caughtError;
|
|
204
188
|
}
|
|
205
|
-
|
|
206
|
-
component.call(ThrowingConstructor.prototype);
|
|
189
|
+
Function.prototype.apply.call(component, ThrowingConstructor.prototype, []);
|
|
207
190
|
}
|
|
208
191
|
} else {
|
|
209
192
|
try {
|
|
@@ -211,22 +194,17 @@ const describeNativeComponentFrame = (
|
|
|
211
194
|
} catch (caughtError) {
|
|
212
195
|
control = caughtError;
|
|
213
196
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// TODO: Implement component stacks for async client components?
|
|
223
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- we literally check if this is a promise here
|
|
224
|
-
if (maybePromise && typeof maybePromise.catch === "function") {
|
|
225
|
-
maybePromise.catch(() => {});
|
|
197
|
+
const maybePromise = Function.prototype.apply.call(component, undefined, []);
|
|
198
|
+
if (
|
|
199
|
+
typeof maybePromise === "object" &&
|
|
200
|
+
maybePromise !== null &&
|
|
201
|
+
"catch" in maybePromise &&
|
|
202
|
+
typeof maybePromise.catch === "function"
|
|
203
|
+
) {
|
|
204
|
+
maybePromise.catch(() => undefined);
|
|
226
205
|
}
|
|
227
206
|
}
|
|
228
207
|
} catch (sample: unknown) {
|
|
229
|
-
// This is inlined manually because closure doesn't do it for us.
|
|
230
208
|
if (
|
|
231
209
|
sample instanceof Error &&
|
|
232
210
|
control instanceof Error &&
|
|
@@ -239,21 +217,18 @@ const describeNativeComponentFrame = (
|
|
|
239
217
|
},
|
|
240
218
|
};
|
|
241
219
|
|
|
242
|
-
|
|
243
|
-
|
|
220
|
+
Object.defineProperty(RunInRootFrame.DetermineComponentFrameRoot, "displayName", {
|
|
221
|
+
value: "DetermineComponentFrameRoot",
|
|
222
|
+
});
|
|
244
223
|
const namePropDescriptor = Object.getOwnPropertyDescriptor(
|
|
245
224
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
246
225
|
RunInRootFrame.DetermineComponentFrameRoot,
|
|
247
226
|
"name",
|
|
248
227
|
);
|
|
249
|
-
// Before ES6, the `name` property was not configurable.
|
|
250
228
|
if (namePropDescriptor?.configurable) {
|
|
251
|
-
// V8 utilizes a function's `name` property when generating a stack trace.
|
|
252
229
|
Object.defineProperty(
|
|
253
230
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
254
231
|
RunInRootFrame.DetermineComponentFrameRoot,
|
|
255
|
-
// Configurable properties can be updated even if its writable descriptor
|
|
256
|
-
// is set to `false`.
|
|
257
232
|
"name",
|
|
258
233
|
{ value: "DetermineComponentFrameRoot" },
|
|
259
234
|
);
|
|
@@ -338,14 +313,14 @@ const describeNativeComponentFrame = (
|
|
|
338
313
|
} finally {
|
|
339
314
|
reEntry = false;
|
|
340
315
|
|
|
341
|
-
|
|
316
|
+
setPrepareStackTrace(previousPrepareStackTrace);
|
|
342
317
|
|
|
343
|
-
|
|
318
|
+
restoreRendererDispatchers(dispatcherSnapshots);
|
|
344
319
|
console.error = previousConsoleError;
|
|
345
320
|
console.warn = previousConsoleWarn;
|
|
346
321
|
}
|
|
347
322
|
|
|
348
|
-
const componentName =
|
|
323
|
+
const componentName = getDisplayName(component);
|
|
349
324
|
const syntheticFrame = componentName ? describeBuiltInComponentFrame(componentName) : "";
|
|
350
325
|
componentFrameCache.set(component, syntheticFrame);
|
|
351
326
|
return syntheticFrame;
|
|
@@ -353,35 +328,34 @@ const describeNativeComponentFrame = (
|
|
|
353
328
|
|
|
354
329
|
// https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
|
|
355
330
|
export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
|
|
356
|
-
const tag = fiber.tag as number;
|
|
357
331
|
let stackFrame = "";
|
|
358
|
-
|
|
359
|
-
|
|
332
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
333
|
+
switch (fiber.tag) {
|
|
334
|
+
case workTags.ActivityComponent:
|
|
360
335
|
stackFrame = describeBuiltInComponentFrame("Activity");
|
|
361
336
|
break;
|
|
362
|
-
case
|
|
337
|
+
case workTags.ClassComponent:
|
|
363
338
|
stackFrame = describeNativeComponentFrame(fiber.type, true);
|
|
364
339
|
break;
|
|
365
|
-
case
|
|
366
|
-
stackFrame = describeNativeComponentFrame(
|
|
367
|
-
(fiber.type as { render: React.ComponentType<unknown> }).render,
|
|
368
|
-
false,
|
|
369
|
-
);
|
|
340
|
+
case workTags.ForwardRef:
|
|
341
|
+
stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
|
|
370
342
|
break;
|
|
371
|
-
case
|
|
372
|
-
case
|
|
343
|
+
case workTags.FunctionComponent:
|
|
344
|
+
case workTags.SimpleMemoComponent:
|
|
373
345
|
stackFrame = describeNativeComponentFrame(fiber.type, false);
|
|
374
346
|
break;
|
|
375
|
-
case
|
|
376
|
-
case
|
|
377
|
-
case
|
|
378
|
-
|
|
347
|
+
case workTags.HostComponent:
|
|
348
|
+
case workTags.HostHoistable:
|
|
349
|
+
case workTags.HostSingleton:
|
|
350
|
+
if (typeof fiber.type === "string") {
|
|
351
|
+
stackFrame = describeBuiltInComponentFrame(fiber.type);
|
|
352
|
+
}
|
|
379
353
|
break;
|
|
380
|
-
case
|
|
354
|
+
case workTags.LazyComponent:
|
|
381
355
|
// TODO: When we support Thenables as component types we should rename this.
|
|
382
356
|
stackFrame = describeBuiltInComponentFrame("Lazy");
|
|
383
357
|
break;
|
|
384
|
-
case
|
|
358
|
+
case workTags.SuspenseComponent:
|
|
385
359
|
if (fiber.child !== childFiber && childFiber !== null) {
|
|
386
360
|
// If we came from the second Fiber then we're in the Suspense Fallback.
|
|
387
361
|
stackFrame = describeBuiltInComponentFrame("Suspense Fallback");
|
|
@@ -389,10 +363,10 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
|
|
|
389
363
|
stackFrame = describeBuiltInComponentFrame("Suspense");
|
|
390
364
|
}
|
|
391
365
|
break;
|
|
392
|
-
case
|
|
366
|
+
case workTags.SuspenseListComponent:
|
|
393
367
|
stackFrame = describeBuiltInComponentFrame("SuspenseList");
|
|
394
368
|
break;
|
|
395
|
-
case
|
|
369
|
+
case workTags.ViewTransitionComponent:
|
|
396
370
|
// Note: enableViewTransition feature flag is not available in this codebase,
|
|
397
371
|
// so we'll always include ViewTransition
|
|
398
372
|
stackFrame = describeBuiltInComponentFrame("ViewTransition");
|
|
@@ -421,8 +395,8 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
421
395
|
// Since we don't have __DEV__ in this codebase, we'll check for _debugInfo
|
|
422
396
|
const debugInfo = currentFiber._debugInfo;
|
|
423
397
|
if (debugInfo && Array.isArray(debugInfo)) {
|
|
424
|
-
for (let
|
|
425
|
-
const debugEntry = debugInfo[
|
|
398
|
+
for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
|
|
399
|
+
const debugEntry = debugInfo[debugInfoIndex];
|
|
426
400
|
if (typeof debugEntry.name === "string") {
|
|
427
401
|
componentStack += describeDebugInfoFrame(debugEntry.name, debugEntry.env);
|
|
428
402
|
}
|
|
@@ -435,7 +409,7 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
435
409
|
return componentStack;
|
|
436
410
|
} catch (error) {
|
|
437
411
|
if (error instanceof Error) {
|
|
438
|
-
return `\
|
|
412
|
+
return `\nBippy couldn’t generate the stack: ${error.message}\n${error.stack}`;
|
|
439
413
|
}
|
|
440
414
|
return "";
|
|
441
415
|
}
|
|
@@ -460,14 +434,10 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
460
434
|
* @see https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js#L12
|
|
461
435
|
*/
|
|
462
436
|
export const formatOwnerStack = (stack: string): string => {
|
|
463
|
-
const prevPrepareStackTrace = Error.prepareStackTrace;
|
|
464
|
-
// HACK: V8 API allows undefined but bun-types declares it as non-optional
|
|
465
|
-
(Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
|
|
466
437
|
let formattedStack = stack;
|
|
467
438
|
if (!formattedStack) {
|
|
468
439
|
return "";
|
|
469
440
|
}
|
|
470
|
-
Error.prepareStackTrace = prevPrepareStackTrace;
|
|
471
441
|
|
|
472
442
|
if (formattedStack.startsWith("Error: react-stack-top-frame\n")) {
|
|
473
443
|
// V8's default formatting prefixes with the error message which we
|
|
@@ -480,8 +450,7 @@ export const formatOwnerStack = (stack: string): string => {
|
|
|
480
450
|
formattedStack = formattedStack.slice(firstNewlineIndex + 1);
|
|
481
451
|
}
|
|
482
452
|
let bottomFrameIndex = Math.max(
|
|
483
|
-
formattedStack.indexOf(
|
|
484
|
-
formattedStack.indexOf("react-stack-bottom-frame"),
|
|
453
|
+
...REACT_STACK_BOTTOM_FRAME_PATTERNS.map((pattern) => formattedStack.indexOf(pattern)),
|
|
485
454
|
);
|
|
486
455
|
if (bottomFrameIndex !== -1) {
|
|
487
456
|
bottomFrameIndex = formattedStack.lastIndexOf("\n", bottomFrameIndex);
|
|
@@ -498,11 +467,6 @@ export const formatOwnerStack = (stack: string): string => {
|
|
|
498
467
|
return formattedStack;
|
|
499
468
|
};
|
|
500
469
|
|
|
501
|
-
interface DebugStackEntry {
|
|
502
|
-
componentName: string;
|
|
503
|
-
stackFrames: StackFrame[];
|
|
504
|
-
}
|
|
505
|
-
|
|
506
470
|
const isReactServerComponentFrame = (stackFrame: StackFrame): boolean =>
|
|
507
471
|
Boolean(
|
|
508
472
|
stackFrame.functionName && stackFrame.fileName && isServerComponentUrl(stackFrame.fileName),
|
|
@@ -514,24 +478,22 @@ const areStackFramesEqual = (firstFrame: StackFrame, secondFrame: StackFrame): b
|
|
|
514
478
|
firstFrame.columnNumber === secondFrame.columnNumber;
|
|
515
479
|
|
|
516
480
|
const buildFunctionNameToRscFramesMap = (
|
|
517
|
-
|
|
481
|
+
debugStackFrames: StackFrame[],
|
|
518
482
|
): Map<string, StackFrame[]> => {
|
|
519
483
|
const functionNameToRscFrames = new Map<string, StackFrame[]>();
|
|
520
484
|
|
|
521
|
-
for (const
|
|
522
|
-
|
|
523
|
-
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
485
|
+
for (const stackFrame of debugStackFrames) {
|
|
486
|
+
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
524
487
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
488
|
+
const functionName = stackFrame.functionName!;
|
|
489
|
+
const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
|
|
490
|
+
const isDuplicateFrame = framesForFunction.some((existingFrame) =>
|
|
491
|
+
areStackFramesEqual(existingFrame, stackFrame),
|
|
492
|
+
);
|
|
530
493
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
494
|
+
if (!isDuplicateFrame) {
|
|
495
|
+
framesForFunction.push(stackFrame);
|
|
496
|
+
functionNameToRscFrames.set(functionName, framesForFunction);
|
|
535
497
|
}
|
|
536
498
|
}
|
|
537
499
|
|
|
@@ -592,7 +554,7 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
|
|
|
592
554
|
while (owner) {
|
|
593
555
|
if (isFiberOwner(owner)) {
|
|
594
556
|
const ownerFiber: Fiber = owner;
|
|
595
|
-
owner =
|
|
557
|
+
owner = ownerFiber._debugOwner;
|
|
596
558
|
if (owner && hasDebugStack(ownerFiber)) {
|
|
597
559
|
const { frames, isTrusted } = parseDebugStack(ownerFiber._debugStack);
|
|
598
560
|
if (isTrusted) {
|
|
@@ -616,51 +578,39 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
|
|
|
616
578
|
return ownerStackFrames;
|
|
617
579
|
};
|
|
618
580
|
|
|
619
|
-
const
|
|
620
|
-
const
|
|
581
|
+
const getDebugStackFrames = (rootFiber: Fiber): StackFrame[] => {
|
|
582
|
+
const debugStackFrames: StackFrame[] = [];
|
|
621
583
|
|
|
622
584
|
traverseFiber(
|
|
623
585
|
rootFiber,
|
|
624
586
|
(currentFiber) => {
|
|
625
587
|
if (!hasDebugStack(currentFiber)) return;
|
|
626
588
|
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
debugStackEntries.push({
|
|
633
|
-
componentName,
|
|
634
|
-
stackFrames: parseStack(formatOwnerStack(currentFiber._debugStack?.stack)),
|
|
635
|
-
});
|
|
589
|
+
const { frames, isTrusted } = parseDebugStack(currentFiber._debugStack);
|
|
590
|
+
if (isTrusted) {
|
|
591
|
+
debugStackFrames.push(...frames);
|
|
592
|
+
}
|
|
636
593
|
},
|
|
637
594
|
true,
|
|
638
595
|
);
|
|
639
596
|
|
|
640
|
-
return
|
|
597
|
+
return debugStackFrames;
|
|
641
598
|
};
|
|
642
599
|
|
|
643
600
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
* with a throwing dispatcher; server frames are enriched from debug stacks by
|
|
648
|
-
* name matching. Works on every React version.
|
|
601
|
+
* The unsymbolicated frames behind {@link getParentStack}: bundle-space
|
|
602
|
+
* locations from re-invoking each component with a throwing dispatcher,
|
|
603
|
+
* with server frames enriched from debug stacks by name matching.
|
|
649
604
|
*/
|
|
650
|
-
export const
|
|
651
|
-
fiber
|
|
652
|
-
shouldCache = true,
|
|
653
|
-
fetchFunction?: (url: string) => Promise<Response>,
|
|
654
|
-
): Promise<StackFrame[]> => {
|
|
655
|
-
const debugStackEntries = getDebugStackEntries(fiber);
|
|
605
|
+
export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
|
|
606
|
+
const debugStackFrames = getDebugStackFrames(fiber);
|
|
656
607
|
const fallbackStackFrames = parseStack(getFallbackParentStack(fiber));
|
|
657
|
-
const functionNameToRscFrames = buildFunctionNameToRscFramesMap(
|
|
608
|
+
const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackFrames);
|
|
658
609
|
const functionNameToUsageIndex = new Map<string, number>();
|
|
659
610
|
|
|
660
611
|
const enrichedStackFrames = fallbackStackFrames.map((stackFrame): StackFrame => {
|
|
661
612
|
const isServerFrame =
|
|
662
|
-
|
|
663
|
-
(stackFrame.source != null && SERVER_ENV_PATTERN.test(stackFrame.source));
|
|
613
|
+
stackFrame.source !== undefined && SERVER_ENV_PATTERN.test(stackFrame.source);
|
|
664
614
|
|
|
665
615
|
if (isServerFrame) {
|
|
666
616
|
return getEnrichedServerStackFrame(
|
|
@@ -673,15 +623,26 @@ export const getParentStack = async (
|
|
|
673
623
|
return stackFrame;
|
|
674
624
|
});
|
|
675
625
|
|
|
676
|
-
|
|
626
|
+
return enrichedStackFrames.filter((stackFrame, index, frames) => {
|
|
677
627
|
if (index === 0) return true;
|
|
678
628
|
const previousFrame = frames[index - 1];
|
|
679
629
|
return stackFrame.functionName !== previousFrame.functionName;
|
|
680
630
|
});
|
|
681
|
-
|
|
682
|
-
return symbolicateStack(deduplicatedStackFrames, shouldCache, fetchFunction);
|
|
683
631
|
};
|
|
684
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Returns a stack of ALL ancestor components in the render tree (the fiber's
|
|
635
|
+
* `return` chain), including wrappers that render `{children}` without having
|
|
636
|
+
* created this fiber's JSX. Locations come from re-invoking each component
|
|
637
|
+
* with a throwing dispatcher; server frames are enriched from debug stacks by
|
|
638
|
+
* name matching. Works on every React version.
|
|
639
|
+
*/
|
|
640
|
+
export const getParentStack = async (
|
|
641
|
+
fiber: Fiber,
|
|
642
|
+
shouldCache = true,
|
|
643
|
+
fetchFunction?: (url: string) => Promise<Response>,
|
|
644
|
+
): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
|
|
645
|
+
|
|
685
646
|
// an owner frame is only actionable if it can point an editor somewhere:
|
|
686
647
|
// it needs a file location and must not be ignore-listed bundler/framework code
|
|
687
648
|
const isLocatableFrame = (stackFrame: StackFrame): boolean =>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
|
|
2
|
+
import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
|
|
2
3
|
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
3
4
|
|
|
4
5
|
interface V8CallSite {
|
|
@@ -117,14 +118,13 @@ export const parseDebugStack = (debugStack: Error): ParsedDebugStack => {
|
|
|
117
118
|
}
|
|
118
119
|
return stackString;
|
|
119
120
|
};
|
|
120
|
-
const previousPrepareStackTrace =
|
|
121
|
-
|
|
122
|
-
Error.prepareStackTrace = collectFramesAndFormatStack as typeof Error.prepareStackTrace;
|
|
121
|
+
const previousPrepareStackTrace = getPrepareStackTrace();
|
|
122
|
+
setPrepareStackTrace(collectFramesAndFormatStack);
|
|
123
123
|
let stackString: string;
|
|
124
124
|
try {
|
|
125
125
|
stackString = String(debugStack.stack);
|
|
126
126
|
} finally {
|
|
127
|
-
|
|
127
|
+
setPrepareStackTrace(previousPrepareStackTrace);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const result = structuredResult ?? parseMaterializedStack(stackString);
|
|
@@ -35,7 +35,7 @@ const flattenHooksTree = (hooksTree: HooksTree): HooksNode[] => {
|
|
|
35
35
|
|
|
36
36
|
const findSourceContentByFileName = (
|
|
37
37
|
sources: string[],
|
|
38
|
-
sourcesContent: string
|
|
38
|
+
sourcesContent: Array<string | null> | undefined,
|
|
39
39
|
fileName: string,
|
|
40
40
|
): string | null => {
|
|
41
41
|
if (!sourcesContent) return null;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export interface StackFrame {
|
|
2
|
-
args?: unknown[];
|
|
3
2
|
columnNumber?: number;
|
|
4
3
|
lineNumber?: number;
|
|
5
4
|
// start of the enclosing function (the definition, not the call site);
|
|
@@ -17,7 +16,6 @@ export interface StackFrame {
|
|
|
17
16
|
|
|
18
17
|
export interface ParseOptions {
|
|
19
18
|
slice?: number | [number, number];
|
|
20
|
-
allowEmpty?: boolean;
|
|
21
19
|
includeInElement?: boolean;
|
|
22
20
|
}
|
|
23
21
|
|
|
@@ -67,7 +65,7 @@ export const extractLocation = (
|
|
|
67
65
|
};
|
|
68
66
|
|
|
69
67
|
const applySlice = <T>(lines: T[], options?: ParseOptions): T[] => {
|
|
70
|
-
if (options && options.slice
|
|
68
|
+
if (options && options.slice !== null && options.slice !== undefined) {
|
|
71
69
|
if (Array.isArray(options.slice)) return lines.slice(options.slice[0], options.slice[1]);
|
|
72
70
|
return lines.slice(0, options.slice);
|
|
73
71
|
}
|