bippy 0.5.37 → 0.5.39
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/dist/index.iife.js +1 -1
- package/dist/install-hook-only.iife.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +15 -12
- package/dist/source.d.cts +25 -1
- package/dist/source.d.ts +25 -1
- package/dist/source.js +15 -12
- package/package.json +1 -1
- package/src/source/constants.ts +1 -1
- package/src/source/index.ts +2 -0
- package/src/source/inspect-hooks.ts +879 -0
- package/src/source/parse-hook-names.ts +224 -0
- package/src/types.ts +157 -22
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { HooksNode, HooksTree, HookSource } from "./inspect-hooks.js";
|
|
2
|
+
import { getSourceMap, getSourceFromSourceMap, type SourceMap } from "./symbolication.js";
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
5
|
+
export interface HookNames extends Map<string, string> {}
|
|
6
|
+
|
|
7
|
+
const UNNAMED_HOOKS = new Set([
|
|
8
|
+
"Effect",
|
|
9
|
+
"LayoutEffect",
|
|
10
|
+
"InsertionEffect",
|
|
11
|
+
"ImperativeHandle",
|
|
12
|
+
"DebugValue",
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
// HACK: matches `const/let/var [name, ...] = use...(...` or `const/let/var name = use...(...`
|
|
16
|
+
// across up to 10 lines; handles TypeScript generics like `useState<T>(`
|
|
17
|
+
const HOOK_DECLARATION_REGEX =
|
|
18
|
+
/(?:const|let|var)\s+((?:\[[\s\S]*?\]|\w+))\s*=\s*(?:[\w$.]+\.)*use[A-Z]\w*\s*(?:<[\s\S]*?>)?\s*\(/g;
|
|
19
|
+
|
|
20
|
+
export const getHookSourceLocationKey = (hookSource: HookSource): string =>
|
|
21
|
+
`${hookSource.fileName ?? ""}:${hookSource.lineNumber ?? 0}:${hookSource.columnNumber ?? 0}`;
|
|
22
|
+
|
|
23
|
+
const flattenHooksTree = (hooksTree: HooksTree): HooksNode[] => {
|
|
24
|
+
const hooksList: HooksNode[] = [];
|
|
25
|
+
const collectNamedHooks = (tree: HooksTree): void => {
|
|
26
|
+
for (const hook of tree) {
|
|
27
|
+
if (UNNAMED_HOOKS.has(hook.name)) continue;
|
|
28
|
+
hooksList.push(hook);
|
|
29
|
+
if (hook.subHooks.length > 0) collectNamedHooks(hook.subHooks);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
collectNamedHooks(hooksTree);
|
|
33
|
+
return hooksList;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const findSourceContentByFileName = (
|
|
37
|
+
sources: string[],
|
|
38
|
+
sourcesContent: string[] | undefined,
|
|
39
|
+
fileName: string,
|
|
40
|
+
): string | null => {
|
|
41
|
+
if (!sourcesContent) return null;
|
|
42
|
+
const sourceIndex = sources.indexOf(fileName);
|
|
43
|
+
return sourceIndex !== -1 ? (sourcesContent[sourceIndex] ?? null) : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getSourceContentFromSourceMap = (
|
|
47
|
+
sourceMap: SourceMap,
|
|
48
|
+
originalFileName: string,
|
|
49
|
+
): string | null => {
|
|
50
|
+
const directResult = findSourceContentByFileName(
|
|
51
|
+
sourceMap.sources,
|
|
52
|
+
sourceMap.sourcesContent,
|
|
53
|
+
originalFileName,
|
|
54
|
+
);
|
|
55
|
+
if (directResult) return directResult;
|
|
56
|
+
|
|
57
|
+
if (sourceMap.sections) {
|
|
58
|
+
for (const section of sourceMap.sections) {
|
|
59
|
+
const sectionResult = findSourceContentByFileName(
|
|
60
|
+
section.map.sources,
|
|
61
|
+
section.map.sourcesContent,
|
|
62
|
+
originalFileName,
|
|
63
|
+
);
|
|
64
|
+
if (sectionResult) return sectionResult;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const extractVariableNameFromBinding = (binding: string): string | null => {
|
|
71
|
+
const trimmed = binding.trim();
|
|
72
|
+
if (trimmed.startsWith("[")) {
|
|
73
|
+
const match = trimmed.match(/\[\s*(\w+)/);
|
|
74
|
+
return match ? match[1] : null;
|
|
75
|
+
}
|
|
76
|
+
return /^\w+$/.test(trimmed) ? trimmed : null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const extractHookVariableName = (
|
|
80
|
+
sourceCode: string,
|
|
81
|
+
lineNumber: number,
|
|
82
|
+
columnNumber: number,
|
|
83
|
+
): string | null => {
|
|
84
|
+
const lines = sourceCode.split("\n");
|
|
85
|
+
const hookLineIndex = lineNumber - 1;
|
|
86
|
+
|
|
87
|
+
if (hookLineIndex < 0 || hookLineIndex >= lines.length) return null;
|
|
88
|
+
|
|
89
|
+
const searchStartLine = Math.max(0, hookLineIndex - 10);
|
|
90
|
+
const chunkLines = lines.slice(searchStartLine, hookLineIndex + 1);
|
|
91
|
+
const sourceChunk = chunkLines.join("\n");
|
|
92
|
+
|
|
93
|
+
const allMatches = [...sourceChunk.matchAll(HOOK_DECLARATION_REGEX)];
|
|
94
|
+
|
|
95
|
+
const hookPositionInChunk = sourceChunk.lastIndexOf("\n") + 1 + columnNumber;
|
|
96
|
+
const closestMatch = allMatches.filter((match) => match.index! <= hookPositionInChunk).at(-1);
|
|
97
|
+
|
|
98
|
+
if (closestMatch) {
|
|
99
|
+
return extractVariableNameFromBinding(closestMatch[1]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
interface ResolvedSource {
|
|
106
|
+
sourceCode: string;
|
|
107
|
+
lineNumber: number;
|
|
108
|
+
columnNumber: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface SourceResolutionContext {
|
|
112
|
+
sourceMapsByFile: Map<string, SourceMap | null>;
|
|
113
|
+
sourceContentCache: Map<string, string | null>;
|
|
114
|
+
fetchFn?: (url: string) => Promise<Response>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const resolveOriginalSource = async (
|
|
118
|
+
runtimeFileName: string,
|
|
119
|
+
runtimeLine: number,
|
|
120
|
+
runtimeColumn: number,
|
|
121
|
+
context: SourceResolutionContext,
|
|
122
|
+
): Promise<ResolvedSource | null> => {
|
|
123
|
+
const { sourceMapsByFile, sourceContentCache, fetchFn } = context;
|
|
124
|
+
|
|
125
|
+
if (!sourceMapsByFile.has(runtimeFileName)) {
|
|
126
|
+
sourceMapsByFile.set(runtimeFileName, await getSourceMap(runtimeFileName, true, fetchFn));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const sourceMap = sourceMapsByFile.get(runtimeFileName) ?? null;
|
|
130
|
+
|
|
131
|
+
if (sourceMap) {
|
|
132
|
+
const originalLocation = getSourceFromSourceMap(sourceMap, runtimeLine, runtimeColumn);
|
|
133
|
+
if (originalLocation?.fileName && originalLocation.lineNumber !== undefined) {
|
|
134
|
+
const cacheKey = `sourcemap:${runtimeFileName}:${originalLocation.fileName}`;
|
|
135
|
+
if (!sourceContentCache.has(cacheKey)) {
|
|
136
|
+
sourceContentCache.set(
|
|
137
|
+
cacheKey,
|
|
138
|
+
getSourceContentFromSourceMap(sourceMap, originalLocation.fileName),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const originalSourceCode = sourceContentCache.get(cacheKey) ?? null;
|
|
142
|
+
if (originalSourceCode) {
|
|
143
|
+
return {
|
|
144
|
+
sourceCode: originalSourceCode,
|
|
145
|
+
lineNumber: originalLocation.lineNumber,
|
|
146
|
+
columnNumber: originalLocation.columnNumber ?? 0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!sourceContentCache.has(runtimeFileName)) {
|
|
153
|
+
try {
|
|
154
|
+
const fetchImpl = fetchFn ?? fetch;
|
|
155
|
+
const response = await fetchImpl(runtimeFileName);
|
|
156
|
+
sourceContentCache.set(runtimeFileName, response.ok ? await response.text() : null);
|
|
157
|
+
} catch {
|
|
158
|
+
sourceContentCache.set(runtimeFileName, null);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const runtimeSourceCode = sourceContentCache.get(runtimeFileName) ?? null;
|
|
163
|
+
if (runtimeSourceCode) {
|
|
164
|
+
return {
|
|
165
|
+
sourceCode: runtimeSourceCode,
|
|
166
|
+
lineNumber: runtimeLine,
|
|
167
|
+
columnNumber: runtimeColumn,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const parseHookNames = async (
|
|
175
|
+
hooksTree: HooksTree,
|
|
176
|
+
fetchFn?: (url: string) => Promise<Response>,
|
|
177
|
+
): Promise<HookNames> => {
|
|
178
|
+
const hookNames: HookNames = new Map();
|
|
179
|
+
const hooksList = flattenHooksTree(hooksTree);
|
|
180
|
+
|
|
181
|
+
if (hooksList.length === 0) return hookNames;
|
|
182
|
+
|
|
183
|
+
const resolutionContext: SourceResolutionContext = {
|
|
184
|
+
sourceMapsByFile: new Map(),
|
|
185
|
+
sourceContentCache: new Map(),
|
|
186
|
+
fetchFn,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
await Promise.all(
|
|
190
|
+
hooksList.map(async (hook) => {
|
|
191
|
+
const hookSource = hook.hookSource;
|
|
192
|
+
if (
|
|
193
|
+
!hookSource ||
|
|
194
|
+
!hookSource.fileName ||
|
|
195
|
+
hookSource.lineNumber === null ||
|
|
196
|
+
hookSource.columnNumber === null
|
|
197
|
+
) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const resolved = await resolveOriginalSource(
|
|
202
|
+
hookSource.fileName,
|
|
203
|
+
hookSource.lineNumber,
|
|
204
|
+
hookSource.columnNumber,
|
|
205
|
+
resolutionContext,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!resolved) return;
|
|
209
|
+
|
|
210
|
+
const variableName = extractHookVariableName(
|
|
211
|
+
resolved.sourceCode,
|
|
212
|
+
resolved.lineNumber,
|
|
213
|
+
resolved.columnNumber,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (variableName) {
|
|
217
|
+
const locationKey = getHookSourceLocationKey(hookSource);
|
|
218
|
+
hookNames.set(locationKey, variableName);
|
|
219
|
+
}
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return hookNames;
|
|
224
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -10,9 +10,67 @@ export type Flags = number;
|
|
|
10
10
|
export type Lanes = number;
|
|
11
11
|
export type TypeOfMode = number;
|
|
12
12
|
export type RootTag = 0 | 1 | 2;
|
|
13
|
-
export type LanePriority =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export type LanePriority =
|
|
14
|
+
| 0
|
|
15
|
+
| 1
|
|
16
|
+
| 2
|
|
17
|
+
| 3
|
|
18
|
+
| 4
|
|
19
|
+
| 5
|
|
20
|
+
| 6
|
|
21
|
+
| 7
|
|
22
|
+
| 8
|
|
23
|
+
| 9
|
|
24
|
+
| 10
|
|
25
|
+
| 11
|
|
26
|
+
| 12
|
|
27
|
+
| 13
|
|
28
|
+
| 14
|
|
29
|
+
| 15
|
|
30
|
+
| 16
|
|
31
|
+
| 17;
|
|
32
|
+
export type WorkTag =
|
|
33
|
+
| 0
|
|
34
|
+
| 1
|
|
35
|
+
| 2
|
|
36
|
+
| 3
|
|
37
|
+
| 4
|
|
38
|
+
| 5
|
|
39
|
+
| 6
|
|
40
|
+
| 7
|
|
41
|
+
| 8
|
|
42
|
+
| 9
|
|
43
|
+
| 10
|
|
44
|
+
| 11
|
|
45
|
+
| 12
|
|
46
|
+
| 13
|
|
47
|
+
| 14
|
|
48
|
+
| 15
|
|
49
|
+
| 16
|
|
50
|
+
| 17
|
|
51
|
+
| 18
|
|
52
|
+
| 19
|
|
53
|
+
| 20
|
|
54
|
+
| 21
|
|
55
|
+
| 22
|
|
56
|
+
| 23
|
|
57
|
+
| 24;
|
|
58
|
+
export type HookType =
|
|
59
|
+
| "useState"
|
|
60
|
+
| "useReducer"
|
|
61
|
+
| "useContext"
|
|
62
|
+
| "useRef"
|
|
63
|
+
| "useEffect"
|
|
64
|
+
| "useLayoutEffect"
|
|
65
|
+
| "useCallback"
|
|
66
|
+
| "useMemo"
|
|
67
|
+
| "useImperativeHandle"
|
|
68
|
+
| "useDebugValue"
|
|
69
|
+
| "useDeferredValue"
|
|
70
|
+
| "useTransition"
|
|
71
|
+
| "useMutableSource"
|
|
72
|
+
| "useOpaqueIdentifier"
|
|
73
|
+
| "useCacheRefresh";
|
|
16
74
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
75
|
export type FiberRoot = any;
|
|
18
76
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -27,10 +85,17 @@ export type React$AbstractComponent<_Config, _Instance = unknown> = any;
|
|
|
27
85
|
export type HostConfig = Record<string, any>;
|
|
28
86
|
|
|
29
87
|
// Structural interfaces
|
|
30
|
-
export interface Source {
|
|
88
|
+
export interface Source {
|
|
89
|
+
fileName: string;
|
|
90
|
+
lineNumber: number;
|
|
91
|
+
}
|
|
31
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
-
export interface RefObject {
|
|
33
|
-
|
|
93
|
+
export interface RefObject {
|
|
94
|
+
current: any;
|
|
95
|
+
}
|
|
96
|
+
export interface Thenable<T> {
|
|
97
|
+
then(resolve: () => T, reject?: () => T): T;
|
|
98
|
+
}
|
|
34
99
|
|
|
35
100
|
export interface ReactContext<T> {
|
|
36
101
|
$$typeof: symbol | number;
|
|
@@ -47,21 +112,66 @@ export interface ReactContext<T> {
|
|
|
47
112
|
displayName?: string;
|
|
48
113
|
}
|
|
49
114
|
|
|
50
|
-
export interface ReactProviderType<T> {
|
|
51
|
-
|
|
52
|
-
|
|
115
|
+
export interface ReactProviderType<T> {
|
|
116
|
+
$$typeof: symbol | number;
|
|
117
|
+
_context: ReactContext<T>;
|
|
118
|
+
}
|
|
119
|
+
export interface ReactProvider<T> {
|
|
120
|
+
$$typeof: symbol | number;
|
|
121
|
+
type: ReactProviderType<T>;
|
|
122
|
+
key: null | string;
|
|
123
|
+
ref: null;
|
|
124
|
+
props: { value: T; children?: ReactNode };
|
|
125
|
+
}
|
|
126
|
+
export interface ReactConsumer<T> {
|
|
127
|
+
$$typeof: symbol | number;
|
|
128
|
+
type: ReactContext<T>;
|
|
129
|
+
key: null | string;
|
|
130
|
+
ref: null;
|
|
131
|
+
props: { children: (value: T) => ReactNode; unstable_observedBits?: number };
|
|
132
|
+
}
|
|
53
133
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
export interface ReactPortal {
|
|
134
|
+
export interface ReactPortal {
|
|
135
|
+
$$typeof: symbol | number;
|
|
136
|
+
key: null | string;
|
|
137
|
+
containerInfo: any;
|
|
138
|
+
children: ReactNode;
|
|
139
|
+
implementation: any;
|
|
140
|
+
}
|
|
55
141
|
|
|
56
|
-
export interface ComponentSelector {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export interface
|
|
61
|
-
|
|
142
|
+
export interface ComponentSelector {
|
|
143
|
+
$$typeof: symbol | number;
|
|
144
|
+
value: React$AbstractComponent<never, unknown>;
|
|
145
|
+
}
|
|
146
|
+
export interface HasPseudoClassSelector {
|
|
147
|
+
$$typeof: symbol | number;
|
|
148
|
+
value: Selector[];
|
|
149
|
+
}
|
|
150
|
+
export interface RoleSelector {
|
|
151
|
+
$$typeof: symbol | number;
|
|
152
|
+
value: string;
|
|
153
|
+
}
|
|
154
|
+
export interface TextSelector {
|
|
155
|
+
$$typeof: symbol | number;
|
|
156
|
+
value: string;
|
|
157
|
+
}
|
|
158
|
+
export interface TestNameSelector {
|
|
159
|
+
$$typeof: symbol | number;
|
|
160
|
+
value: string;
|
|
161
|
+
}
|
|
162
|
+
export type Selector =
|
|
163
|
+
| ComponentSelector
|
|
164
|
+
| HasPseudoClassSelector
|
|
165
|
+
| RoleSelector
|
|
166
|
+
| TextSelector
|
|
167
|
+
| TestNameSelector;
|
|
62
168
|
|
|
63
169
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
-
export interface DevToolsConfig<
|
|
170
|
+
export interface DevToolsConfig<
|
|
171
|
+
Instance = any,
|
|
172
|
+
TextInstance = any,
|
|
173
|
+
RendererInspectionConfig = any,
|
|
174
|
+
> {
|
|
65
175
|
bundleType: BundleType;
|
|
66
176
|
version: string;
|
|
67
177
|
rendererPackageName: string;
|
|
@@ -76,12 +186,37 @@ export interface SuspenseHydrationCallbacks<SuspenseInstance = unknown> {
|
|
|
76
186
|
|
|
77
187
|
export interface TransitionTracingCallbacks {
|
|
78
188
|
onTransitionStart?: (transitionName: string, startTime: number) => void;
|
|
79
|
-
onTransitionProgress?: (
|
|
80
|
-
|
|
189
|
+
onTransitionProgress?: (
|
|
190
|
+
transitionName: string,
|
|
191
|
+
startTime: number,
|
|
192
|
+
currentTime: number,
|
|
193
|
+
pending: Array<{ name: null | string }>,
|
|
194
|
+
) => void;
|
|
195
|
+
onTransitionIncomplete?: (
|
|
196
|
+
transitionName: string,
|
|
197
|
+
startTime: number,
|
|
198
|
+
deletions: Array<{ type: string; name?: string; newName?: string; endTime: number }>,
|
|
199
|
+
) => void;
|
|
81
200
|
onTransitionComplete?: (transitionName: string, startTime: number, endTime: number) => void;
|
|
82
|
-
onMarkerProgress?: (
|
|
83
|
-
|
|
84
|
-
|
|
201
|
+
onMarkerProgress?: (
|
|
202
|
+
transitionName: string,
|
|
203
|
+
marker: string,
|
|
204
|
+
startTime: number,
|
|
205
|
+
currentTime: number,
|
|
206
|
+
pending: Array<{ name: null | string }>,
|
|
207
|
+
) => void;
|
|
208
|
+
onMarkerIncomplete?: (
|
|
209
|
+
transitionName: string,
|
|
210
|
+
marker: string,
|
|
211
|
+
startTime: number,
|
|
212
|
+
deletions: Array<{ type: string; name?: string; newName?: string; endTime: number }>,
|
|
213
|
+
) => void;
|
|
214
|
+
onMarkerComplete?: (
|
|
215
|
+
transitionName: string,
|
|
216
|
+
marker: string,
|
|
217
|
+
startTime: number,
|
|
218
|
+
endTime: number,
|
|
219
|
+
) => void;
|
|
85
220
|
}
|
|
86
221
|
|
|
87
222
|
// The base Fiber interface from react-reconciler, used to derive bippy's Fiber below
|