bippy 0.7.3-dev.1d4f35b → 0.7.3-dev.65d7336

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
+ };
@@ -28,16 +28,14 @@ export const parseStack = (stackString: string, options?: ParseOptions): StackFr
28
28
  const frames: StackFrame[] = [];
29
29
  for (const rawLine of lines) {
30
30
  if (/^\s*at\s+/.test(rawLine)) {
31
- const parsed = parseV8OrIeString(rawLine)[0];
32
- if (parsed) frames.push(parsed);
31
+ if (CHROME_IE_STACK_REGEXP.test(rawLine)) frames.push(parseV8Line(rawLine));
33
32
  } else if (/^\s*in\s+/.test(rawLine)) {
34
33
  const elementName = rawLine
35
34
  .replace(/^\s*in\s+/, "")
36
35
  .replace(/\s*(?:\(at .*\)|\[[^\]]+\])$/, "");
37
36
  frames.push({ functionName: elementName, source: rawLine });
38
37
  } else if (rawLine.match(FIREFOX_SAFARI_STACK_REGEXP)) {
39
- const parsed = parseFFOrSafariString(rawLine)[0];
40
- if (parsed) frames.push(parsed);
38
+ if (!SAFARI_NATIVE_CODE_REGEXP.test(rawLine)) frames.push(parseSafariLine(rawLine));
41
39
  }
42
40
  }
43
41
  return frames;
@@ -48,6 +46,18 @@ export const parseStack = (stackString: string, options?: ParseOptions): StackFr
48
46
  return parseFFOrSafariString(stackString);
49
47
  };
50
48
 
49
+ const getPositionIndex = (location: string, endIndex: number): number => {
50
+ let positionIndex = endIndex - 1;
51
+ while (positionIndex >= 0) {
52
+ const character = location.charCodeAt(positionIndex);
53
+ if (character < 48 || character > 57) break;
54
+ positionIndex--;
55
+ }
56
+ return positionIndex < endIndex - 1 && location.charCodeAt(positionIndex) === 58
57
+ ? positionIndex
58
+ : -1;
59
+ };
60
+
51
61
  export const extractLocation = (
52
62
  urlLike: string,
53
63
  ): [string, string | undefined, string | undefined] => {
@@ -59,77 +69,115 @@ export const extractLocation = (
59
69
  const isWrappedLocation = urlLike.startsWith("(") && /:\d+\)$/.test(urlLike);
60
70
  const sanitizedResult = isWrappedLocation ? urlLike.slice(1, -1) : urlLike;
61
71
 
62
- const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
63
- const parts = regExp.exec(sanitizedResult);
64
- if (!parts) return [sanitizedResult, undefined, undefined];
65
- return [parts[1], parts[2] || undefined, parts[3] || undefined] as const;
66
- };
72
+ if (/[\n\r\u2028\u2029]/.test(sanitizedResult)) {
73
+ const parts = /(.+?)(?::(\d+))?(?::(\d+))?$/.exec(sanitizedResult);
74
+ return parts
75
+ ? [parts[1], parts[2] || undefined, parts[3] || undefined]
76
+ : [sanitizedResult, undefined, undefined];
77
+ }
67
78
 
