bippy 0.6.1 → 0.7.0-dev.07e6032
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 +223 -463
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +410 -0
- package/dist/errors.d.ts +410 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +154 -3
- package/dist/index.d.ts +154 -3
- 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.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 +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +14 -5
- package/package.json +26 -27
- package/src/core.ts +367 -685
- package/src/errors.ts +34 -0
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +6 -2
- package/src/rdt-hook.ts +177 -156
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +73 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/react-internals/types.ts +186 -0
- package/src/react.ts +76 -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 +44 -40
- package/src/source/get-source.ts +43 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +121 -174
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +709 -120
- package/dist/core.d.cts +0 -214
- package/dist/core.d.ts +0 -214
- package/dist/core2.d.cts +0 -3
- package/dist/core2.d.ts +0 -3
- package/dist/get-source.cjs +0 -19
- package/dist/get-source.js +0 -19
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- 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,32 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
ClassComponentTag,
|
|
1
|
+
import { getDisplayName, getType, traverseFiber } from "../core.js";
|
|
2
|
+
import { getReactWorkTagsForFiber } from "../react-internals/index.js";
|
|
3
|
+
import type {
|
|
5
4
|
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";
|
|
5
|
+
RendererDispatcherRef,
|
|
6
|
+
ServerComponentInfo,
|
|
7
|
+
} from "../react-internals/index.js";
|
|
21
8
|
import {
|
|
9
|
+
REACT_STACK_BOTTOM_FRAME_PATTERNS,
|
|
22
10
|
SERVER_FRAME_MARKER,
|
|
23
11
|
SERVER_ENV_PATTERN,
|
|
24
12
|
SERVER_COMPONENT_URL_PREFIXES,
|
|
25
13
|
} from "./constants.js";
|
|
26
|
-
|
|
14
|
+
import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
|
|
27
15
|
import { parseDebugStack } from "./parse-debug-stack.js";
|
|
28
|
-
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
29
|
-
import {
|
|
16
|
+
import { parseStack, type StackFrame } from "./parse-stack.js";
|
|
17
|
+
import {
|
|
18
|
+
getRendererDispatcherRefs,
|
|
19
|
+
readDispatcher,
|
|
20
|
+
writeDispatcher,
|
|
21
|
+
} from "./renderer-dispatchers.js";
|
|
22
|
+
import { symbolicateStack, type SourceFetch } from "./symbolication.js";
|
|
23
|
+
|
|
24
|
+
interface RendererDispatcherSnapshot {
|
|
25
|
+
currentDispatcherRef: RendererDispatcherRef;
|
|
26
|
+
value: unknown;
|
|
27
|
+
}
|
|
30
28
|
|
|
31
29
|
export const hasDebugStack = (
|
|
32
30
|
fiber: Fiber,
|
|
@@ -37,13 +35,7 @@ export const hasDebugStack = (
|
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
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;
|
|
38
|
+
"tag" in owner && typeof owner.tag === "number";
|
|
47
39
|
|
|
48
40
|
/**
|
|
49
41
|
* Locates a frame inside the fiber's own function body without invoking it:
|
|
@@ -90,27 +82,18 @@ export const getDefinitionFrameFromOwnedChild = (fiber: Fiber): StackFrame | nul
|
|
|
90
82
|
return null;
|
|
91
83
|
};
|
|
92
84
|
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
for (const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return "H" in currentDispatcherRef ? currentDispatcherRef.H : currentDispatcherRef.current;
|
|
99
|
-
}
|
|
85
|
+
const clearRendererDispatchers = (): RendererDispatcherSnapshot[] => {
|
|
86
|
+
const dispatcherSnapshots: RendererDispatcherSnapshot[] = [];
|
|
87
|
+
for (const currentDispatcherRef of getRendererDispatcherRefs()) {
|
|
88
|
+
dispatcherSnapshots.push({ currentDispatcherRef, value: readDispatcher(currentDispatcherRef) });
|
|
89
|
+
writeDispatcher(currentDispatcherRef, null);
|
|
100
90
|
}
|
|
101
|
-
return
|
|
91
|
+
return dispatcherSnapshots;
|
|
102
92
|
};
|
|
103
93
|
|
|
104
|
-
const
|
|
105
|
-
for (const
|
|
106
|
-
|
|
107
|
-
if (currentDispatcherRef && typeof currentDispatcherRef === "object") {
|
|
108
|
-
if ("H" in currentDispatcherRef) {
|
|
109
|
-
currentDispatcherRef.H = value;
|
|
110
|
-
} else {
|
|
111
|
-
currentDispatcherRef.current = value;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
94
|
+
const restoreRendererDispatchers = (dispatcherSnapshots: RendererDispatcherSnapshot[]): void => {
|
|
95
|
+
for (const { currentDispatcherRef, value } of dispatcherSnapshots) {
|
|
96
|
+
writeDispatcher(currentDispatcherRef, value);
|
|
114
97
|
}
|
|
115
98
|
};
|
|
116
99
|
|
|
@@ -146,49 +129,31 @@ const describeNativeComponentFrame = (
|
|
|
146
129
|
return cachedFrame;
|
|
147
130
|
}
|
|
148
131
|
|
|
149
|
-
const previousPrepareStackTrace =
|
|
150
|
-
|
|
151
|
-
(Error as { prepareStackTrace?: typeof Error.prepareStackTrace }).prepareStackTrace = undefined;
|
|
132
|
+
const previousPrepareStackTrace = getPrepareStackTrace();
|
|
133
|
+
setPrepareStackTrace(undefined);
|
|
152
134
|
reEntry = true;
|
|
153
135
|
|
|
154
|
-
const
|
|
155
|
-
setCurrentDispatcher(null);
|
|
136
|
+
const dispatcherSnapshots = clearRendererDispatchers();
|
|
156
137
|
const previousConsoleError = console.error;
|
|
157
138
|
const previousConsoleWarn = console.warn;
|
|
158
139
|
console.error = () => {};
|
|
159
140
|
console.warn = () => {};
|
|
160
141
|
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
|
-
*/
|
|
142
|
+
// HACK: Run the control and sample under the same property name so their stacks share a frame.
|
|
172
143
|
const RunInRootFrame = {
|
|
173
|
-
DetermineComponentFrameRoot() {
|
|
144
|
+
DetermineComponentFrameRoot(this: void) {
|
|
174
145
|
let control: unknown;
|
|
175
146
|
try {
|
|
176
|
-
// This should throw.
|
|
177
147
|
if (construct) {
|
|
178
|
-
// Something should be setting the props in the constructor.
|
|
179
148
|
const ThrowingConstructor = function () {
|
|
180
149
|
throw Error();
|
|
181
150
|
};
|
|
182
151
|
Object.defineProperty(ThrowingConstructor.prototype, "props", {
|
|
183
152
|
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
153
|
throw Error();
|
|
187
154
|
},
|
|
188
155
|
});
|
|
189
156
|
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
157
|
try {
|
|
193
158
|
Reflect.construct(ThrowingConstructor, []);
|
|
194
159
|
} catch (caughtError) {
|
|
@@ -197,13 +162,11 @@ const describeNativeComponentFrame = (
|
|
|
197
162
|
Reflect.construct(component, [], ThrowingConstructor);
|
|
198
163
|
} else {
|
|
199
164
|
try {
|
|
200
|
-
|
|
201
|
-
ThrowingConstructor.call();
|
|
165
|
+
Function.prototype.apply.call(ThrowingConstructor, undefined, []);
|
|
202
166
|
} catch (caughtError) {
|
|
203
167
|
control = caughtError;
|
|
204
168
|
}
|
|
205
|
-
|
|
206
|
-
component.call(ThrowingConstructor.prototype);
|
|
169
|
+
Function.prototype.apply.call(component, ThrowingConstructor.prototype, []);
|
|
207
170
|
}
|
|
208
171
|
} else {
|
|
209
172
|
try {
|
|
@@ -211,22 +174,17 @@ const describeNativeComponentFrame = (
|
|
|
211
174
|
} catch (caughtError) {
|
|
212
175
|
control = caughtError;
|
|
213
176
|
}
|
|
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(() => {});
|
|
177
|
+
const maybePromise = Function.prototype.apply.call(component, undefined, []);
|
|
178
|
+
if (
|
|
179
|
+
typeof maybePromise === "object" &&
|
|
180
|
+
maybePromise !== null &&
|
|
181
|
+
"catch" in maybePromise &&
|
|
182
|
+
typeof maybePromise.catch === "function"
|
|
183
|
+
) {
|
|
184
|
+
maybePromise.catch(() => undefined);
|
|
226
185
|
}
|
|
227
186
|
}
|
|
228
187
|
} catch (sample: unknown) {
|
|
229
|
-
// This is inlined manually because closure doesn't do it for us.
|
|
230
188
|
if (
|
|
231
189
|
sample instanceof Error &&
|
|
232
190
|
control instanceof Error &&
|
|
@@ -239,21 +197,18 @@ const describeNativeComponentFrame = (
|
|
|
239
197
|
},
|
|
240
198
|
};
|
|
241
199
|
|
|
242
|
-
|
|
243
|
-
|
|
200
|
+
Object.defineProperty(RunInRootFrame.DetermineComponentFrameRoot, "displayName", {
|
|
201
|
+
value: "DetermineComponentFrameRoot",
|
|
202
|
+
});
|
|
244
203
|
const namePropDescriptor = Object.getOwnPropertyDescriptor(
|
|
245
204
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
246
205
|
RunInRootFrame.DetermineComponentFrameRoot,
|
|
247
206
|
"name",
|
|
248
207
|
);
|
|
249
|
-
// Before ES6, the `name` property was not configurable.
|
|
250
208
|
if (namePropDescriptor?.configurable) {
|
|
251
|
-
// V8 utilizes a function's `name` property when generating a stack trace.
|
|
252
209
|
Object.defineProperty(
|
|
253
210
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
254
211
|
RunInRootFrame.DetermineComponentFrameRoot,
|
|
255
|
-
// Configurable properties can be updated even if its writable descriptor
|
|
256
|
-
// is set to `false`.
|
|
257
212
|
"name",
|
|
258
213
|
{ value: "DetermineComponentFrameRoot" },
|
|
259
214
|
);
|
|
@@ -317,6 +272,12 @@ const describeNativeComponentFrame = (
|
|
|
317
272
|
if (controlIndex < 0 || sampleLines[sampleIndex] !== controlLines[controlIndex]) {
|
|
318
273
|
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
|
|
319
274
|
let stackFrame = `\n${sampleLines[sampleIndex].replace(" at new ", " at ")}`;
|
|
275
|
+
const [parsedStackFrame] = parseStack(stackFrame);
|
|
276
|
+
if (!parsedStackFrame?.fileName) {
|
|
277
|
+
// Deeper sample frames belong to React internals, not the
|
|
278
|
+
// component, so fall back to the synthetic frame instead.
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
320
281
|
|
|
321
282
|
const displayName = getDisplayName(component);
|
|
322
283
|
// If our component frame is labeled "<anonymous>"
|
|
@@ -338,14 +299,14 @@ const describeNativeComponentFrame = (
|
|
|
338
299
|
} finally {
|
|
339
300
|
reEntry = false;
|
|
340
301
|
|
|
341
|
-
|
|
302
|
+
setPrepareStackTrace(previousPrepareStackTrace);
|
|
342
303
|
|
|
343
|
-
|
|
304
|
+
restoreRendererDispatchers(dispatcherSnapshots);
|
|
344
305
|
console.error = previousConsoleError;
|
|
345
306
|
console.warn = previousConsoleWarn;
|
|
346
307
|
}
|
|
347
308
|
|
|
348
|
-
const componentName =
|
|
309
|
+
const componentName = getDisplayName(component);
|
|
349
310
|
const syntheticFrame = componentName ? describeBuiltInComponentFrame(componentName) : "";
|
|
350
311
|
componentFrameCache.set(component, syntheticFrame);
|
|
351
312
|
return syntheticFrame;
|
|
@@ -353,35 +314,34 @@ const describeNativeComponentFrame = (
|
|
|
353
314
|
|
|
354
315
|
// https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
|
|
355
316
|
export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
|
|
356
|
-
const tag = fiber.tag as number;
|
|
357
317
|
let stackFrame = "";
|
|
358
|
-
|
|
359
|
-
|
|
318
|
+
const workTags = getReactWorkTagsForFiber(fiber);
|
|
319
|
+
switch (fiber.tag) {
|
|
320
|
+
case workTags.ActivityComponent:
|
|
360
321
|
stackFrame = describeBuiltInComponentFrame("Activity");
|
|
361
322
|
break;
|
|
362
|
-
case
|
|
323
|
+
case workTags.ClassComponent:
|
|
363
324
|
stackFrame = describeNativeComponentFrame(fiber.type, true);
|
|
364
325
|
break;
|
|
365
|
-
case
|
|
366
|
-
stackFrame = describeNativeComponentFrame(
|
|
367
|
-
(fiber.type as { render: React.ComponentType<unknown> }).render,
|
|
368
|
-
false,
|
|
369
|
-
);
|
|
326
|
+
case workTags.ForwardRef:
|
|
327
|
+
stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
|
|
370
328
|
break;
|
|
371
|
-
case
|
|
372
|
-
case
|
|
373
|
-
stackFrame = describeNativeComponentFrame(fiber.type, false);
|
|
329
|
+
case workTags.FunctionComponent:
|
|
330
|
+
case workTags.SimpleMemoComponent:
|
|
331
|
+
stackFrame = describeNativeComponentFrame(getType(fiber.type) ?? fiber.type, false);
|
|
374
332
|
break;
|
|
375
|
-
case
|
|
376
|
-
case
|
|
377
|
-
case
|
|
378
|
-
|
|
333
|
+
case workTags.HostComponent:
|
|
334
|
+
case workTags.HostHoistable:
|
|
335
|
+
case workTags.HostSingleton:
|
|
336
|
+
if (typeof fiber.type === "string") {
|
|
337
|
+
stackFrame = describeBuiltInComponentFrame(fiber.type);
|
|
338
|
+
}
|
|
379
339
|
break;
|
|
380
|
-
case
|
|
340
|
+
case workTags.LazyComponent:
|
|
381
341
|
// TODO: When we support Thenables as component types we should rename this.
|
|
382
342
|
stackFrame = describeBuiltInComponentFrame("Lazy");
|
|
383
343
|
break;
|
|
384
|
-
case
|
|
344
|
+
case workTags.SuspenseComponent:
|
|
385
345
|
if (fiber.child !== childFiber && childFiber !== null) {
|
|
386
346
|
// If we came from the second Fiber then we're in the Suspense Fallback.
|
|
387
347
|
stackFrame = describeBuiltInComponentFrame("Suspense Fallback");
|
|
@@ -389,10 +349,10 @@ export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string =>
|
|
|
389
349
|
stackFrame = describeBuiltInComponentFrame("Suspense");
|
|
390
350
|
}
|
|
391
351
|
break;
|
|
392
|
-
case
|
|
352
|
+
case workTags.SuspenseListComponent:
|
|
393
353
|
stackFrame = describeBuiltInComponentFrame("SuspenseList");
|
|
394
354
|
break;
|
|
395
|
-
case
|
|
355
|
+
case workTags.ViewTransitionComponent:
|
|
396
356
|
// Note: enableViewTransition feature flag is not available in this codebase,
|
|
397
357
|
// so we'll always include ViewTransition
|
|
398
358
|
stackFrame = describeBuiltInComponentFrame("ViewTransition");
|
|
@@ -421,8 +381,8 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
421
381
|
// Since we don't have __DEV__ in this codebase, we'll check for _debugInfo
|
|
422
382
|
const debugInfo = currentFiber._debugInfo;
|
|
423
383
|
if (debugInfo && Array.isArray(debugInfo)) {
|
|
424
|
-
for (let
|
|
425
|
-
const debugEntry = debugInfo[
|
|
384
|
+
for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
|
|
385
|
+
const debugEntry = debugInfo[debugInfoIndex];
|
|
426
386
|
if (typeof debugEntry.name === "string") {
|
|
427
387
|
componentStack += describeDebugInfoFrame(debugEntry.name, debugEntry.env);
|
|
428
388
|
}
|
|
@@ -435,7 +395,7 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
435
395
|
return componentStack;
|
|
436
396
|
} catch (error) {
|
|
437
397
|
if (error instanceof Error) {
|
|
438
|
-
return `\
|
|
398
|
+
return `\nBippy couldn’t generate the stack: ${error.message}\n${error.stack}`;
|
|
439
399
|
}
|
|
440
400
|
return "";
|
|
441
401
|
}
|
|
@@ -460,14 +420,10 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
|
|
|
460
420
|
* @see https://github.com/facebook/react/blob/main/packages/react-devtools-shared/src/backend/shared/DevToolsOwnerStack.js#L12
|
|
461
421
|
*/
|
|
462
422
|
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
423
|
let formattedStack = stack;
|
|
467
424
|
if (!formattedStack) {
|
|
468
425
|
return "";
|
|
469
426
|
}
|
|
470
|
-
Error.prepareStackTrace = prevPrepareStackTrace;
|
|
471
427
|
|
|
472
428
|
if (formattedStack.startsWith("Error: react-stack-top-frame\n")) {
|
|
473
429
|
// V8's default formatting prefixes with the error message which we
|
|
@@ -480,8 +436,7 @@ export const formatOwnerStack = (stack: string): string => {
|
|
|
480
436
|
formattedStack = formattedStack.slice(firstNewlineIndex + 1);
|
|
481
437
|
}
|
|
482
438
|
let bottomFrameIndex = Math.max(
|
|
483
|
-
formattedStack.indexOf(
|
|
484
|
-
formattedStack.indexOf("react-stack-bottom-frame"),
|
|
439
|
+
...REACT_STACK_BOTTOM_FRAME_PATTERNS.map((pattern) => formattedStack.indexOf(pattern)),
|
|
485
440
|
);
|
|
486
441
|
if (bottomFrameIndex !== -1) {
|
|
487
442
|
bottomFrameIndex = formattedStack.lastIndexOf("\n", bottomFrameIndex);
|
|
@@ -498,11 +453,6 @@ export const formatOwnerStack = (stack: string): string => {
|
|
|
498
453
|
return formattedStack;
|
|
499
454
|
};
|
|
500
455
|
|
|
501
|
-
interface DebugStackEntry {
|
|
502
|
-
componentName: string;
|
|
503
|
-
stackFrames: StackFrame[];
|
|
504
|
-
}
|
|
505
|
-
|
|
506
456
|
const isReactServerComponentFrame = (stackFrame: StackFrame): boolean =>
|
|
507
457
|
Boolean(
|
|
508
458
|
stackFrame.functionName && stackFrame.fileName && isServerComponentUrl(stackFrame.fileName),
|
|
@@ -514,24 +464,22 @@ const areStackFramesEqual = (firstFrame: StackFrame, secondFrame: StackFrame): b
|
|
|
514
464
|
firstFrame.columnNumber === secondFrame.columnNumber;
|
|
515
465
|
|
|
516
466
|
const buildFunctionNameToRscFramesMap = (
|
|
517
|
-
|
|
467
|
+
debugStackFrames: StackFrame[],
|
|
518
468
|
): Map<string, StackFrame[]> => {
|
|
519
469
|
const functionNameToRscFrames = new Map<string, StackFrame[]>();
|
|
520
470
|
|
|
521
|
-
for (const
|
|
522
|
-
|
|
523
|
-
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
471
|
+
for (const stackFrame of debugStackFrames) {
|
|
472
|
+
if (!isReactServerComponentFrame(stackFrame)) continue;
|
|
524
473
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
474
|
+
const functionName = stackFrame.functionName!;
|
|
475
|
+
const framesForFunction = functionNameToRscFrames.get(functionName) ?? [];
|
|
476
|
+
const isDuplicateFrame = framesForFunction.some((existingFrame) =>
|
|
477
|
+
areStackFramesEqual(existingFrame, stackFrame),
|
|
478
|
+
);
|
|
530
479
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
480
|
+
if (!isDuplicateFrame) {
|
|
481
|
+
framesForFunction.push(stackFrame);
|
|
482
|
+
functionNameToRscFrames.set(functionName, framesForFunction);
|
|
535
483
|
}
|
|
536
484
|
}
|
|
537
485
|
|
|
@@ -592,7 +540,7 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
|
|
|
592
540
|
while (owner) {
|
|
593
541
|
if (isFiberOwner(owner)) {
|
|
594
542
|
const ownerFiber: Fiber = owner;
|
|
595
|
-
owner =
|
|
543
|
+
owner = ownerFiber._debugOwner;
|
|
596
544
|
if (owner && hasDebugStack(ownerFiber)) {
|
|
597
545
|
const { frames, isTrusted } = parseDebugStack(ownerFiber._debugStack);
|
|
598
546
|
if (isTrusted) {
|
|
@@ -616,51 +564,39 @@ const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
|
|
|
616
564
|
return ownerStackFrames;
|
|
617
565
|
};
|
|
618
566
|
|
|
619
|
-
const
|
|
620
|
-
const
|
|
567
|
+
const getDebugStackFrames = (rootFiber: Fiber): StackFrame[] => {
|
|
568
|
+
const debugStackFrames: StackFrame[] = [];
|
|
621
569
|
|
|
622
570
|
traverseFiber(
|
|
623
571
|
rootFiber,
|
|
624
572
|
(currentFiber) => {
|
|
625
573
|
if (!hasDebugStack(currentFiber)) return;
|
|
626
574
|
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
debugStackEntries.push({
|
|
633
|
-
componentName,
|
|
634
|
-
stackFrames: parseStack(formatOwnerStack(currentFiber._debugStack?.stack)),
|
|
635
|
-
});
|
|
575
|
+
const { frames, isTrusted } = parseDebugStack(currentFiber._debugStack);
|
|
576
|
+
if (isTrusted) {
|
|
577
|
+
debugStackFrames.push(...frames);
|
|
578
|
+
}
|
|
636
579
|
},
|
|
637
580
|
true,
|
|
638
581
|
);
|
|
639
582
|
|
|
640
|
-
return
|
|
583
|
+
return debugStackFrames;
|
|
641
584
|
};
|
|
642
585
|
|
|
643
586
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
* with a throwing dispatcher; server frames are enriched from debug stacks by
|
|
648
|
-
* name matching. Works on every React version.
|
|
587
|
+
* The unsymbolicated frames behind {@link getParentStack}: bundle-space
|
|
588
|
+
* locations from re-invoking each component with a throwing dispatcher,
|
|
589
|
+
* with server frames enriched from debug stacks by name matching.
|
|
649
590
|
*/
|
|
650
|
-
export const
|
|
651
|
-
fiber
|
|
652
|
-
shouldCache = true,
|
|
653
|
-
fetchFunction?: (url: string) => Promise<Response>,
|
|
654
|
-
): Promise<StackFrame[]> => {
|
|
655
|
-
const debugStackEntries = getDebugStackEntries(fiber);
|
|
591
|
+
export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
|
|
592
|
+
const debugStackFrames = getDebugStackFrames(fiber);
|
|
656
593
|
const fallbackStackFrames = parseStack(getFallbackParentStack(fiber));
|
|
657
|
-
const functionNameToRscFrames = buildFunctionNameToRscFramesMap(
|
|
594
|
+
const functionNameToRscFrames = buildFunctionNameToRscFramesMap(debugStackFrames);
|
|
658
595
|
const functionNameToUsageIndex = new Map<string, number>();
|
|
659
596
|
|
|
660
597
|
const enrichedStackFrames = fallbackStackFrames.map((stackFrame): StackFrame => {
|
|
661
598
|
const isServerFrame =
|
|
662
|
-
|
|
663
|
-
(stackFrame.source != null && SERVER_ENV_PATTERN.test(stackFrame.source));
|
|
599
|
+
stackFrame.source !== undefined && SERVER_ENV_PATTERN.test(stackFrame.source);
|
|
664
600
|
|
|
665
601
|
if (isServerFrame) {
|
|
666
602
|
return getEnrichedServerStackFrame(
|
|
@@ -673,15 +609,26 @@ export const getParentStack = async (
|
|
|
673
609
|
return stackFrame;
|
|
674
610
|
});
|
|
675
611
|
|
|
676
|
-
|
|
612
|
+
return enrichedStackFrames.filter((stackFrame, index, frames) => {
|
|
677
613
|
if (index === 0) return true;
|
|
678
614
|
const previousFrame = frames[index - 1];
|
|
679
615
|
return stackFrame.functionName !== previousFrame.functionName;
|
|
680
616
|
});
|
|
681
|
-
|
|
682
|
-
return symbolicateStack(deduplicatedStackFrames, shouldCache, fetchFunction);
|
|
683
617
|
};
|
|
684
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Returns a stack of ALL ancestor components in the render tree (the fiber's
|
|
621
|
+
* `return` chain), including wrappers that render `{children}` without having
|
|
622
|
+
* created this fiber's JSX. Locations come from re-invoking each component
|
|
623
|
+
* with a throwing dispatcher; server frames are enriched from debug stacks by
|
|
624
|
+
* name matching. Works on every React version.
|
|
625
|
+
*/
|
|
626
|
+
export const getParentStack = async (
|
|
627
|
+
fiber: Fiber,
|
|
628
|
+
shouldCache = true,
|
|
629
|
+
fetchFunction?: SourceFetch,
|
|
630
|
+
): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
|
|
631
|
+
|
|
685
632
|
// an owner frame is only actionable if it can point an editor somewhere:
|
|
686
633
|
// it needs a file location and must not be ignore-listed bundler/framework code
|
|
687
634
|
const isLocatableFrame = (stackFrame: StackFrame): boolean =>
|
|
@@ -700,7 +647,7 @@ const isLocatableFrame = (stackFrame: StackFrame): boolean =>
|
|
|
700
647
|
export const getOwnerStack = async (
|
|
701
648
|
fiber: Fiber,
|
|
702
649
|
shouldCache = true,
|
|
703
|
-
fetchFunction?:
|
|
650
|
+
fetchFunction?: SourceFetch,
|
|
704
651
|
): Promise<StackFrame[]> => {
|
|
705
652
|
const debugStackFrames = getOwnerStackFromDebugStacks(fiber);
|
|
706
653
|
if (debugStackFrames.length > 0) {
|
|
@@ -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);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { HooksNode, HooksTree, HookSource } from "./inspect-hooks.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getSourceContentFromSourceMap,
|
|
4
|
+
getSourceFromSourceMap,
|
|
5
|
+
getSourceMap,
|
|
6
|
+
type SourceFetch,
|
|
7
|
+
} from "./symbolication.js";
|
|
3
8
|
|
|
4
9
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
5
10
|
export interface HookNames extends Map<string, string> {}
|
|
@@ -33,40 +38,6 @@ const flattenHooksTree = (hooksTree: HooksTree): HooksNode[] => {
|
|
|
33
38
|
return hooksList;
|
|
34
39
|
};
|
|
35
40
|
|
|
36
|
-
const findSourceContentByFileName = (
|
|
37
|
-
sources: string[],
|
|
38
|
-
sourcesContent: string[] | undefined,
|
|
39
|
-
fileName: string,
|
|
40
|
-
): string | null => {
|
|
41
|
-
if (!sourcesContent) return null;
|
|
42
|
-
const sourceIndex = sources.indexOf(fileName);
|
|
43
|
-
return sourceIndex !== -1 ? (sourcesContent[sourceIndex] ?? null) : null;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const getSourceContentFromSourceMap = (
|
|
47
|
-
sourceMap: SourceMap,
|
|
48
|
-
originalFileName: string,
|
|
49
|
-
): string | null => {
|
|
50
|
-
const directResult = findSourceContentByFileName(
|
|
51
|
-
sourceMap.sources,
|
|
52
|
-
sourceMap.sourcesContent,
|
|
53
|
-
originalFileName,
|
|
54
|
-
);
|
|
55
|
-
if (directResult) return directResult;
|
|
56
|
-
|
|
57
|
-
if (sourceMap.sections) {
|
|
58
|
-
for (const section of sourceMap.sections) {
|
|
59
|
-
const sectionResult = findSourceContentByFileName(
|
|
60
|
-
section.map.sources,
|
|
61
|
-
section.map.sourcesContent,
|
|
62
|
-
originalFileName,
|
|
63
|
-
);
|
|
64
|
-
if (sectionResult) return sectionResult;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
41
|
const extractVariableNameFromBinding = (binding: string): string | null => {
|
|
71
42
|
const trimmed = binding.trim();
|
|
72
43
|
if (trimmed.startsWith("[")) {
|
|
@@ -109,9 +80,8 @@ interface ResolvedSource {
|
|
|
109
80
|
}
|
|
110
81
|
|
|
111
82
|
interface SourceResolutionContext {
|
|
112
|
-
sourceMapsByFile: Map<string, SourceMap | null>;
|
|
113
83
|
sourceContentCache: Map<string, string | null>;
|
|
114
|
-
fetchFn?:
|
|
84
|
+
fetchFn?: SourceFetch;
|
|
115
85
|
}
|
|
116
86
|
|
|
117
87
|
const resolveOriginalSource = async (
|
|
@@ -120,25 +90,17 @@ const resolveOriginalSource = async (
|
|
|
120
90
|
runtimeColumn: number,
|
|
121
91
|
context: SourceResolutionContext,
|
|
122
92
|
): Promise<ResolvedSource | null> => {
|
|
123
|
-
const {
|
|
93
|
+
const { sourceContentCache, fetchFn } = context;
|
|
124
94
|
|
|
125
|
-
|
|
126
|
-
sourceMapsByFile.set(runtimeFileName, await getSourceMap(runtimeFileName, true, fetchFn));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const sourceMap = sourceMapsByFile.get(runtimeFileName) ?? null;
|
|
95
|
+
const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn);
|
|
130
96
|
|
|
131
97
|
if (sourceMap) {
|
|
132
98
|
const originalLocation = getSourceFromSourceMap(sourceMap, runtimeLine, runtimeColumn);
|
|
133
99
|
if (originalLocation?.fileName && originalLocation.lineNumber !== undefined) {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
getSourceContentFromSourceMap(sourceMap, originalLocation.fileName),
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
const originalSourceCode = sourceContentCache.get(cacheKey) ?? null;
|
|
100
|
+
const originalSourceCode = getSourceContentFromSourceMap(
|
|
101
|
+
sourceMap,
|
|
102
|
+
originalLocation.fileName,
|
|
103
|
+
);
|
|
142
104
|
if (originalSourceCode) {
|
|
143
105
|
return {
|
|
144
106
|
sourceCode: originalSourceCode,
|
|
@@ -173,7 +135,7 @@ const resolveOriginalSource = async (
|
|
|
173
135
|
|
|
174
136
|
export const parseHookNames = async (
|
|
175
137
|
hooksTree: HooksTree,
|
|
176
|
-
fetchFn?:
|
|
138
|
+
fetchFn?: SourceFetch,
|
|
177
139
|
): Promise<HookNames> => {
|
|
178
140
|
const hookNames: HookNames = new Map();
|
|
179
141
|
const hooksList = flattenHooksTree(hooksTree);
|
|
@@ -181,7 +143,6 @@ export const parseHookNames = async (
|
|
|
181
143
|
if (hooksList.length === 0) return hookNames;
|
|
182
144
|
|
|
183
145
|
const resolutionContext: SourceResolutionContext = {
|
|
184
|
-
sourceMapsByFile: new Map(),
|
|
185
146
|
sourceContentCache: new Map(),
|
|
186
147
|
fetchFn,
|
|
187
148
|
};
|