bippy 0.7.2 → 0.7.3-dev.04de4f7

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.
@@ -0,0 +1,416 @@
1
+ import {
2
+ ABOUT_REACT_PREFIX,
3
+ ANONYMOUS_FILE_PATTERNS,
4
+ BUNDLED_FILE_PATTERN_REGEX,
5
+ INTERNAL_SCHEME_PREFIXES,
6
+ QUERY_PARAMETER_PATTERN_REGEX,
7
+ SCHEME_REGEX,
8
+ SOURCE_FILE_EXTENSION_REGEX,
9
+ } from "./constants.js";
10
+
11
+ const VITE_FS_PREFIX = "/@fs/";
12
+ const VITE_ID_PREFIX = "/@id/";
13
+ const VITE_NULL_BYTE_PLACEHOLDER = "__x00__";
14
+ const NEXT_WEBPACK_NAMESPACE = "_N_E/";
15
+ const TURBOPACK_PROJECT_TOKEN = "[project]/";
16
+ const WINDOWS_DRIVE_PREFIX = /^[a-zA-Z]:[\\/]/;
17
+ const WINDOWS_DRIVE_SEGMENT = /^[a-zA-Z]:$/;
18
+ const APP_PAGES_BROWSER_PREFIX = /^\/?(?:app-pages-browser|\(app-pages-browser\))\//;
19
+ const NEXT_BUNDLER_LAYER_PREFIX =
20
+ /^\/?\((?:action-browser|api-edge|api-node|edge-asset|instrument|middleware|pages-dir-browser|pages-dir-edge|pages-dir-node|rsc|shared|ssr)\)\//;
21
+ const ENCODED_SEPARATOR = /%(?:2f|5c)/i;
22
+ const WINDOWS_EXTENDED_PATH_PREFIX = "\\\\?\\";
23
+ const REACT_ENVIRONMENT_PREFIX = /^React\/[^/]+\//;
24
+ const TURBOPACK_MAGIC_IDENTIFIER = /__TURBOPACK__[a-zA-Z0-9_$]+__/g;
25
+ const TURBOPACK_MAGIC_IDENTIFIER_EXACT = /^__TURBOPACK__([a-zA-Z0-9_$]+)__$/;
26
+ const IMPORTED_MODULE_PREFIX = "imported module ";
27
+ const BUNDLER_LAYER_SUFFIX =
28
+ /\s\[(?:app-[a-z0-9-]+|action-browser|api-edge|api-node|edge-asset|instrument|middleware|pages-dir-browser|pages-dir-edge|pages-dir-node|rsc|shared|ssr)\]$/;
29
+ const BUNDLER_MODULE_TYPE_SUFFIX =
30
+ /\s\((?:asset|client reference proxy|css(?: client reference| module)?|ecmascript(?: client reference to (?:client|ssr))?|javascript|json|less|mdx|raw|sass|scss|static|typescript|unknown|wasm)\b[^)]*\)$/;
31
+ const BUNDLER_EXPORT_SUFFIX = /\s<(?:exports?|locals|namespace)\b[^>]*>$/;
32
+ const BUNDLER_MODULE_SUFFIXES = [
33
+ BUNDLER_MODULE_TYPE_SUFFIX,
34
+ BUNDLER_LAYER_SUFFIX,
35
+ BUNDLER_EXPORT_SUFFIX,
36
+ ];
37
+
38
+ const isWindowsDrivePath = (filePath: string): boolean => WINDOWS_DRIVE_PREFIX.test(filePath);
39
+
40
+ const VIRTUAL_SCHEME_PREFIXES = ["astro:", "cloudflare:", "nitro:"] as const;
41
+
42
+ const isVirtualModuleId = (filePath: string): boolean =>
43
+ filePath.startsWith("\0") ||
44
+ filePath.startsWith("virtual:") ||
45
+ VIRTUAL_SCHEME_PREFIXES.some((prefix) => filePath.startsWith(prefix));
46
+
47
+ const decodeHex = (hexText: string): string => {
48
+ if (hexText.trim() === "") throw new Error("empty hex");
49
+ const codePoint = Number.parseInt(hexText, 16);
50
+ if (Number.isNaN(codePoint)) throw new Error("invalid hex");
51
+ return String.fromCodePoint(codePoint);
52
+ };
53
+
54
+ const decodeMagicIdentifier = (identifier: string): string => {
55
+ const innerIdentifier = identifier.match(TURBOPACK_MAGIC_IDENTIFIER_EXACT)?.[1];
56
+ if (!innerIdentifier) return identifier;
57
+
58
+ let output = "";
59
+ let mode: "text" | "underscore" | "hex" | "long-hex" = "text";
60
+ let buffer = "";
61
+
62
+ for (const character of innerIdentifier) {
63
+ if (mode === "text") {
64
+ if (character === "_") mode = "underscore";
65
+ else if (character === "$") mode = "hex";
66
+ else output += character;
67
+ continue;
68
+ }
69
+
70
+ if (mode === "underscore") {
71
+ if (character === "_") output += " ";
72
+ else if (character === "$") {
73
+ output += "_";
74
+ mode = "hex";
75
+ continue;
76
+ } else output += character;
77
+ mode = "text";
78
+ continue;
79
+ }
80
+
81
+ if (mode === "hex") {
82
+ if (buffer.length === 2) {
83
+ output += decodeHex(buffer);
84
+ buffer = "";
85
+ }
86
+ if (character === "_") {
87
+ if (buffer !== "") throw new Error("invalid hex");
88
+ mode = "long-hex";
89
+ } else if (character === "$") {
90
+ if (buffer !== "") throw new Error("invalid hex");
91
+ mode = "text";
92
+ } else {
93
+ buffer += character;
94
+ }
95
+ continue;
96
+ }
97
+
98
+ if (character === "_") throw new Error("invalid hex");
99
+ if (character === "$") {
100
+ output += decodeHex(buffer);
101
+ buffer = "";
102
+ mode = "text";
103
+ continue;
104
+ }
105
+ buffer += character;
106
+ }
107
+
108
+ return output;
109
+ };
110
+
111
+ const decodeMagicIdentifiers = (filePath: string): string => {
112
+ if (!filePath.includes("__TURBOPACK__")) return filePath;
113
+ const decodedFilePath = filePath.replace(TURBOPACK_MAGIC_IDENTIFIER, (identifier) => {
114
+ try {
115
+ return decodeMagicIdentifier(identifier);
116
+ } catch {
117
+ return identifier;
118
+ }
119
+ });
120
+ if (!decodedFilePath.startsWith(IMPORTED_MODULE_PREFIX)) return decodedFilePath;
121
+ return decodedFilePath.slice(IMPORTED_MODULE_PREFIX.length);
122
+ };
123
+
124
+ const stripBundlerModuleMetadata = (filePath: string): string => {
125
+ let strippedFilePath = filePath;
126
+ let didStripMetadata = true;
127
+ while (didStripMetadata) {
128
+ didStripMetadata = false;
129
+ for (const suffix of BUNDLER_MODULE_SUFFIXES) {
130
+ const suffixMatch = strippedFilePath.match(suffix);
131
+ if (!suffixMatch?.[0]) continue;
132
+ strippedFilePath = strippedFilePath.slice(0, -suffixMatch[0].length).trimEnd();
133
+ didStripMetadata = true;
134
+ break;
135
+ }
136
+ }
137
+ return strippedFilePath;
138
+ };
139
+
140
+ const stripWebpackLoaders = (filePath: string): string => {
141
+ const separatorIndex = filePath.lastIndexOf("!");
142
+ if (separatorIndex <= 0) return filePath;
143
+ const loaderPath = filePath.slice(0, separatorIndex);
144
+ const resourcePath = filePath.slice(separatorIndex + 1);
145
+ if (!loaderPath.includes("/") && !loaderPath.includes("\\")) return filePath;
146
+ const isResourcePath =
147
+ resourcePath.startsWith("./") ||
148
+ resourcePath.startsWith("../") ||
149
+ resourcePath.startsWith("/") ||
150
+ resourcePath.startsWith("\\") ||
151
+ resourcePath.includes("/") ||
152
+ resourcePath.includes("\\") ||
153
+ isWindowsDrivePath(resourcePath);
154
+ if (!isResourcePath) return filePath;
155
+ return resourcePath;
156
+ };
157
+
158
+ const decodePath = (filePath: string): string => {
159
+ if (!filePath.includes("%")) return filePath;
160
+ return filePath
161
+ .split("/")
162
+ .map((segment) => {
163
+ if (!segment.includes("%") || ENCODED_SEPARATOR.test(segment)) return segment;
164
+ try {
165
+ return decodeURIComponent(segment);
166
+ } catch {
167
+ return segment;
168
+ }
169
+ })
170
+ .join("/");
171
+ };
172
+
173
+ const resolveFileProtocol = (specifier: string): string => {
174
+ const afterScheme = specifier.slice("file://".length);
175
+ const postfixIndex = afterScheme.search(/[?#]/);
176
+ const pathWithHost = postfixIndex === -1 ? afterScheme : afterScheme.slice(0, postfixIndex);
177
+
178
+ let hostname = "";
179
+ let pathname = pathWithHost;
180
+ if (pathWithHost.startsWith("/")) {
181
+ pathname = pathWithHost.replace(/^\/+/, "/");
182
+ } else if (isWindowsDrivePath(pathWithHost)) {
183
+ pathname = pathWithHost;
184
+ } else {
185
+ const slashIndex = pathWithHost.indexOf("/");
186
+ hostname = slashIndex === -1 ? pathWithHost : pathWithHost.slice(0, slashIndex);
187
+ pathname = slashIndex === -1 ? "/" : pathWithHost.slice(slashIndex);
188
+ }
189
+
190
+ if (hostname.toLowerCase() === "localhost") hostname = "";
191
+ if (hostname) {
192
+ return `//${hostname}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
193
+ }
194
+
195
+ if (pathname.startsWith("/") && isWindowsDrivePath(pathname.slice(1))) {
196
+ return pathname.slice(1);
197
+ }
198
+ if (isWindowsDrivePath(pathname)) return pathname;
199
+ return pathname.startsWith("/") ? pathname : `/${pathname}`;
200
+ };
201
+
202
+ const unwrapViteId = (filePath: string): string => {
203
+ if (filePath.startsWith(VITE_FS_PREFIX)) {
204
+ const absolutePath = filePath.slice(VITE_FS_PREFIX.length);
205
+ if (isWindowsDrivePath(absolutePath)) return absolutePath;
206
+ return absolutePath.startsWith("/") ? absolutePath : `/${absolutePath}`;
207
+ }
208
+
209
+ if (!filePath.startsWith(VITE_ID_PREFIX)) return filePath;
210
+ const unwrappedId = filePath
211
+ .slice(VITE_ID_PREFIX.length)
212
+ .replaceAll(VITE_NULL_BYTE_PLACEHOLDER, "\0");
213
+ if (unwrappedId.startsWith("\0") || unwrappedId.startsWith("virtual:")) return "";
214
+ return unwrappedId;
215
+ };
216
+
217
+ const stripWebpackNamespace = (filePath: string): string => {
218
+ if (filePath.startsWith(NEXT_WEBPACK_NAMESPACE)) {
219
+ return filePath.slice(NEXT_WEBPACK_NAMESPACE.length);
220
+ }
221
+ const slashIndex = filePath.indexOf("/");
222
+ if (slashIndex <= 0) return filePath;
223
+ const remainder = filePath.slice(slashIndex + 1);
224
+ if (remainder.startsWith("./") || remainder.startsWith("../")) return remainder;
225
+ return filePath;
226
+ };
227
+
228
+ const stripBundlerLayer = (filePath: string, didStripBundlerScheme: boolean): string => {
229
+ const appPagesMatch = filePath.match(APP_PAGES_BROWSER_PREFIX);
230
+ if (appPagesMatch?.[0]) return filePath.slice(appPagesMatch[0].length);
231
+ if (!didStripBundlerScheme) return filePath;
232
+ const layerMatch = filePath.match(NEXT_BUNDLER_LAYER_PREFIX);
233
+ if (!layerMatch?.[0]) return filePath;
234
+ return filePath.slice(layerMatch[0].length);
235
+ };
236
+
237
+ const stripUrlPostfix = (filePath: string): string => {
238
+ const hashIndex = filePath.indexOf("#");
239
+ const withoutHash = hashIndex === -1 ? filePath : filePath.slice(0, hashIndex);
240
+ const queryParameterIndex = withoutHash.indexOf("?");
241
+ if (queryParameterIndex === -1) return withoutHash;
242
+ const potentialQueryParameters = withoutHash.slice(queryParameterIndex);
243
+ if (!QUERY_PARAMETER_PATTERN_REGEX.test(potentialQueryParameters)) return withoutHash;
244
+ return withoutHash.slice(0, queryParameterIndex);
245
+ };
246
+
247
+ const normalizeLexicalPath = (filePath: string): string => {
248
+ if (!filePath || filePath.includes("\\")) return filePath;
249
+ const isAbsolute = filePath.startsWith("/");
250
+ const isNetworkPath = filePath.startsWith("//");
251
+ const hasRelativeDotPrefix = filePath.startsWith("./");
252
+ const normalizedSegments: string[] = [];
253
+
254
+ for (const segment of filePath.split("/")) {
255
+ if (segment === "" || segment === ".") continue;
256
+ if (segment === "..") {
257
+ const previousSegment = normalizedSegments[normalizedSegments.length - 1];
258
+ if (
259
+ previousSegment &&
260
+ previousSegment !== ".." &&
261
+ (!isNetworkPath || normalizedSegments.length > 2) &&
262
+ !WINDOWS_DRIVE_SEGMENT.test(previousSegment)
263
+ ) {
264
+ normalizedSegments.pop();
265
+ } else if (!isAbsolute && !WINDOWS_DRIVE_SEGMENT.test(previousSegment ?? "")) {
266
+ normalizedSegments.push("..");
267
+ }
268
+ continue;
269
+ }
270
+ normalizedSegments.push(segment);
271
+ }
272
+
273
+ if (isAbsolute) return `${isNetworkPath ? "//" : "/"}${normalizedSegments.join("/")}`;
274
+ const joinedPath = normalizedSegments.join("/");
275
+ if (!joinedPath) return hasRelativeDotPrefix ? "./" : "";
276
+ if (hasRelativeDotPrefix && !joinedPath.startsWith("../")) return `./${joinedPath}`;
277
+ return joinedPath;
278
+ };
279
+
280
+ export const normalizeFileName = (fileName: string): string => {
281
+ if (!fileName) return "";
282
+ if (ANONYMOUS_FILE_PATTERNS.some((pattern) => pattern === fileName)) return "";
283
+ if (isVirtualModuleId(fileName)) return "";
284
+
285
+ let normalizedFileName = fileName;
286
+ if (normalizedFileName.startsWith(WINDOWS_EXTENDED_PATH_PREFIX)) {
287
+ const extendedPath = normalizedFileName.slice(WINDOWS_EXTENDED_PATH_PREFIX.length);
288
+ if (isWindowsDrivePath(extendedPath)) normalizedFileName = extendedPath;
289
+ }
290
+ normalizedFileName = decodeMagicIdentifiers(normalizedFileName);
291
+ if (isVirtualModuleId(normalizedFileName)) return "";
292
+
293
+ let didStripWebpackScheme = false;
294
+ let didStripBundlerScheme = false;
295
+ let didStripRscScheme = false;
296
+ let didResolveFileProtocol = false;
297
+
298
+ const isHttpUrl =
299
+ normalizedFileName.startsWith("http://") || normalizedFileName.startsWith("https://");
300
+ if (isHttpUrl) {
301
+ try {
302
+ normalizedFileName = new URL(normalizedFileName).pathname;
303
+ } catch {}
304
+ normalizedFileName = stripSingleBasePathPrefix(normalizedFileName);
305
+ }
306
+
307
+ if (normalizedFileName.startsWith(ABOUT_REACT_PREFIX)) {
308
+ const remainder = normalizedFileName.slice(ABOUT_REACT_PREFIX.length);
309
+ const slashIndex = remainder.indexOf("/");
310
+ const colonIndex = remainder.indexOf(":");
311
+ normalizedFileName =
312
+ slashIndex !== -1 && (colonIndex === -1 || slashIndex < colonIndex)
313
+ ? remainder.slice(slashIndex + 1)
314
+ : remainder;
315
+ }
316
+
317
+ let didStripPrefix = true;
318
+ while (didStripPrefix) {
319
+ didStripPrefix = false;
320
+ if (normalizedFileName.startsWith("file://")) {
321
+ const resolvedFilePath = resolveFileProtocol(normalizedFileName);
322
+ if (resolvedFilePath === normalizedFileName) break;
323
+ normalizedFileName = resolvedFilePath;
324
+ didResolveFileProtocol = true;
325
+ didStripPrefix = true;
326
+ continue;
327
+ }
328
+ if (didStripRscScheme) {
329
+ const reactEnvironmentMatch = normalizedFileName.match(REACT_ENVIRONMENT_PREFIX);
330
+ if (reactEnvironmentMatch?.[0]) {
331
+ normalizedFileName = normalizedFileName.slice(reactEnvironmentMatch[0].length);
332
+ didStripRscScheme = false;
333
+ didStripPrefix = true;
334
+ continue;
335
+ }
336
+ }
337
+ for (const prefix of INTERNAL_SCHEME_PREFIXES) {
338
+ if (!normalizedFileName.startsWith(prefix)) continue;
339
+ if (prefix === "rsc://") didStripRscScheme = true;
340
+ if (prefix === "webpack://" || prefix === "rspack://") didStripWebpackScheme = true;
341
+ if (
342
+ prefix === "webpack://" ||
343
+ prefix === "webpack-internal://" ||
344
+ prefix === "rspack://" ||
345
+ prefix === "rspack-internal://" ||
346
+ prefix === "turbopack://"
347
+ ) {
348
+ didStripBundlerScheme = true;
349
+ }
350
+ normalizedFileName = normalizedFileName.slice(prefix.length);
351
+ didStripPrefix = true;
352
+ break;
353
+ }
354
+ }
355
+
356
+ if (isVirtualModuleId(normalizedFileName)) return "";
357
+
358
+ if (!isWindowsDrivePath(normalizedFileName)) {
359
+ const schemeMatch = normalizedFileName.match(SCHEME_REGEX);
360
+ if (schemeMatch) normalizedFileName = normalizedFileName.slice(schemeMatch[0].length);
361
+ }
362
+
363
+ if (!didResolveFileProtocol && normalizedFileName.startsWith("//")) {
364
+ const firstPathSlashIndex = normalizedFileName.indexOf("/", 2);
365
+ normalizedFileName =
366
+ firstPathSlashIndex === -1 ? "" : normalizedFileName.slice(firstPathSlashIndex);
367
+ }
368
+
369
+ normalizedFileName = unwrapViteId(normalizedFileName);
370
+ if (didStripWebpackScheme) normalizedFileName = stripWebpackNamespace(normalizedFileName);
371
+ normalizedFileName = stripBundlerLayer(normalizedFileName, didStripBundlerScheme);
372
+ if (normalizedFileName.startsWith(TURBOPACK_PROJECT_TOKEN)) {
373
+ normalizedFileName = `./${normalizedFileName.slice(TURBOPACK_PROJECT_TOKEN.length)}`;
374
+ } else if (normalizedFileName.startsWith(`/${TURBOPACK_PROJECT_TOKEN}`)) {
375
+ normalizedFileName = normalizedFileName.slice(TURBOPACK_PROJECT_TOKEN.length);
376
+ }
377
+ normalizedFileName = stripBundlerModuleMetadata(normalizedFileName);
378
+ const resourcePath = stripWebpackLoaders(normalizedFileName);
379
+ if (resourcePath !== normalizedFileName) return normalizeFileName(resourcePath);
380
+ normalizedFileName = stripUrlPostfix(normalizedFileName);
381
+ normalizedFileName = decodePath(normalizedFileName);
382
+ normalizedFileName = normalizeLexicalPath(normalizedFileName);
383
+ if (isVirtualModuleId(normalizedFileName)) return "";
384
+ return normalizedFileName;
385
+ };
386
+
387
+ const getPathSegmentCount = (path: string): number => path.split("/").filter(Boolean).length;
388
+
389
+ const stripSingleBasePathPrefix = (path: string): string => {
390
+ const firstSlashIndex = path.indexOf("/", 1);
391
+ if (firstSlashIndex === -1) return path;
392
+
393
+ const basePath = path.slice(0, firstSlashIndex);
394
+ if (getPathSegmentCount(basePath) !== 1) return path;
395
+ if (basePath === "/@fs" || basePath === "/@id") return path;
396
+
397
+ const remainderPath = path.slice(firstSlashIndex);
398
+ if (!SOURCE_FILE_EXTENSION_REGEX.test(remainderPath)) return path;
399
+
400
+ const remainderSegments = remainderPath.split("/").filter(Boolean);
401
+ if (remainderSegments.length < 2) return path;
402
+
403
+ const firstRemainderSegment = remainderSegments[0];
404
+ if (!firstRemainderSegment || firstRemainderSegment.startsWith("@")) return path;
405
+ if (firstRemainderSegment.length > 4) return path;
406
+ return remainderPath;
407
+ };
408
+
409
+ export const isSourceFile = (fileName: string): boolean => {
410
+ const normalizedFileName = normalizeFileName(fileName);
411
+ if (!normalizedFileName) return false;
412
+ if (normalizedFileName.includes("\0") || normalizedFileName.startsWith("virtual:")) return false;
413
+ if (!SOURCE_FILE_EXTENSION_REGEX.test(normalizedFileName)) return false;
414
+ if (BUNDLED_FILE_PATTERN_REGEX.test(normalizedFileName)) return false;
415
+ return true;
416
+ };
@@ -7,7 +7,6 @@ import type {
7
7
  } from "../react-internals/index.js";
8
8
  import {
9
9
  REACT_STACK_BOTTOM_FRAME_PATTERNS,
10
- SERVER_FRAME_MARKER,
11
10
  SERVER_ENV_PATTERN,
12
11
  SERVER_COMPONENT_URL_PREFIXES,
13
12
  } from "./constants.js";
@@ -19,7 +18,11 @@ import {
19
18
  readDispatcher,
20
19
  writeDispatcher,
21
20
  } from "./renderer-dispatchers.js";
22
- import { symbolicateStack, type SourceFetch } from "./symbolication.js";
21
+ import {
22
+ symbolicateStack,
23
+ type SourceFetch,
24
+ type SourceMapRequestOptions,
25
+ } from "./symbolication.js";
23
26
 
24
27
  interface RendererDispatcherSnapshot {
25
28
  currentDispatcherRef: RendererDispatcherRef;
@@ -101,12 +104,18 @@ const describeBuiltInComponentFrame = (name: string): string => {
101
104
  return `\n in ${name}`;
102
105
  };
103
106
 
104
- export const describeDebugInfoFrame = (name: string, env?: string): string => {
105
- let frameDescription = describeBuiltInComponentFrame(name);
106
- if (env) {
107
- frameDescription += ` (at ${env})`;
107
+ export const describeDebugInfoFrame = (
108
+ name: string,
109
+ env?: string,
110
+ location?: Error | null,
111
+ ): string => {
112
+ if (location) {
113
+ const childStack = formatOwnerStack(location.stack ?? "");
114
+ const lastNewlineIndex = childStack.lastIndexOf("\n");
115
+ const lastLine = lastNewlineIndex === -1 ? childStack : childStack.slice(lastNewlineIndex + 1);
116
+ if (lastLine.includes(name)) return `\n${lastLine}`;
108
117
  }
109
- return frameDescription;
118
+ return describeBuiltInComponentFrame(`${name}${env ? ` [${env}]` : ""}`);
110
119
  };
111
120
 
112
121
  let reEntry = false;
@@ -115,7 +124,7 @@ let reEntry = false;
115
124
  // component type like React DevTools does.
116
125
  const componentFrameCache = new WeakMap<React.ComponentType<unknown>, string>();
117
126
 
118
- // https://github.com/facebook/react/blob/f739642745577a8e4dcb9753836ac3589b9c590a/packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js#L22
127
+ // https://github.com/facebook/react/blob/eafeac0/packages/shared/ReactComponentStackFrame.js#L64
119
128
  const describeNativeComponentFrame = (
120
129
  component: React.ComponentType<unknown>,
121
130
  construct: boolean,
@@ -153,21 +162,12 @@ const describeNativeComponentFrame = (
153
162
  throw Error();
154
163
  },
155
164
  });
156
- if (typeof Reflect === "object" && Reflect.construct) {
157
- try {
158
- Reflect.construct(ThrowingConstructor, []);
159
- } catch (caughtError) {
160
- control = caughtError;
161
- }
162
- Reflect.construct(component, [], ThrowingConstructor);
163
- } else {
164
- try {
165
- Function.prototype.apply.call(ThrowingConstructor, undefined, []);
166
- } catch (caughtError) {
167
- control = caughtError;
168
- }
169
- Function.prototype.apply.call(component, ThrowingConstructor.prototype, []);
165
+ try {
166
+ Reflect.construct(ThrowingConstructor, []);
167
+ } catch (caughtError) {
168
+ control = caughtError;
170
169
  }
170
+ Reflect.construct(component, [], ThrowingConstructor);
171
171
  } else {
172
172
  try {
173
173
  throw Error();
@@ -312,7 +312,7 @@ const describeNativeComponentFrame = (
312
312
  return syntheticFrame;
313
313
  };
314
314
 
315
- // https://github.com/facebook/react/blob/ac3e705a18696168acfcaed39dce0cfaa6be8836/packages/react-reconciler/src/ReactFiberComponentStack.js#L180
315
+ // https://github.com/facebook/react/blob/eafeac0/packages/react-reconciler/src/ReactFiberComponentStack.js#L37
316
316
  export const describeFiber = (fiber: Fiber, childFiber: Fiber | null): string => {
317
317
  let stackFrame = "";
318
318
  const workTags = getReactWorkTagsForFiber(fiber);
@@ -384,7 +384,11 @@ export const getFallbackParentStack = (thisFiber: Fiber): string => {
384
384
  for (let debugInfoIndex = debugInfo.length - 1; debugInfoIndex >= 0; debugInfoIndex--) {
385
385
  const debugEntry = debugInfo[debugInfoIndex];
386
386
  if (typeof debugEntry.name === "string") {
387
- componentStack += describeDebugInfoFrame(debugEntry.name, debugEntry.env);
387
+ componentStack += describeDebugInfoFrame(
388
+ debugEntry.name,
389
+ debugEntry.env,
390
+ debugEntry.debugLocation,
391
+ );
388
392
  }
389
393
  }
390
394
  }
@@ -511,7 +515,7 @@ const getEnrichedServerStackFrame = (
511
515
  lineNumber: resolvedRscFrame.lineNumber,
512
516
  columnNumber: resolvedRscFrame.columnNumber,
513
517
  source: serverFrame.source?.replace(
514
- SERVER_FRAME_MARKER,
518
+ SERVER_ENV_PATTERN,
515
519
  `(${resolvedRscFrame.fileName}:${resolvedRscFrame.lineNumber}:${resolvedRscFrame.columnNumber})`,
516
520
  ),
517
521
  };
@@ -534,7 +538,7 @@ const markFlightServerFrame = (stackFrame: StackFrame): StackFrame =>
534
538
  * carries `.debugStack`). This is exact - no re-invoking components, no
535
539
  * name-matching heuristics - but requires React 19.
536
540
  */
537
- const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
541
+ export const getRawOwnerStack = (fiber: Fiber): StackFrame[] => {
538
542
  const ownerStackFrames: StackFrame[] = [];
539
543
  let owner: Fiber | ServerComponentInfo | null | undefined = fiber;
540
544
  while (owner) {
@@ -625,9 +629,11 @@ export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
625
629
  */
626
630
  export const getParentStack = async (
627
631
  fiber: Fiber,
628
- shouldCache = true,
629
- fetchFunction?: SourceFetch,
630
- ): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
632
+ shouldUseCache = true,
633
+ sourceFetch?: SourceFetch,
634
+ requestOptions: SourceMapRequestOptions = {},
635
+ ): Promise<StackFrame[]> =>
636
+ symbolicateStack(getRawParentStack(fiber), shouldUseCache, sourceFetch, requestOptions);
631
637
 
632
638
  // an owner frame is only actionable if it can point an editor somewhere:
633
639
  // it needs a file location and must not be ignore-listed bundler/framework code
@@ -646,10 +652,11 @@ const isLocatableFrame = (stackFrame: StackFrame): boolean =>
646
652
  */
647
653
  export const getOwnerStack = async (
648
654
  fiber: Fiber,
649
- shouldCache = true,
650
- fetchFunction?: SourceFetch,
655
+ shouldUseCache = true,
656
+ sourceFetch?: SourceFetch,
657
+ requestOptions: SourceMapRequestOptions = {},
651
658
  ): Promise<StackFrame[]> => {
652
- const debugStackFrames = getOwnerStackFromDebugStacks(fiber);
659
+ const debugStackFrames = getRawOwnerStack(fiber);
653
660
  if (debugStackFrames.length > 0) {
654
661
  // the owner chain does not include the fiber itself, but bippy's stacks
655
662
  // always start with the fiber's own frame
@@ -657,8 +664,9 @@ export const getOwnerStack = async (
657
664
  selfFrame.functionName = getDisplayName(fiber.type) ?? selfFrame.functionName;
658
665
  const symbolicatedFrames = await symbolicateStack(
659
666
  [selfFrame, ...debugStackFrames],
660
- shouldCache,
661
- fetchFunction,
667
+ shouldUseCache,
668
+ sourceFetch,
669
+ requestOptions,
662
670
  );
663
671
  const hasLocatableOwnerFrame = symbolicatedFrames.some(
664
672
  (stackFrame, frameIndex) => frameIndex > 0 && isLocatableFrame(stackFrame),
@@ -668,5 +676,5 @@ export const getOwnerStack = async (
668
676
  }
669
677
  }
670
678
 
671
- return getParentStack(fiber, shouldCache, fetchFunction);
679
+ return getParentStack(fiber, shouldUseCache, sourceFetch, requestOptions);
672
680
  };
@@ -1,6 +1,6 @@
1
1
  import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
2
2
  import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
3
- import { parseStack, StackFrame } from "./parse-stack.js";
3
+ import { parseStack, type StackFrame } from "./parse-stack.js";
4
4
 
5
5
  interface V8CallSite {
6
6
  getFunctionName?: () => string | null;
@@ -4,6 +4,7 @@ import {
4
4
  getSourceFromSourceMap,
5
5
  getSourceMap,
6
6
  type SourceFetch,
7
+ type SourceMapRequestOptions,
7
8
  } from "./symbolication.js";
8
9
 
9
10
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -19,8 +20,10 @@ const UNNAMED_HOOKS = new Set([
19
20
 
20
21
  // HACK: matches `const/let/var [name, ...] = use...(...` or `const/let/var name = use...(...`
21
22
  // across up to 10 lines; handles TypeScript generics like `useState<T>(`
23
+ // The member-access prefix consumes one dotless segment per repetition. Letting a segment
24
+ // span dots too would make the parse ambiguous and backtrack exponentially on long chains.
22
25
  const HOOK_DECLARATION_REGEX =
23
- /(?:const|let|var)\s+((?:\[[\s\S]*?\]|\w+))\s*=\s*(?:[\w$.]+\.)*use[A-Z]\w*\s*(?:<[\s\S]*?>)?\s*\(/g;
26
+ /(?:const|let|var)\s+((?:\[[\s\S]*?\]|\w+))\s*=\s*(?:(?:[\w$]+|require\s*\([^)]*\))\.)*use[A-Z]\w*\s*(?:<[\s\S]*?>)?\s*\(/g;
24
27
 
25
28
  export const getHookSourceLocationKey = (hookSource: HookSource): string =>
26
29
  `${hookSource.fileName ?? ""}:${hookSource.lineNumber ?? 0}:${hookSource.columnNumber ?? 0}`;
@@ -63,12 +66,13 @@ export const extractHookVariableName = (
63
66
 
64
67
  const allMatches = [...sourceChunk.matchAll(HOOK_DECLARATION_REGEX)];
65
68
 
66
- const hookPositionInChunk = sourceChunk.lastIndexOf("\n") + 1 + columnNumber;
67
- const closestMatch = allMatches.filter((match) => match.index! <= hookPositionInChunk).at(-1);
69
+ const hookLineStart = sourceChunk.lastIndexOf("\n") + 1;
70
+ const hookPositionInChunk = hookLineStart + columnNumber;
71
+ const closestMatch =
72
+ allMatches.filter((match) => match.index! <= hookPositionInChunk).at(-1) ??
73
+ (columnNumber === 0 ? allMatches.find((match) => match.index! >= hookLineStart) : undefined);
68
74
 
69
- if (closestMatch) {
70
- return extractVariableNameFromBinding(closestMatch[1]);
71
- }
75
+ if (closestMatch) return extractVariableNameFromBinding(closestMatch[1]);
72
76
 
73
77
  return null;
74
78
  };
@@ -82,6 +86,7 @@ interface ResolvedSource {
82
86
  interface SourceResolutionContext {
83
87
  sourceContentCache: Map<string, string | null>;
84
88
  fetchFn?: SourceFetch;
89
+ requestOptions?: SourceMapRequestOptions;
85
90
  }
86
91
 
87
92
  const resolveOriginalSource = async (
@@ -90,9 +95,9 @@ const resolveOriginalSource = async (
90
95
  runtimeColumn: number,
91
96
  context: SourceResolutionContext,
92
97
  ): Promise<ResolvedSource | null> => {
93
- const { sourceContentCache, fetchFn } = context;
98
+ const { sourceContentCache, fetchFn, requestOptions } = context;
94
99
 
95
- const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn);
100
+ const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn, requestOptions);
96
101
 
97
102
  if (sourceMap) {
98
103
  const originalLocation = getSourceFromSourceMap(sourceMap, runtimeLine, runtimeColumn);
@@ -136,6 +141,7 @@ const resolveOriginalSource = async (
136
141
  export const parseHookNames = async (
137
142
  hooksTree: HooksTree,
138
143
  fetchFn?: SourceFetch,
144
+ requestOptions?: SourceMapRequestOptions,
139
145
  ): Promise<HookNames> => {
140
146
  const hookNames: HookNames = new Map();
141
147
  const hooksList = flattenHooksTree(hooksTree);
@@ -145,6 +151,7 @@ export const parseHookNames = async (
145
151
  const resolutionContext: SourceResolutionContext = {
146
152
  sourceContentCache: new Map(),
147
153
  fetchFn,
154
+ requestOptions,
148
155
  };
149
156
 
150
157
  await Promise.all(