68
- export const parseV8OrIeString = (stack: string): StackFrame[] => {
69
- const filteredLines = stack.split("\n").filter((line) => {
70
- return !!line.match(CHROME_IE_STACK_REGEXP);
71
- });
72
-
73
- return filteredLines.map((line): StackFrame => {
74
- let currentLine = line;
75
- if (currentLine.includes("(eval ")) {
76
- currentLine = currentLine
77
- .replace(/eval code/g, "eval")
78
- .replace(/(\(eval at [^()]*)|(,.*$)/g, "");
79
- }
80
- let sanitizedLine = currentLine
81
- .replace(/^\s+/, "")
82
- .replace(/\(eval code/g, "(")
83
- .replace(/^.*?\s+/, "");
79
+ const lastPositionIndex = getPositionIndex(sanitizedResult, sanitizedResult.length);
80
+ if (lastPositionIndex <= 0) return [sanitizedResult, undefined, undefined];
81
+ const previousPositionIndex = getPositionIndex(sanitizedResult, lastPositionIndex);
82
+ if (previousPositionIndex <= 0) {
83
+ return [
84
+ sanitizedResult.slice(0, lastPositionIndex),
85
+ sanitizedResult.slice(lastPositionIndex + 1),
86
+ undefined,
87
+ ];
88
+ }
89
+ return [
90
+ sanitizedResult.slice(0, previousPositionIndex),
91
+ sanitizedResult.slice(previousPositionIndex + 1, lastPositionIndex),
92
+ sanitizedResult.slice(lastPositionIndex + 1),
93
+ ];
94
+ };
84
95
 
85
- const locationMatch = sanitizedLine.match(/ (\(.+\)$)/);
96
+ const parseV8Line = (line: string): StackFrame => {
97
+ let currentLine = line;
98
+ if (currentLine.includes("(eval ")) {
99
+ currentLine = currentLine
100
+ .replace(/eval code/g, "eval")
101
+ .replace(/(\(eval at [^()]*)|(,.*$)/g, "");
102
+ }
103
+ let sanitizedLine = currentLine
104
+ .replace(/^\s+/, "")
105
+ .replace(/\(eval code/g, "(")
106
+ .replace(/^.*?\s+/, "");
107
+
108
+ const locationMatch = sanitizedLine.match(/ (\(.+\)$)/);
109
+
110
+ sanitizedLine = locationMatch ? sanitizedLine.replace(locationMatch[0], "") : sanitizedLine;
111
+
112
+ const locationParts = extractLocation(locationMatch ? locationMatch[1] : sanitizedLine);
113
+ const functionName = (locationMatch && sanitizedLine) || undefined;
114
+ const fileName = ["eval", "<anonymous>", "(native)"].includes(locationParts[0])
115
+ ? undefined
116
+ : locationParts[0];
117
+
118
+ return {
119
+ functionName,
120
+ fileName,
121
+ lineNumber: locationParts[1] ? +locationParts[1] : undefined,
122
+ columnNumber: locationParts[2] ? +locationParts[2] : undefined,
123
+ source: currentLine,
124
+ };
125
+ };
86
126
 
87
- sanitizedLine = locationMatch ? sanitizedLine.replace(locationMatch[0], "") : sanitizedLine;
127
+ const parseSafariLine = (line: string): StackFrame => {
128
+ let currentLine = line;
129
+ if (currentLine.includes(" > eval"))
130
+ currentLine = currentLine.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ":$1");
88
131
 
89
- const locationParts = extractLocation(locationMatch ? locationMatch[1] : sanitizedLine);
90
- const functionName = (locationMatch && sanitizedLine) || undefined;
91
- const fileName = ["eval", "<anonymous>", "(native)"].includes(locationParts[0])
92
- ? undefined
93
- : locationParts[0];
132
+ if (!currentLine.includes("@") && !currentLine.includes(":")) {
133
+ return {
134
+ functionName: currentLine,
135
+ };
136
+ } else {
137
+ const functionNameRegex =
138
+ /(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
139
+ const matches = currentLine.match(functionNameRegex);
140
+ const functionName = matches && matches[1] ? matches[1] : undefined;
141
+ const locationParts = extractLocation(currentLine.replace(functionNameRegex, ""));
94
142
 
95
143
  return {
96
144
  functionName,
97
- fileName,
145
+ fileName: locationParts[0],
98
146
  lineNumber: locationParts[1] ? +locationParts[1] : undefined,
99
147
  columnNumber: locationParts[2] ? +locationParts[2] : undefined,
100
148
  source: currentLine,
101
149
  };
102
- });
150
+ }
103
151
  };
104
152
 
105
- export const parseFFOrSafariString = (stack: string): StackFrame[] => {
106
- const filteredLines = stack.split("\n").filter((line) => {
107
- return !line.match(SAFARI_NATIVE_CODE_REGEXP);
108
- });
109
-
110
- return filteredLines.map((line): StackFrame => {
111
- let currentLine = line;
112
- if (currentLine.includes(" > eval"))
113
- currentLine = currentLine.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ":$1");
114
-
115
- if (!currentLine.includes("@") && !currentLine.includes(":")) {
116
- return {
117
- functionName: currentLine,
118
- };
119
- } else {
120
- const functionNameRegex =
121
- /(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
122
- const matches = currentLine.match(functionNameRegex);
123
- const functionName = matches && matches[1] ? matches[1] : undefined;
124
- const locationParts = extractLocation(currentLine.replace(functionNameRegex, ""));
125
-
126
- return {
127
- functionName,
128
- fileName: locationParts[0],
129
- lineNumber: locationParts[1] ? +locationParts[1] : undefined,
130
- columnNumber: locationParts[2] ? +locationParts[2] : undefined,
131
- source: currentLine,
132
- };
153
+ const parseLines = (
154
+ stack: string,
155
+ isV8: boolean,
156
+ cache?: Map<string, StackFrame>,
157
+ ): StackFrame[] => {
158
+ const frames: StackFrame[] = [];
159
+ for (const line of stack.split("\n")) {
160
+ let frame = cache?.get(line);
161
+ if (!frame) {
162
+ if (isV8 ? !CHROME_IE_STACK_REGEXP.test(line) : SAFARI_NATIVE_CODE_REGEXP.test(line))
163
+ continue;
164
+ frame = isV8 ? parseV8Line(line) : parseSafariLine(line);
165
+ cache?.set(line, frame);
133
166
  }
134
- });
167
+ frames.push(frame);
168
+ }
169
+ return frames;
170
+ };
171
+
172
+ export const parseV8OrIeString = (stack: string): StackFrame[] => parseLines(stack, true);
173
+
174
+ export const parseFFOrSafariString = (stack: string): StackFrame[] => parseLines(stack, false);
175
+
176
+ export const createStackParser = () => {
177
+ const v8Frames = new Map<string, StackFrame>();
178
+ const safariFrames = new Map<string, StackFrame>();
179
+ return (stack: string): StackFrame[] => {
180
+ const isV8 = CHROME_IE_STACK_REGEXP.test(stack);
181
+ return parseLines(stack, isV8, isV8 ? v8Frames : safariFrames);
182
+ };
135
183
  };