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.
- package/README.md +12 -79
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +59 -9
- package/dist/errors.d.ts +59 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +34 -58
- package/dist/index.d.ts +36 -60
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +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 +11 -11
- package/dist/source.d.cts +47 -18
- package/dist/source.d.ts +47 -18
- package/dist/source.js +12 -12
- package/package.json +7 -35
- package/src/call-listener.ts +13 -0
- package/src/core.ts +367 -282
- package/src/rdt-hook.ts +159 -87
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/generated/react-work-tags.ts +3 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +11 -9
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +42 -157
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/normalize-file-name.ts +416 -0
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +114 -64
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +131 -70
|
@@ -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 {
|
|
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 = (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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/
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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/
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
650
|
-
|
|
655
|
+
shouldUseCache = true,
|
|
656
|
+
sourceFetch?: SourceFetch,
|
|
657
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
651
658
|
): Promise<StackFrame[]> => {
|
|
652
|
-
const debugStackFrames =
|
|
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
|
-
|
|
661
|
-
|
|
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,
|
|
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
|
|
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
|
|
67
|
-
const
|
|
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(
|