bippy 0.7.3-dev.e2b879b → 0.7.3-dev.e6929e3
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 +1 -8
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +3 -0
- package/dist/errors.d.ts +3 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +27 -27
- package/dist/index.d.ts +29 -29
- package/dist/index.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -12
- package/dist/source.d.cts +22 -22
- package/dist/source.d.ts +22 -22
- package/dist/source.js +11 -12
- package/package.json +4 -4
- package/src/call-listener.ts +13 -0
- package/src/core.ts +99 -143
- package/src/rdt-hook.ts +5 -4
- package/src/react-internals/generated/react-work-tags.ts +3 -0
- package/src/react-internals/index.ts +36 -14
- package/src/react.ts +139 -75
- package/src/source/inspect-hooks.ts +5 -3
- package/src/source/parse-stack.ts +111 -63
- package/src/source/symbolication.ts +15 -4
package/src/react.ts
CHANGED
|
@@ -1,98 +1,162 @@
|
|
|
1
1
|
import "./install-hook-only.js";
|
|
2
|
-
import
|
|
3
|
-
import { isFiber } from "./core.js";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { isFiber, traverseFiber } from "./core.js";
|
|
4
|
+
import { _renderers } from "./rdt-hook.js";
|
|
5
|
+
import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
|
|
4
6
|
import type { Fiber } from "./react-internals/index.js";
|
|
5
7
|
|
|
6
8
|
export type { Fiber } from "./react-internals/index.js";
|
|
7
9
|
|
|
8
|
-
interface
|
|
9
|
-
|
|
10
|
-
next: unknown;
|
|
10
|
+
interface RenderMarker {
|
|
11
|
+
(state: undefined): undefined;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const subscribeToEmptyStore = (): (() => void) => unsubscribeFromEmptyStore;
|
|
17
|
-
const useSyncExternalStore: unknown = Reflect.get(React, "useSyncExternalStore");
|
|
14
|
+
interface ReducerQueue {
|
|
15
|
+
lastRenderedReducer: unknown;
|
|
16
|
+
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const bindProxy = new Proxy(originalBind, {
|
|
24
|
-
apply: (bind, functionToBind, boundArguments) => {
|
|
25
|
-
const fiber = boundArguments[1];
|
|
26
|
-
if (!capturedFiber && isFiber(fiber)) {
|
|
27
|
-
capturedFiber = fiber;
|
|
28
|
-
}
|
|
29
|
-
return Reflect.apply(bind, functionToBind, boundArguments);
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
18
|
+
interface FiberCapture {
|
|
19
|
+
fiber: Fiber;
|
|
20
|
+
queue: ReducerQueue | null;
|
|
21
|
+
}
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} finally {
|
|
37
|
-
if (Function.prototype.bind === bindProxy) {
|
|
38
|
-
Reflect.set(Function.prototype, "bind", originalBind);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
23
|
+
const isReducerQueue = (value: unknown): value is ReducerQueue =>
|
|
24
|
+
typeof value === "object" && value !== null && "lastRenderedReducer" in value;
|
|
41
25
|
|
|
42
|
-
|
|
26
|
+
const hasRenderMarker = (fiber: Fiber, renderMarker: RenderMarker): boolean => {
|
|
27
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
28
|
+
if (Array.isArray(hook.memoizedState) && hook.memoizedState[0] === renderMarker) return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
43
31
|
};
|
|
44
32
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]);
|
|
33
|
+
const getReducerQueue = (fiber: Fiber, renderMarker: RenderMarker): ReducerQueue | null => {
|
|
34
|
+
for (let hook = fiber.memoizedState; hook; hook = hook.next) {
|
|
35
|
+
if (isReducerQueue(hook.queue) && hook.queue.lastRenderedReducer === renderMarker)
|
|
36
|
+
return hook.queue;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
52
39
|
};
|
|
53
40
|
|
|
54
|
-
const
|
|
55
|
-
|
|
41
|
+
const getDevToolsFiber = (renderMarker: RenderMarker): Fiber | null => {
|
|
42
|
+
for (const renderer of _renderers) {
|
|
43
|
+
try {
|
|
44
|
+
const fiber = renderer.getCurrentFiber?.();
|
|
45
|
+
if (fiber && isFiber(fiber) && hasRenderMarker(fiber, renderMarker)) return fiber;
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
56
49
|
};
|
|
57
50
|
|
|
58
|
-
const
|
|
59
|
-
|
|
51
|
+
const captureReducerFiber = (
|
|
52
|
+
renderMarker: RenderMarker,
|
|
53
|
+
shouldCapture: boolean,
|
|
54
|
+
): FiberCapture | null => {
|
|
55
|
+
let originalBind: typeof Function.prototype.bind | undefined;
|
|
56
|
+
if (shouldCapture) {
|
|
57
|
+
try {
|
|
58
|
+
originalBind = Function.prototype.bind;
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
if (!originalBind) {
|
|
62
|
+
React.useReducer(renderMarker, undefined);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
let capture: FiberCapture | null = null;
|
|
67
|
+
let isCapturing = true;
|
|
68
|
+
// HACK: Production React binds a new reducer's Fiber and queue; validate the private reducer marker, not argument positions.
|
|
69
|
+
const bindProxy = new Proxy(originalBind, {
|
|
70
|
+
apply: (bind, callback, boundArguments) => {
|
|
71
|
+
if (isCapturing && !capture) {
|
|
72
|
+
try {
|
|
73
|
+
const queue = boundArguments.find(
|
|
74
|
+
(argument): argument is ReducerQueue =>
|
|
75
|
+
isReducerQueue(argument) && argument.lastRenderedReducer === renderMarker,
|
|
76
|
+
);
|
|
77
|
+
if (queue) {
|
|
78
|
+
const fiber = boundArguments.find(
|
|
79
|
+
(argument): argument is Fiber => isFiber(argument) && isFiber(argument.return),
|
|
80
|
+
);
|
|
81
|
+
if (fiber) capture = { fiber, queue };
|
|
82
|
+
}
|
|
83
|
+
} catch {}
|
|
84
|
+
}
|
|
85
|
+
return Reflect.apply(bind, callback, boundArguments);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
try {
|
|
90
|
+
try {
|
|
91
|
+
Reflect.set(Function.prototype, "bind", bindProxy);
|
|
92
|
+
} catch {}
|
|
93
|
+
React.useReducer(renderMarker, undefined);
|
|
94
|
+
return capture;
|
|
95
|
+
} finally {
|
|
96
|
+
isCapturing = false;
|
|
97
|
+
capture = null;
|
|
98
|
+
try {
|
|
99
|
+
if (Function.prototype.bind === bindProxy) {
|
|
100
|
+
Reflect.set(Function.prototype, "bind", originalBind);
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
74
104
|
};
|
|
75
105
|
|
|
76
|
-
// HACK: React
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return
|
|
106
|
+
// HACK: React <16.13 attaches hooks after render; the shared reducer queue validates the root-walk fallback.
|
|
107
|
+
const getRenderingFiberFromRoot = (
|
|
108
|
+
{ fiber, queue }: FiberCapture,
|
|
109
|
+
renderMarker: RenderMarker,
|
|
110
|
+
): Fiber | null => {
|
|
111
|
+
if (queue?.lastRenderedReducer !== renderMarker) return null;
|
|
112
|
+
let rootFiber = fiber;
|
|
113
|
+
while (rootFiber.return) rootFiber = rootFiber.return;
|
|
114
|
+
const root = rootFiber.stateNode;
|
|
115
|
+
if (typeof root !== "object" || root === null || !("current" in root) || !isFiber(root.current)) {
|
|
116
|
+
return null;
|
|
87
117
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
|
|
118
|
+
const renderingRoot = root.current.alternate;
|
|
119
|
+
const renderingFiber = getCurrentFiberFromRoot(fiber)?.alternate;
|
|
120
|
+
if (renderingFiber) {
|
|
121
|
+
let ancestor = renderingFiber;
|
|
122
|
+
while (ancestor.return) ancestor = ancestor.return;
|
|
123
|
+
if (ancestor === renderingRoot) return renderingFiber;
|
|
124
|
+
}
|
|
125
|
+
return traverseFiber(renderingRoot, (candidate) => {
|
|
126
|
+
if (candidate !== fiber && candidate !== fiber.alternate) return false;
|
|
127
|
+
let parent = candidate;
|
|
128
|
+
while (parent.return) parent = parent.return;
|
|
129
|
+
return parent === renderingRoot;
|
|
130
|
+
});
|
|
95
131
|
};
|
|
96
132
|
|
|
97
|
-
export const useFiber =
|
|
98
|
-
|
|
133
|
+
export const useFiber = (): Fiber | undefined => {
|
|
134
|
+
"use no memo";
|
|
135
|
+
const fiberRef = React.useRef<FiberCapture | null>(null);
|
|
136
|
+
const renderMarker: RenderMarker = (state) => state;
|
|
137
|
+
React.useMemo(() => renderMarker, [renderMarker]);
|
|
138
|
+
const devToolsFiber = fiberRef.current ? null : getDevToolsFiber(renderMarker);
|
|
139
|
+
const capture = captureReducerFiber(renderMarker, !fiberRef.current && !devToolsFiber);
|
|
140
|
+
if (devToolsFiber) {
|
|
141
|
+
fiberRef.current = {
|
|
142
|
+
fiber: devToolsFiber,
|
|
143
|
+
queue: getReducerQueue(devToolsFiber, renderMarker),
|
|
144
|
+
};
|
|
145
|
+
return devToolsFiber;
|
|
146
|
+
}
|
|
147
|
+
const knownCapture = capture ?? fiberRef.current;
|
|
148
|
+
if (!knownCapture) return undefined;
|
|
149
|
+
const knownFiber = knownCapture.fiber;
|
|
150
|
+
const fiber =
|
|
151
|
+
capture?.fiber ??
|
|
152
|
+
(hasRenderMarker(knownFiber, renderMarker)
|
|
153
|
+
? knownFiber
|
|
154
|
+
: knownFiber.alternate && hasRenderMarker(knownFiber.alternate, renderMarker)
|
|
155
|
+
? knownFiber.alternate
|
|
156
|
+
: getRenderingFiberFromRoot(knownCapture, renderMarker));
|
|
157
|
+
if (fiber) {
|
|
158
|
+
knownCapture.fiber = fiber;
|
|
159
|
+
fiberRef.current = knownCapture;
|
|
160
|
+
}
|
|
161
|
+
return fiber ?? undefined;
|
|
162
|
+
};
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
BippyUnsupportedHookError,
|
|
15
15
|
} from "../errors.js";
|
|
16
16
|
import { getReactWorkTagsForFiber } from "../react-internals/index.js";
|
|
17
|
-
import { parseStack, type StackFrame } from "./parse-stack.js";
|
|
17
|
+
import { createStackParser, parseStack, type StackFrame } from "./parse-stack.js";
|
|
18
18
|
import {
|
|
19
19
|
getRendererDispatcherRefs,
|
|
20
20
|
readDispatcher,
|
|
@@ -604,8 +604,9 @@ const findPrimitiveIndex = (hookStack: StackFrame[], hook: HookLogEntry): number
|
|
|
604
604
|
const parseTrimmedStack = (
|
|
605
605
|
rootStack: StackFrame[],
|
|
606
606
|
hook: HookLogEntry,
|
|
607
|
+
parseHookStack: (stack: string) => StackFrame[],
|
|
607
608
|
): [StackFrame | null, StackFrame[] | null] => {
|
|
608
|
-
const hookStack =
|
|
609
|
+
const hookStack = parseHookStack(hook.stackError.stack || "");
|
|
609
610
|
const rootIndex = findCommonAncestorIndex(rootStack, hookStack);
|
|
610
611
|
const primitiveIndex = findPrimitiveIndex(hookStack, hook);
|
|
611
612
|
if (rootIndex === -1 || primitiveIndex === -1 || rootIndex - primitiveIndex < 2) {
|
|
@@ -626,13 +627,14 @@ const NON_ID_HOOK_PRIMITIVES = new Set([
|
|
|
626
627
|
|
|
627
628
|
const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): HooksTree => {
|
|
628
629
|
const rootChildren: HooksNode[] = [];
|
|
630
|
+
const parseHookStack = createStackParser();
|
|
629
631
|
let previousStack: StackFrame[] | null = null;
|
|
630
632
|
let levelChildren = rootChildren;
|
|
631
633
|
let nativeHookID = 0;
|
|
632
634
|
const childrenStack: HooksNode[][] = [];
|
|
633
635
|
|
|
634
636
|
for (const hook of capturedHookLog) {
|
|
635
|
-
const [primitiveFrame, stack] = parseTrimmedStack(rootStack, hook);
|
|
637
|
+
const [primitiveFrame, stack] = parseTrimmedStack(rootStack, hook, parseHookStack);
|
|
636
638
|
let displayName = hook.displayName;
|
|
637
639
|
if (displayName === null && primitiveFrame !== null) {
|
|
638
640
|
const primitiveName = parseHookName(primitiveFrame.functionName);
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
};
|
|
@@ -207,6 +207,13 @@ export const getSourceFromSourceMap = (
|
|
|
207
207
|
);
|
|
208
208
|
};
|
|
209
209
|
|
|
210
|
+
const getStringIndex = (values: string[], target: string): number => {
|
|
211
|
+
for (let valueIndex = 0; valueIndex < values.length; valueIndex++) {
|
|
212
|
+
if (values[valueIndex] === target) return valueIndex;
|
|
213
|
+
}
|
|
214
|
+
return -1;
|
|
215
|
+
};
|
|
216
|
+
|
|
210
217
|
const getSourceFromMappingsByFunctionName = (
|
|
211
218
|
mappings: SourceMapMappings,
|
|
212
219
|
sources: string[],
|
|
@@ -215,13 +222,17 @@ const getSourceFromMappingsByFunctionName = (
|
|
|
215
222
|
ignoredSourceIndices?: Set<number>,
|
|
216
223
|
): StackFrame | null => {
|
|
217
224
|
if (!names) return null;
|
|
218
|
-
const functionNameIndex = names
|
|
225
|
+
const functionNameIndex = getStringIndex(names, functionName);
|
|
219
226
|
if (functionNameIndex === -1) return null;
|
|
220
227
|
|
|
221
228
|
let ignoredSource: StackFrame | null = null;
|
|
222
|
-
for (
|
|
223
|
-
|
|
229
|
+
for (let lineIndex = 0; lineIndex < mappings.length; lineIndex++) {
|
|
230
|
+
const lineMapping = mappings[lineIndex];
|
|
231
|
+
for (let segmentIndex = 0; segmentIndex < lineMapping.length; segmentIndex++) {
|
|
232
|
+
const segment = lineMapping[segmentIndex];
|
|
224
233
|
if (segment[4] !== functionNameIndex) continue;
|
|
234
|
+
if (ignoredSource && segment[1] !== undefined && ignoredSourceIndices?.has(segment[1]))
|
|
235
|
+
continue;
|
|
225
236
|
const source = getSourceFromSegment(segment, sources, ignoredSourceIndices, names);
|
|
226
237
|
if (!source) continue;
|
|
227
238
|
if (!source.isIgnoreListed) return source;
|
|
@@ -267,7 +278,7 @@ const findSourceContentByFileName = (
|
|
|
267
278
|
fileName: string,
|
|
268
279
|
): string | null => {
|
|
269
280
|
if (!sourcesContent) return null;
|
|
270
|
-
const sourceIndex = sources
|
|
281
|
+
const sourceIndex = getStringIndex(sources, fileName);
|
|
271
282
|
return sourceIndex === -1 ? null : (sourcesContent[sourceIndex] ?? null);
|
|
272
283
|
};
|
|
273
284
|
|