@eventra_dev/eventra-cli 0.1.28 → 0.1.30
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/engine/functionAnalyzer.js +15 -50
- package/dist/engine/resolver.js +57 -138
- package/dist/engine/scanner.js +17 -148
- package/dist/engine/trackingDetector.js +3 -54
- package/dist/utils/universal.js +52 -89
- package/package.json +1 -1
- package/src/engine/functionAnalyzer.ts +14 -54
- package/src/engine/resolver.ts +83 -184
- package/src/engine/scanner.ts +15 -205
- package/src/engine/trackingDetector.ts +3 -80
- package/src/utils/universal.ts +64 -96
package/src/engine/scanner.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveNodeValue } from "./resolver";
|
|
3
3
|
import { resolveFunctionFromCall } from "./callResolver";
|
|
4
|
-
import { resolveExportedSymbol } from "./exportResolver";
|
|
5
4
|
import { findTrackParamIndex } from "./functionAnalyzer";
|
|
6
5
|
import { EventraConfig } from "../types";
|
|
7
6
|
import { isExternalFile } from "./boundary";
|
|
8
|
-
import { isTrackingCall } from "./trackingDetector";
|
|
9
7
|
|
|
10
8
|
function getCallName(expr: ts.Expression): string {
|
|
11
9
|
if (ts.isIdentifier(expr)) return expr.text;
|
|
@@ -13,25 +11,6 @@ function getCallName(expr: ts.Expression): string {
|
|
|
13
11
|
return "";
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
function getJsxAttrName(name: ts.JsxAttributeName): string | null {
|
|
17
|
-
if (ts.isIdentifier(name)) return name.text;
|
|
18
|
-
if (ts.isJsxNamespacedName(name)) return name.name.text;
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// unwrap
|
|
23
|
-
function unwrap(expr: ts.Expression): ts.Expression {
|
|
24
|
-
while (
|
|
25
|
-
ts.isAsExpression(expr) ||
|
|
26
|
-
ts.isParenthesizedExpression(expr) ||
|
|
27
|
-
ts.isTypeAssertionExpression(expr) ||
|
|
28
|
-
ts.isNonNullExpression(expr)
|
|
29
|
-
) {
|
|
30
|
-
expr = expr.expression;
|
|
31
|
-
}
|
|
32
|
-
return expr;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
14
|
function isValidEvent(v: string) {
|
|
36
15
|
return (
|
|
37
16
|
v &&
|
|
@@ -42,43 +21,6 @@ function isValidEvent(v: string) {
|
|
|
42
21
|
);
|
|
43
22
|
}
|
|
44
23
|
|
|
45
|
-
function isPropUsedInTracking(
|
|
46
|
-
fn: ts.FunctionLikeDeclaration,
|
|
47
|
-
checker: ts.TypeChecker,
|
|
48
|
-
prop: string
|
|
49
|
-
) {
|
|
50
|
-
let found = false;
|
|
51
|
-
|
|
52
|
-
function visit(node: ts.Node) {
|
|
53
|
-
if (ts.isCallExpression(node)) {
|
|
54
|
-
if (!isTrackingCall(node, checker)) {
|
|
55
|
-
ts.forEachChild(node, visit);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const arg of node.arguments) {
|
|
60
|
-
const a = unwrap(arg as ts.Expression);
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
(ts.isIdentifier(a) && a.text === prop) ||
|
|
64
|
-
(ts.isPropertyAccessExpression(a) &&
|
|
65
|
-
a.name.text === prop)
|
|
66
|
-
) {
|
|
67
|
-
found = true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
ts.forEachChild(node, visit);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (fn.body) {
|
|
76
|
-
visit(fn.body);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return found;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
24
|
export type ScanResult = {
|
|
83
25
|
events: Set<string>;
|
|
84
26
|
detectedFunctionWrappers: Set<string>;
|
|
@@ -90,7 +32,6 @@ export function scanSource(
|
|
|
90
32
|
checker: ts.TypeChecker,
|
|
91
33
|
config: EventraConfig,
|
|
92
34
|
visited = new Set<string>(),
|
|
93
|
-
depth = 0,
|
|
94
35
|
inheritedParamMap?: Map<string, ts.Expression>
|
|
95
36
|
): ScanResult {
|
|
96
37
|
const events = new Set<string>();
|
|
@@ -104,7 +45,6 @@ export function scanSource(
|
|
|
104
45
|
visited.add(source.fileName);
|
|
105
46
|
|
|
106
47
|
function visit(node: ts.Node) {
|
|
107
|
-
// CALL EXPRESSIONS
|
|
108
48
|
if (ts.isCallExpression(node)) {
|
|
109
49
|
const name = getCallName(node.expression);
|
|
110
50
|
|
|
@@ -112,50 +52,28 @@ export function scanSource(
|
|
|
112
52
|
w => w.name === name
|
|
113
53
|
);
|
|
114
54
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
let resolvedFn = resolveFunctionFromCall(node.expression, checker);
|
|
55
|
+
const resolvedFn = resolveFunctionFromCall(node.expression, checker);
|
|
118
56
|
|
|
119
57
|
const isAutoWrapper =
|
|
120
|
-
resolvedFn && findTrackParamIndex(resolvedFn) !== null;
|
|
58
|
+
!!resolvedFn && findTrackParamIndex(resolvedFn) !== null;
|
|
121
59
|
|
|
122
|
-
|
|
60
|
+
const isTracking =
|
|
61
|
+
name === "track" ||
|
|
123
62
|
isKnownWrapper ||
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// fallback track(...)
|
|
129
|
-
if (!isTracking) {
|
|
130
|
-
if (
|
|
131
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
132
|
-
node.expression.name.text === "track"
|
|
133
|
-
) {
|
|
134
|
-
isTracking = true;
|
|
135
|
-
} else if (
|
|
136
|
-
ts.isIdentifier(node.expression) &&
|
|
137
|
-
node.expression.text === "track"
|
|
138
|
-
) {
|
|
139
|
-
isTracking = true;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
63
|
+
(isAutoWrapper &&
|
|
64
|
+
resolvedFn &&
|
|
65
|
+
!isExternalFile(resolvedFn.getSourceFile().fileName));
|
|
142
66
|
|
|
143
67
|
if (!isTracking) {
|
|
144
68
|
ts.forEachChild(node, visit);
|
|
145
69
|
return;
|
|
146
70
|
}
|
|
147
71
|
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
!isKnownWrapper &&
|
|
151
|
-
isAutoWrapper &&
|
|
152
|
-
!isTrackingByType &&
|
|
153
|
-
!name.startsWith("track")
|
|
154
|
-
) {
|
|
72
|
+
// авто-детект wrapper
|
|
73
|
+
if (!isKnownWrapper && isAutoWrapper && name !== "track") {
|
|
155
74
|
detectedFunctionWrappers.add(name);
|
|
156
75
|
}
|
|
157
76
|
|
|
158
|
-
// PARAM MAP
|
|
159
77
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
160
78
|
|
|
161
79
|
if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
|
|
@@ -169,9 +87,7 @@ export function scanSource(
|
|
|
169
87
|
});
|
|
170
88
|
}
|
|
171
89
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// MAIN RESOLVE
|
|
90
|
+
// ✅ ТОЛЬКО правильный аргумент
|
|
175
91
|
if (resolvedFn) {
|
|
176
92
|
const idx = findTrackParamIndex(resolvedFn);
|
|
177
93
|
|
|
@@ -182,22 +98,11 @@ export function scanSource(
|
|
|
182
98
|
paramMap
|
|
183
99
|
);
|
|
184
100
|
|
|
185
|
-
res.values
|
|
186
|
-
if (isValidEvent(v))
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// fallback
|
|
194
|
-
if (!handled) {
|
|
195
|
-
for (const arg of node.arguments) {
|
|
196
|
-
const res = resolveNodeValue(arg, checker, paramMap);
|
|
197
|
-
|
|
198
|
-
res.values.forEach(v => {
|
|
199
|
-
if (isValidEvent(v)) events.add(v);
|
|
200
|
-
});
|
|
101
|
+
for (const v of res.values) {
|
|
102
|
+
if (isValidEvent(v)) {
|
|
103
|
+
events.add(v);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
201
106
|
}
|
|
202
107
|
}
|
|
203
108
|
|
|
@@ -211,7 +116,6 @@ export function scanSource(
|
|
|
211
116
|
checker,
|
|
212
117
|
config,
|
|
213
118
|
visited,
|
|
214
|
-
depth + 1,
|
|
215
119
|
paramMap
|
|
216
120
|
);
|
|
217
121
|
|
|
@@ -220,100 +124,6 @@ export function scanSource(
|
|
|
220
124
|
}
|
|
221
125
|
}
|
|
222
126
|
|
|
223
|
-
// JSX
|
|
224
|
-
if (
|
|
225
|
-
ts.isJsxSelfClosingElement(node) ||
|
|
226
|
-
ts.isJsxOpeningElement(node)
|
|
227
|
-
) {
|
|
228
|
-
const tagNode =
|
|
229
|
-
ts.isPropertyAccessExpression(node.tagName)
|
|
230
|
-
? node.tagName.name
|
|
231
|
-
: node.tagName;
|
|
232
|
-
|
|
233
|
-
let symbol = checker.getSymbolAtLocation(tagNode);
|
|
234
|
-
|
|
235
|
-
if (symbol) {
|
|
236
|
-
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
|
|
240
|
-
symbol = checker.getAliasedSymbol(symbol);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (!symbol) {
|
|
244
|
-
ts.forEachChild(node, visit);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const tagName =
|
|
249
|
-
ts.isPropertyAccessExpression(node.tagName)
|
|
250
|
-
? node.tagName.name.getText()
|
|
251
|
-
: node.tagName.getText();
|
|
252
|
-
|
|
253
|
-
for (const decl of symbol.getDeclarations() ?? []) {
|
|
254
|
-
let fn: ts.FunctionLikeDeclaration | null = null;
|
|
255
|
-
|
|
256
|
-
if (ts.isFunctionDeclaration(decl)) fn = decl;
|
|
257
|
-
|
|
258
|
-
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
259
|
-
const init = decl.initializer;
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
ts.isArrowFunction(init) ||
|
|
263
|
-
ts.isFunctionExpression(init)
|
|
264
|
-
) {
|
|
265
|
-
fn = init;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (!fn) continue;
|
|
270
|
-
|
|
271
|
-
const param = fn.parameters[0];
|
|
272
|
-
if (!param || !ts.isObjectBindingPattern(param.name)) continue;
|
|
273
|
-
|
|
274
|
-
for (const el of param.name.elements) {
|
|
275
|
-
const prop = el.name.getText();
|
|
276
|
-
|
|
277
|
-
const isEvent =
|
|
278
|
-
isPropUsedInTracking(fn, checker, prop) ||
|
|
279
|
-
config.wrappers.some(
|
|
280
|
-
w => w.name === tagName && w.prop === prop
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
if (!isEvent) continue;
|
|
284
|
-
|
|
285
|
-
const attr = node.attributes.properties.find(p => {
|
|
286
|
-
if (!ts.isJsxAttribute(p)) return false;
|
|
287
|
-
return getJsxAttrName(p.name) === prop;
|
|
288
|
-
}) as ts.JsxAttribute | undefined;
|
|
289
|
-
|
|
290
|
-
if (!attr || !attr.initializer) continue;
|
|
291
|
-
|
|
292
|
-
const expr = ts.isJsxExpression(attr.initializer)
|
|
293
|
-
? attr.initializer.expression
|
|
294
|
-
: attr.initializer;
|
|
295
|
-
|
|
296
|
-
if (!expr) continue;
|
|
297
|
-
|
|
298
|
-
const paramMap = new Map<string, ts.Expression>();
|
|
299
|
-
paramMap.set(prop, expr);
|
|
300
|
-
|
|
301
|
-
const res = scanSource(
|
|
302
|
-
fn.getSourceFile(),
|
|
303
|
-
checker,
|
|
304
|
-
config,
|
|
305
|
-
visited,
|
|
306
|
-
depth + 1,
|
|
307
|
-
paramMap
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
res.events.forEach(e => events.add(e));
|
|
311
|
-
|
|
312
|
-
detectedComponentWrappers.set(tagName, prop);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
127
|
ts.forEachChild(node, visit);
|
|
318
128
|
}
|
|
319
129
|
|
|
@@ -1,93 +1,16 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
-
function isStringLike(type: ts.Type) {
|
|
4
|
-
return (
|
|
5
|
-
(type.flags & ts.TypeFlags.StringLike) !== 0 ||
|
|
6
|
-
(type.flags & ts.TypeFlags.StringLiteral) !== 0
|
|
7
|
-
);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function hasTrackLikeSignature(
|
|
11
|
-
type: ts.Type,
|
|
12
|
-
checker: ts.TypeChecker
|
|
13
|
-
): boolean {
|
|
14
|
-
const sigs = type.getCallSignatures();
|
|
15
|
-
|
|
16
|
-
for (const sig of sigs) {
|
|
17
|
-
const params = sig.getParameters();
|
|
18
|
-
if (!params.length) continue;
|
|
19
|
-
|
|
20
|
-
const t = checker.getTypeOfSymbolAtLocation(
|
|
21
|
-
params[0],
|
|
22
|
-
params[0].valueDeclaration!
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
if (isStringLike(t)) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isEventraLikeType(
|
|
34
|
-
type: ts.Type,
|
|
35
|
-
checker: ts.TypeChecker
|
|
36
|
-
): boolean {
|
|
37
|
-
const props = type.getProperties();
|
|
38
|
-
|
|
39
|
-
for (const prop of props) {
|
|
40
|
-
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
41
|
-
if (!decl) continue;
|
|
42
|
-
|
|
43
|
-
const t = checker.getTypeOfSymbolAtLocation(prop, decl);
|
|
44
|
-
|
|
45
|
-
if (hasTrackLikeSignature(t, checker)) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
3
|
export function isTrackingCall(
|
|
54
|
-
node: ts.CallExpression
|
|
55
|
-
checker: ts.TypeChecker
|
|
4
|
+
node: ts.CallExpression
|
|
56
5
|
): boolean {
|
|
57
6
|
const expr = node.expression;
|
|
58
7
|
|
|
59
|
-
// fn(...)
|
|
60
8
|
if (ts.isIdentifier(expr)) {
|
|
61
|
-
|
|
62
|
-
if (hasTrackLikeSignature(type, checker)) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
9
|
+
return expr.text === "track";
|
|
65
10
|
}
|
|
66
11
|
|
|
67
|
-
// obj.method(...)
|
|
68
12
|
if (ts.isPropertyAccessExpression(expr)) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// объект похож на tracker
|
|
72
|
-
if (isEventraLikeType(objType, checker)) {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// fallback
|
|
77
|
-
const methodType = checker.getTypeAtLocation(expr.name);
|
|
78
|
-
|
|
79
|
-
if (hasTrackLikeSignature(methodType, checker)) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// obj["track"](...)
|
|
85
|
-
if (ts.isElementAccessExpression(expr)) {
|
|
86
|
-
const objType = checker.getTypeAtLocation(expr.expression);
|
|
87
|
-
|
|
88
|
-
if (isEventraLikeType(objType, checker)) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
13
|
+
return expr.name.text === "track";
|
|
91
14
|
}
|
|
92
15
|
|
|
93
16
|
return false;
|
package/src/utils/universal.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { ParseResult } from "../types";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Remove comments
|
|
5
5
|
function stripComments(input: string): string {
|
|
6
6
|
return input
|
|
7
7
|
.replace(/<!--[\s\S]*?-->/g, "")
|
|
@@ -9,80 +9,78 @@ function stripComments(input: string): string {
|
|
|
9
9
|
.replace(/\/\/.*$/gm, "");
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
function
|
|
12
|
+
// Basic filter
|
|
13
|
+
function looksLikeTracking(code: string) {
|
|
14
|
+
return /track/i.test(code);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// HTML inline handlers (onclick="")
|
|
18
|
+
function extractHTMLHandlers(input: string): string[] {
|
|
14
19
|
const result: string[] = [];
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
const matches = input.matchAll(
|
|
22
|
+
/\bon\w+\s*=\s*["']([^"']+)["']/g
|
|
23
|
+
);
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
for (const m of matches) {
|
|
26
|
+
if (looksLikeTracking(m[1])) {
|
|
27
|
+
result.push(m[1]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else if (ch === inString) inString = null;
|
|
29
|
-
i++;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
34
|
+
// Vue handlers (@click / v-on)
|
|
35
|
+
function extractVueHandlers(input: string): string[] {
|
|
36
|
+
const result: string[] = [];
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
+
const matches = input.matchAll(
|
|
39
|
+
/(?:@|v-on:)[\w:-]+\s*=\s*["']([^"']+)["']/g
|
|
40
|
+
);
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
for (const m of matches) {
|
|
43
|
+
if (looksLikeTracking(m[1])) {
|
|
44
|
+
result.push(m[1]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
else if (input[j] === "}") depth--;
|
|
46
|
-
j++;
|
|
47
|
-
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
// Svelte handlers (on:click)
|
|
52
|
+
function extractSvelteHandlers(input: string): string[] {
|
|
53
|
+
const result: string[] = [];
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const matches = input.matchAll(
|
|
56
|
+
/\bon:[\w]+\s*=\s*{([^}]+)}/g
|
|
57
|
+
);
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
for (const m of matches) {
|
|
60
|
+
if (looksLikeTracking(m[1])) {
|
|
61
|
+
result.push(m[1]);
|
|
59
62
|
}
|
|
60
|
-
|
|
61
|
-
i++;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
return result;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
//
|
|
68
|
-
function
|
|
68
|
+
// JSX / template { ... }
|
|
69
|
+
function extractJSExpressions(input: string): string[] {
|
|
69
70
|
const result: string[] = [];
|
|
70
71
|
|
|
71
|
-
const
|
|
72
|
+
const matches = input.matchAll(/\{([^}]+)\}/g);
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (code && /[a-zA-Z0-9_$]\s*\(/.test(code)) {
|
|
78
|
-
result.push(code);
|
|
74
|
+
for (const m of matches) {
|
|
75
|
+
if (looksLikeTracking(m[1])) {
|
|
76
|
+
result.push(m[1]);
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
return result;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
//
|
|
83
|
+
// dynamic import()
|
|
86
84
|
function extractDynamicImports(
|
|
87
85
|
input: string,
|
|
88
86
|
file: string
|
|
@@ -90,47 +88,25 @@ function extractDynamicImports(
|
|
|
90
88
|
const deps: string[] = [];
|
|
91
89
|
const calls: string[] = [];
|
|
92
90
|
|
|
93
|
-
const
|
|
91
|
+
const matches = input.matchAll(
|
|
92
|
+
/import\(\s*["'](.+?)["']\s*\)/g
|
|
93
|
+
);
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const mod = match[1];
|
|
95
|
+
for (const m of matches) {
|
|
96
|
+
const mod = m[1];
|
|
98
97
|
|
|
99
98
|
const resolved = mod.startsWith("/")
|
|
100
99
|
? path.resolve(process.cwd(), "." + mod)
|
|
101
100
|
: path.resolve(path.dirname(file), mod);
|
|
102
101
|
|
|
103
102
|
deps.push(resolved);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// also extract calls chained after import
|
|
107
|
-
const callRegex = /import\([^)]+\)\.then\(([^)]+)\)/g;
|
|
108
|
-
|
|
109
|
-
while ((match = callRegex.exec(input))) {
|
|
110
|
-
calls.push(match[1]);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { deps, calls };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Loose JS extractor
|
|
117
|
-
function extractLooseJS(input: string): string[] {
|
|
118
|
-
const result: string[] = [];
|
|
119
|
-
|
|
120
|
-
const lines = input.split("\n");
|
|
121
103
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (!l) continue;
|
|
126
|
-
if (l.startsWith("<")) continue;
|
|
127
|
-
|
|
128
|
-
if (/[a-zA-Z0-9_$]\s*\(/.test(l)) {
|
|
129
|
-
result.push(l);
|
|
104
|
+
if (looksLikeTracking(mod)) {
|
|
105
|
+
calls.push(`import("${mod}")`);
|
|
130
106
|
}
|
|
131
107
|
}
|
|
132
108
|
|
|
133
|
-
return
|
|
109
|
+
return { deps, calls };
|
|
134
110
|
}
|
|
135
111
|
|
|
136
112
|
// UNIVERSAL PARSER
|
|
@@ -170,38 +146,30 @@ export function parseUniversal(
|
|
|
170
146
|
);
|
|
171
147
|
|
|
172
148
|
for (const m of scripts) {
|
|
173
|
-
const attrs = m[1];
|
|
174
|
-
|
|
175
|
-
if (/type=["']application\/json/.test(attrs)) continue;
|
|
176
|
-
|
|
177
149
|
if (m[2].trim()) {
|
|
178
150
|
parts.push(m[2]);
|
|
179
151
|
}
|
|
180
152
|
}
|
|
181
153
|
|
|
182
|
-
//
|
|
154
|
+
// TEMPLATE PART
|
|
183
155
|
const noScripts = clean.replace(
|
|
184
156
|
/<script[\s\S]*?<\/script>/g,
|
|
185
157
|
""
|
|
186
158
|
);
|
|
187
159
|
|
|
188
|
-
//
|
|
189
|
-
parts.push(...
|
|
160
|
+
// handlers
|
|
161
|
+
parts.push(...extractHTMLHandlers(noScripts));
|
|
162
|
+
parts.push(...extractVueHandlers(noScripts));
|
|
163
|
+
parts.push(...extractSvelteHandlers(noScripts));
|
|
164
|
+
|
|
165
|
+
// JSX / Astro / Vue expressions
|
|
166
|
+
parts.push(...extractJSExpressions(clean));
|
|
190
167
|
|
|
191
|
-
//
|
|
168
|
+
// dynamic imports
|
|
192
169
|
const dyn = extractDynamicImports(clean, file);
|
|
193
170
|
deps.push(...dyn.deps);
|
|
194
171
|
parts.push(...dyn.calls);
|
|
195
172
|
|
|
196
|
-
// LOOSE JS
|
|
197
|
-
parts.push(...extractLooseJS(noScripts));
|
|
198
|
-
|
|
199
|
-
// EXPRESSIONS
|
|
200
|
-
const extracted = extractJS(clean);
|
|
201
|
-
for (const e of extracted) {
|
|
202
|
-
parts.push(e + ";");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
173
|
return {
|
|
206
174
|
code: [...new Set(parts)].join("\n"),
|
|
207
175
|
deps: [...new Set(deps.filter(Boolean))],
|