@eventra_dev/eventra-cli 0.1.31 → 0.1.33
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/ARCHITECTURE.md +88 -0
- package/dist/engine/callResolver.js +56 -25
- package/dist/engine/languageService.js +36 -11
- package/dist/engine/scanner.js +74 -71
- package/dist/utils/universal.js +73 -21
- package/package.json +1 -1
- package/src/engine/callResolver.ts +61 -28
- package/src/engine/languageService.ts +50 -16
- package/src/engine/scanner.ts +83 -94
- package/src/utils/universal.ts +90 -21
package/src/engine/scanner.ts
CHANGED
|
@@ -173,15 +173,13 @@ export function scanSource(
|
|
|
173
173
|
config: EventraConfig,
|
|
174
174
|
visited = new Set<string>(),
|
|
175
175
|
inheritedParamMap?: Map<string, ts.Expression>,
|
|
176
|
-
wrapperCache
|
|
176
|
+
wrapperCache = new Map<string, string | null>()
|
|
177
177
|
): ScanResult {
|
|
178
|
-
wrapperCache = wrapperCache ?? new Map();
|
|
179
|
-
|
|
180
178
|
const events = new Set<string>();
|
|
181
179
|
const detectedFunctionWrappers = new Set<string>();
|
|
182
180
|
const detectedComponentWrappers = new Map<string, string>();
|
|
183
181
|
|
|
184
|
-
if (visited.
|
|
182
|
+
if (visited.has(source.fileName) || visited.size > 200) {
|
|
185
183
|
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -189,76 +187,58 @@ export function scanSource(
|
|
|
189
187
|
|
|
190
188
|
function visit(node: ts.Node) {
|
|
191
189
|
// JSX WRAPPERS
|
|
192
|
-
if (
|
|
193
|
-
ts.isJsxSelfClosingElement(node) ||
|
|
194
|
-
ts.isJsxOpeningElement(node)
|
|
195
|
-
) {
|
|
190
|
+
if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
|
|
196
191
|
const tagName = getJsxTagName(node.tagName);
|
|
197
192
|
|
|
198
193
|
// skip html
|
|
199
|
-
if (
|
|
194
|
+
if (tagName[0] === tagName[0].toLowerCase()) {
|
|
200
195
|
ts.forEachChild(node, visit);
|
|
201
196
|
return;
|
|
202
197
|
}
|
|
203
198
|
|
|
204
|
-
let wrapper = config.wrappers.find(
|
|
205
|
-
(w) => w.name === tagName
|
|
206
|
-
);
|
|
199
|
+
let wrapper = config.wrappers.find(w => w.name === tagName);
|
|
207
200
|
|
|
201
|
+
// AUTO DETECT
|
|
208
202
|
if (!wrapper) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
203
|
+
if (wrapperCache.has(tagName)) {
|
|
204
|
+
const cached = wrapperCache.get(tagName);
|
|
205
|
+
if (cached) wrapper = { name: tagName, prop: cached };
|
|
206
|
+
} else {
|
|
207
|
+
let symbol = checker.getSymbolAtLocation(node.tagName);
|
|
212
208
|
|
|
213
|
-
|
|
214
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
209
|
+
if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
|
|
215
210
|
symbol = checker.getAliasedSymbol(symbol);
|
|
216
211
|
}
|
|
217
212
|
|
|
218
|
-
symbol
|
|
219
|
-
resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
220
|
-
|
|
221
|
-
cacheKey = checker.getFullyQualifiedName(symbol);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (wrapperCache?.has(cacheKey)) {
|
|
225
|
-
const cached = wrapperCache.get(cacheKey);
|
|
226
|
-
if (cached) {
|
|
227
|
-
wrapper = { name: tagName, prop: cached };
|
|
228
|
-
}
|
|
229
|
-
} else if (symbol) {
|
|
230
|
-
let detected: string | null = null;
|
|
213
|
+
if (symbol) {
|
|
214
|
+
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
231
215
|
|
|
232
|
-
|
|
233
|
-
|
|
216
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
217
|
+
let fn: ts.FunctionLikeDeclaration | null = null;
|
|
234
218
|
|
|
235
|
-
|
|
219
|
+
if (ts.isFunctionDeclaration(decl)) fn = decl;
|
|
236
220
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
221
|
+
if (
|
|
222
|
+
ts.isVariableDeclaration(decl) &&
|
|
223
|
+
decl.initializer &&
|
|
224
|
+
(ts.isArrowFunction(decl.initializer) ||
|
|
225
|
+
ts.isFunctionExpression(decl.initializer))
|
|
226
|
+
) {
|
|
227
|
+
fn = decl.initializer;
|
|
228
|
+
}
|
|
245
229
|
|
|
246
|
-
|
|
230
|
+
if (!fn) continue;
|
|
247
231
|
|
|
248
|
-
|
|
232
|
+
const prop = findTrackedPropName(fn, checker);
|
|
233
|
+
wrapperCache.set(tagName, prop ?? null);
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
235
|
+
if (prop) {
|
|
236
|
+
wrapper = { name: tagName, prop };
|
|
237
|
+
detectedComponentWrappers.set(tagName, prop);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
253
240
|
}
|
|
254
241
|
}
|
|
255
|
-
|
|
256
|
-
wrapperCache?.set(cacheKey, detected);
|
|
257
|
-
|
|
258
|
-
if (detected) {
|
|
259
|
-
wrapper = { name: tagName, prop: detected };
|
|
260
|
-
detectedComponentWrappers.set(tagName, detected);
|
|
261
|
-
}
|
|
262
242
|
}
|
|
263
243
|
}
|
|
264
244
|
|
|
@@ -268,25 +248,21 @@ export function scanSource(
|
|
|
268
248
|
|
|
269
249
|
const name = getJsxAttrName(attr.name);
|
|
270
250
|
if (name !== wrapper.prop) continue;
|
|
251
|
+
|
|
271
252
|
if (!attr.initializer) continue;
|
|
272
253
|
|
|
273
254
|
if (ts.isStringLiteral(attr.initializer)) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
255
|
+
const v = attr.initializer.text;
|
|
256
|
+
if (isValidEvent(v)) events.add(v);
|
|
277
257
|
}
|
|
278
258
|
|
|
279
259
|
if (ts.isJsxExpression(attr.initializer)) {
|
|
280
260
|
const expr = attr.initializer.expression;
|
|
281
261
|
if (!expr) continue;
|
|
282
262
|
|
|
283
|
-
const res = resolveNodeValue(
|
|
284
|
-
expr,
|
|
285
|
-
checker,
|
|
286
|
-
inheritedParamMap
|
|
287
|
-
);
|
|
263
|
+
const res = resolveNodeValue(expr, checker, inheritedParamMap);
|
|
288
264
|
|
|
289
|
-
res.values.forEach(
|
|
265
|
+
res.values.forEach(v => {
|
|
290
266
|
if (isValidEvent(v)) events.add(v);
|
|
291
267
|
});
|
|
292
268
|
}
|
|
@@ -297,40 +273,56 @@ export function scanSource(
|
|
|
297
273
|
return;
|
|
298
274
|
}
|
|
299
275
|
|
|
300
|
-
// FUNCTION
|
|
276
|
+
// FUNCTION CALLS
|
|
301
277
|
if (ts.isCallExpression(node)) {
|
|
302
|
-
|
|
278
|
+
let trackParamIndex: number | null = null;
|
|
279
|
+
let isTracking = false;
|
|
280
|
+
|
|
281
|
+
const callName = getCallName(node.expression);
|
|
282
|
+
|
|
283
|
+
// direct .track()
|
|
284
|
+
if (
|
|
285
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
286
|
+
node.expression.name.text === "track"
|
|
287
|
+
) {
|
|
288
|
+
isTracking = true;
|
|
289
|
+
trackParamIndex = 0;
|
|
290
|
+
}
|
|
303
291
|
|
|
304
|
-
|
|
305
|
-
|
|
292
|
+
// config wrapper
|
|
293
|
+
const manualWrapper = config.functionWrappers.find(
|
|
294
|
+
w => w.name === callName
|
|
306
295
|
);
|
|
307
296
|
|
|
297
|
+
if (manualWrapper) {
|
|
298
|
+
isTracking = true;
|
|
299
|
+
trackParamIndex = 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// resolve function → check body
|
|
308
303
|
const resolvedFn = resolveFunctionFromCall(
|
|
309
304
|
node.expression,
|
|
310
305
|
checker
|
|
311
306
|
);
|
|
312
307
|
|
|
313
|
-
|
|
314
|
-
|
|
308
|
+
if (resolvedFn) {
|
|
309
|
+
const idx = findTrackParamIndex(resolvedFn);
|
|
315
310
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
311
|
+
if (idx !== null) {
|
|
312
|
+
isTracking = true;
|
|
313
|
+
trackParamIndex = idx;
|
|
314
|
+
|
|
315
|
+
if (!manualWrapper && callName !== "track") {
|
|
316
|
+
detectedFunctionWrappers.add(callName);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
324
320
|
|
|
325
321
|
if (!isTracking) {
|
|
326
322
|
ts.forEachChild(node, visit);
|
|
327
323
|
return;
|
|
328
324
|
}
|
|
329
325
|
|
|
330
|
-
if (!isKnownWrapper && isAutoWrapper && name !== "track") {
|
|
331
|
-
detectedFunctionWrappers.add(name);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
326
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
335
327
|
|
|
336
328
|
if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
|
|
@@ -344,22 +336,19 @@ export function scanSource(
|
|
|
344
336
|
});
|
|
345
337
|
}
|
|
346
338
|
|
|
347
|
-
if (
|
|
348
|
-
const
|
|
339
|
+
if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
|
|
340
|
+
const res = resolveNodeValue(
|
|
341
|
+
node.arguments[trackParamIndex],
|
|
342
|
+
checker,
|
|
343
|
+
paramMap
|
|
344
|
+
);
|
|
349
345
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
checker,
|
|
354
|
-
paramMap
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
res.values.forEach((v) => {
|
|
358
|
-
if (isValidEvent(v)) events.add(v);
|
|
359
|
-
});
|
|
360
|
-
}
|
|
346
|
+
res.values.forEach(v => {
|
|
347
|
+
if (isValidEvent(v)) events.add(v);
|
|
348
|
+
});
|
|
361
349
|
}
|
|
362
350
|
|
|
351
|
+
// CROSS FILE
|
|
363
352
|
if (resolvedFn) {
|
|
364
353
|
const sf = resolvedFn.getSourceFile();
|
|
365
354
|
|
|
@@ -373,7 +362,7 @@ export function scanSource(
|
|
|
373
362
|
wrapperCache
|
|
374
363
|
);
|
|
375
364
|
|
|
376
|
-
res.events.forEach(
|
|
365
|
+
res.events.forEach(e => events.add(e));
|
|
377
366
|
}
|
|
378
367
|
}
|
|
379
368
|
}
|
package/src/utils/universal.ts
CHANGED
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { ParseResult } from "../types";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/* ---------------------------------- */
|
|
5
|
+
/* COMMENT STRIP (SAFE) */
|
|
6
|
+
/* ---------------------------------- */
|
|
5
7
|
function stripComments(input: string): string {
|
|
6
8
|
return input
|
|
7
9
|
.replace(/<!--[\s\S]*?-->/g, "")
|
|
8
10
|
.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
/* ---------------------------------- */
|
|
14
|
+
/* TRACK DETECTOR */
|
|
15
|
+
/* ---------------------------------- */
|
|
12
16
|
function looksLikeTracking(code: string) {
|
|
13
|
-
return /\btrack\
|
|
17
|
+
return /\btrack\s*\(|\.track\s*\(/.test(code);
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
/* ---------------------------------- */
|
|
21
|
+
/* SAFE {} PARSER (JSX / TEMPLATES) */
|
|
22
|
+
/* ---------------------------------- */
|
|
17
23
|
function extractBracedExpressions(input: string): string[] {
|
|
18
24
|
const result: string[] = [];
|
|
19
25
|
|
|
20
26
|
let depth = 0;
|
|
21
27
|
let start = -1;
|
|
28
|
+
let inString: string | null = null;
|
|
22
29
|
|
|
23
30
|
for (let i = 0; i < input.length; i++) {
|
|
24
31
|
const char = input[i];
|
|
25
32
|
|
|
33
|
+
// handle strings
|
|
34
|
+
if (inString) {
|
|
35
|
+
if (char === inString && input[i - 1] !== "\\") {
|
|
36
|
+
inString = null;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
42
|
+
inString = char;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
if (char === "{") {
|
|
27
47
|
if (depth === 0) start = i + 1;
|
|
28
48
|
depth++;
|
|
@@ -30,9 +50,11 @@ function extractBracedExpressions(input: string): string[] {
|
|
|
30
50
|
depth--;
|
|
31
51
|
if (depth === 0 && start !== -1) {
|
|
32
52
|
const expr = input.slice(start, i);
|
|
53
|
+
|
|
33
54
|
if (looksLikeTracking(expr)) {
|
|
34
55
|
result.push(expr);
|
|
35
56
|
}
|
|
57
|
+
|
|
36
58
|
start = -1;
|
|
37
59
|
}
|
|
38
60
|
}
|
|
@@ -41,7 +63,9 @@ function extractBracedExpressions(input: string): string[] {
|
|
|
41
63
|
return result;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
|
-
|
|
66
|
+
/* ---------------------------------- */
|
|
67
|
+
/* HTML HANDLERS */
|
|
68
|
+
/* ---------------------------------- */
|
|
45
69
|
function extractHTMLHandlers(input: string): string[] {
|
|
46
70
|
const result: string[] = [];
|
|
47
71
|
|
|
@@ -56,7 +80,9 @@ function extractHTMLHandlers(input: string): string[] {
|
|
|
56
80
|
return result;
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
|
|
83
|
+
/* ---------------------------------- */
|
|
84
|
+
/* VUE HANDLERS */
|
|
85
|
+
/* ---------------------------------- */
|
|
60
86
|
function extractVueHandlers(input: string): string[] {
|
|
61
87
|
const result: string[] = [];
|
|
62
88
|
|
|
@@ -71,29 +97,34 @@ function extractVueHandlers(input: string): string[] {
|
|
|
71
97
|
return result;
|
|
72
98
|
}
|
|
73
99
|
|
|
74
|
-
|
|
100
|
+
/* ---------------------------------- */
|
|
101
|
+
/* SVELTE HANDLERS */
|
|
102
|
+
/* ---------------------------------- */
|
|
75
103
|
function extractSvelteHandlers(input: string): string[] {
|
|
76
104
|
const result: string[] = [];
|
|
77
105
|
|
|
78
|
-
const regex = /\bon:[\w]+\s*=\s*{([
|
|
106
|
+
const regex = /\bon:[\w]+\s*=\s*{([\s\S]*?)}/g;
|
|
79
107
|
|
|
80
108
|
for (const m of input.matchAll(regex)) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
109
|
+
const expr = m[1];
|
|
110
|
+
|
|
111
|
+
const nested = extractBracedExpressions(`{${expr}}`);
|
|
112
|
+
result.push(...nested);
|
|
84
113
|
}
|
|
85
114
|
|
|
86
115
|
return result;
|
|
87
116
|
}
|
|
88
117
|
|
|
89
|
-
|
|
118
|
+
/* ---------------------------------- */
|
|
119
|
+
/* SCRIPT EXTRACTION */
|
|
120
|
+
/* ---------------------------------- */
|
|
90
121
|
function extractScripts(input: string, file: string) {
|
|
91
122
|
const scripts: string[] = [];
|
|
92
123
|
const deps: string[] = [];
|
|
93
124
|
|
|
94
|
-
// src
|
|
125
|
+
// <script src="">
|
|
95
126
|
const srcRegex =
|
|
96
|
-
/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/
|
|
127
|
+
/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/gi;
|
|
97
128
|
|
|
98
129
|
for (const m of input.matchAll(srcRegex)) {
|
|
99
130
|
const src = m[1];
|
|
@@ -105,9 +136,9 @@ function extractScripts(input: string, file: string) {
|
|
|
105
136
|
deps.push(resolved);
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
// inline
|
|
139
|
+
// inline <script>
|
|
109
140
|
const scriptRegex =
|
|
110
|
-
/<script\b[^>]*>([\s\S]*?)<\/script>/
|
|
141
|
+
/<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
111
142
|
|
|
112
143
|
for (const m of input.matchAll(scriptRegex)) {
|
|
113
144
|
if (m[1]?.trim()) {
|
|
@@ -120,9 +151,37 @@ function extractScripts(input: string, file: string) {
|
|
|
120
151
|
return { scripts, deps, noScripts };
|
|
121
152
|
}
|
|
122
153
|
|
|
123
|
-
|
|
154
|
+
/* ---------------------------------- */
|
|
155
|
+
/* STATIC IMPORTS */
|
|
156
|
+
/* ---------------------------------- */
|
|
157
|
+
function extractStaticImports(input: string, file: string) {
|
|
158
|
+
const deps: string[] = [];
|
|
159
|
+
|
|
160
|
+
const regex =
|
|
161
|
+
/import\s+(?:.+?\s+from\s+)?["'](.+?)["']/g;
|
|
162
|
+
|
|
163
|
+
for (const m of input.matchAll(regex)) {
|
|
164
|
+
const mod = m[1];
|
|
165
|
+
|
|
166
|
+
if (!mod.startsWith(".") && !mod.startsWith("/")) continue;
|
|
167
|
+
|
|
168
|
+
const resolved = path.resolve(
|
|
169
|
+
path.dirname(file),
|
|
170
|
+
mod
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
deps.push(resolved);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return deps;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* ---------------------------------- */
|
|
180
|
+
/* DYNAMIC IMPORTS */
|
|
181
|
+
/* ---------------------------------- */
|
|
124
182
|
function extractDynamicImports(input: string, file: string) {
|
|
125
183
|
const deps: string[] = [];
|
|
184
|
+
const calls: string[] = [];
|
|
126
185
|
|
|
127
186
|
const regex = /import\(\s*["'](.+?)["']\s*\)/g;
|
|
128
187
|
|
|
@@ -134,12 +193,18 @@ function extractDynamicImports(input: string, file: string) {
|
|
|
134
193
|
: path.resolve(path.dirname(file), mod);
|
|
135
194
|
|
|
136
195
|
deps.push(resolved);
|
|
196
|
+
|
|
197
|
+
if (looksLikeTracking(mod)) {
|
|
198
|
+
calls.push(`import("${mod}")`);
|
|
199
|
+
}
|
|
137
200
|
}
|
|
138
201
|
|
|
139
|
-
return deps;
|
|
202
|
+
return { deps, calls };
|
|
140
203
|
}
|
|
141
204
|
|
|
142
|
-
|
|
205
|
+
/* ---------------------------------- */
|
|
206
|
+
/* MAIN */
|
|
207
|
+
/* ---------------------------------- */
|
|
143
208
|
export function parseUniversal(
|
|
144
209
|
content: string,
|
|
145
210
|
file: string
|
|
@@ -150,7 +215,7 @@ export function parseUniversal(
|
|
|
150
215
|
const clean = stripComments(content);
|
|
151
216
|
|
|
152
217
|
/* ---------- ASTRO FRONTMATTER ---------- */
|
|
153
|
-
const fm = clean.match(
|
|
218
|
+
const fm = clean.match(/^\s*---([\s\S]*?)---/);
|
|
154
219
|
if (fm?.[1]) {
|
|
155
220
|
parts.push(fm[1]);
|
|
156
221
|
}
|
|
@@ -171,7 +236,11 @@ export function parseUniversal(
|
|
|
171
236
|
parts.push(...extractBracedExpressions(clean));
|
|
172
237
|
|
|
173
238
|
/* ---------- IMPORTS ---------- */
|
|
174
|
-
deps.push(...
|
|
239
|
+
deps.push(...extractStaticImports(clean, file));
|
|
240
|
+
|
|
241
|
+
const dyn = extractDynamicImports(clean, file);
|
|
242
|
+
deps.push(...dyn.deps);
|
|
243
|
+
parts.push(...dyn.calls);
|
|
175
244
|
|
|
176
245
|
return {
|
|
177
246
|
code: [...new Set(parts)].join("\n"),
|