@eventra_dev/eventra-cli 0.1.33 → 0.2.1

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.
@@ -1,196 +1,181 @@
1
1
  import ts from "typescript";
2
2
  import { resolveNodeValue } from "./resolver";
3
3
  import { resolveFunctionFromCall } from "./callResolver";
4
- import { findTrackParamIndex } from "./functionAnalyzer";
5
4
  import { resolveExportedSymbol } from "./exportResolver";
6
5
  import { EventraConfig, ScanResult } from "../types";
7
- import { isExternalFile } from "./boundary";
8
6
 
9
- // UTILS
7
+ // HELPERS
8
+ function isValidEvent(v: string) {
9
+ return (
10
+ v &&
11
+ v.length > 1 &&
12
+ v.length < 120 &&
13
+ !v.includes(" ") &&
14
+ /^[a-zA-Z0-9._:-]+$/.test(v)
15
+ );
16
+ }
17
+
18
+ function getCallName(expr: ts.Expression): string {
19
+ if (ts.isIdentifier(expr)) return expr.text;
20
+ if (ts.isPropertyAccessExpression(expr)) return expr.name.text;
21
+ return "";
22
+ }
23
+
24
+ function getJsxTagName(tag: ts.JsxTagNameExpression): string {
25
+ if (ts.isIdentifier(tag)) return tag.text;
26
+ if (ts.isPropertyAccessExpression(tag)) return tag.name.text;
27
+ return tag.getText();
28
+ }
29
+
10
30
  function getJsxAttrName(name: ts.JsxAttributeName): string {
11
31
  if (ts.isIdentifier(name)) return name.text;
12
32
  if (ts.isJsxNamespacedName(name)) return name.name.text;
13
33
  return "";
14
34
  }
15
35
 
16
- function getJsxTagName(tag: ts.JsxTagNameExpression): string {
17
- if (ts.isIdentifier(tag)) return tag.text;
36
+ // MAIN
37
+ export function scanSource(
38
+ source: ts.SourceFile,
39
+ checker: ts.TypeChecker,
40
+ config: EventraConfig,
41
+ visited = new Set<string>(),
42
+ inheritedParamMap?: Map<string, ts.Expression>,
43
+ wrapperCache = new Map<string, string | null>()
44
+ ): ScanResult {
45
+ const events = new Set<string>();
46
+ const detectedFunctionWrappers = new Set<string>();
47
+ const detectedComponentWrappers = new Map<string, string>();
18
48
 
19
- if (ts.isPropertyAccessExpression(tag)) {
20
- return tag.name.text;
49
+ if (visited.has(source.fileName)) {
50
+ return { events, detectedFunctionWrappers, detectedComponentWrappers };
21
51
  }
22
52
 
23
- return "";
24
- }
53
+ visited.add(source.fileName);
25
54
 
26
- function getCallName(expr: ts.Expression): string {
27
- if (ts.isIdentifier(expr)) return expr.text;
28
- if (ts.isPropertyAccessExpression(expr)) return expr.name.text;
29
- return "";
30
- }
55
+ // TRACK DETECTION
56
+ function isTrackCall(expr: ts.Expression) {
57
+ if (ts.isIdentifier(expr)) {
58
+ return expr.text === "track";
59
+ }
31
60
 
32
- function isValidEvent(v: string) {
33
- return (
34
- v &&
35
- v.length > 1 &&
36
- v.length < 100 &&
37
- !v.includes(" ") &&
38
- /^[a-zA-Z0-9._:-]+$/.test(v)
39
- );
40
- }
61
+ if (ts.isPropertyAccessExpression(expr)) {
62
+ return expr.name.text === "track";
63
+ }
41
64
 
42
- // COMPONENT ANALYSIS
43
- function getComponentPropsMap(
44
- fn: ts.FunctionLikeDeclaration
45
- ): Map<string, string> {
46
- const map = new Map<string, string>();
47
- const firstParam = fn.parameters[0];
48
- if (!firstParam) return map;
65
+ return false;
66
+ }
49
67
 
50
- // ({ event }) => ...
51
- if (ts.isObjectBindingPattern(firstParam.name)) {
52
- for (const el of firstParam.name.elements) {
53
- if (!ts.isBindingElement(el)) continue;
68
+ function extractTrackCall(
69
+ node: ts.CallExpression,
70
+ paramMap: Map<string, ts.Expression>
71
+ ) {
72
+ if (!isTrackCall(node.expression)) return;
54
73
 
55
- const propName =
56
- el.propertyName?.getText() || el.name.getText();
74
+ const arg = node.arguments[0];
75
+ if (!arg) return;
57
76
 
58
- map.set(el.name.getText(), propName);
77
+ const res = resolveNodeValue(arg, checker, paramMap);
78
+
79
+ for (const v of res.values) {
80
+ if (isValidEvent(v)) {
81
+ events.add(v);
82
+ }
59
83
  }
60
84
  }
61
85
 
62
- // (props) => props.event
63
- if (ts.isIdentifier(firstParam.name)) {
64
- map.set(firstParam.name.text, "*");
86
+ // WRAPPER DETECTION
87
+ function isWrapperFunction(fn: ts.FunctionLikeDeclaration) {
88
+ let found = false;
89
+
90
+ function walk(n: ts.Node) {
91
+ if (found) return;
92
+
93
+ if (ts.isCallExpression(n) && isTrackCall(n.expression)) {
94
+ found = true;
95
+ return;
96
+ }
97
+
98
+ ts.forEachChild(n, walk);
99
+ }
100
+
101
+ if (fn.body) walk(fn.body);
102
+ return found;
65
103
  }
66
104
 
67
- return map;
68
- }
105
+ // COMPONENT WRAPPER
106
+ function detectComponentWrapper(tag: ts.JsxTagNameExpression): string | null {
107
+ const tagName = getJsxTagName(tag);
108
+
109
+ if (wrapperCache.has(tagName)) {
110
+ return wrapperCache.get(tagName)!;
111
+ }
69
112
 
70
- function findTrackedPropName(
71
- fn: ts.FunctionLikeDeclaration,
72
- checker: ts.TypeChecker
73
- ): string | null {
74
- const propMap = getComponentPropsMap(fn);
75
- let result: string | null = null;
113
+ let symbol = checker.getSymbolAtLocation(tag);
76
114
 
77
- function resolveIdentifierToProp(
78
- node: ts.Identifier
79
- ): string | null {
80
- const mapped = propMap.get(node.text);
81
- if (mapped && mapped !== "*") return mapped;
115
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
116
+ symbol = checker.getAliasedSymbol(symbol);
117
+ }
82
118
 
83
- const symbol = checker.getSymbolAtLocation(node);
84
- if (!symbol) return null;
119
+ if (!symbol) {
120
+ wrapperCache.set(tagName, null);
121
+ return null;
122
+ }
123
+
124
+ symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
85
125
 
86
126
  for (const decl of symbol.getDeclarations() ?? []) {
87
- // const e = props.event
88
- if (
89
- ts.isVariableDeclaration(decl) &&
90
- decl.initializer &&
91
- ts.isPropertyAccessExpression(decl.initializer)
92
- ) {
93
- return decl.initializer.name.text;
94
- }
127
+ let fn: ts.FunctionLikeDeclaration | null = null;
128
+
129
+ if (ts.isFunctionDeclaration(decl)) fn = decl;
95
130
 
96
- // const e = event
97
131
  if (
98
132
  ts.isVariableDeclaration(decl) &&
99
133
  decl.initializer &&
100
- ts.isIdentifier(decl.initializer)
134
+ (ts.isArrowFunction(decl.initializer) ||
135
+ ts.isFunctionExpression(decl.initializer))
101
136
  ) {
102
- const inner = propMap.get(decl.initializer.text);
103
- if (inner) return inner;
137
+ fn = decl.initializer;
104
138
  }
105
- }
106
-
107
- return null;
108
- }
109
139
 
110
- function visit(node: ts.Node) {
111
- if (result) return;
140
+ if (!fn || !fn.body) continue;
112
141
 
113
- if (ts.isCallExpression(node)) {
114
- const expr = node.expression;
142
+ let prop: string | null = null;
115
143
 
116
- const isTrack =
117
- (ts.isIdentifier(expr) && expr.text === "track") ||
118
- (ts.isPropertyAccessExpression(expr) &&
119
- expr.name.text === "track");
144
+ function walk(n: ts.Node) {
145
+ if (prop) return;
120
146
 
121
- if (!isTrack) {
122
- ts.forEachChild(node, visit);
123
- return;
124
- }
147
+ if (ts.isCallExpression(n) && isTrackCall(n.expression)) {
148
+ const arg = n.arguments[0];
125
149
 
126
- for (const arg of node.arguments) {
127
- // track(event)
128
- if (ts.isIdentifier(arg)) {
129
- const prop = resolveIdentifierToProp(arg);
130
- if (prop) {
131
- result = prop;
150
+ if (ts.isPropertyAccessExpression(arg)) {
151
+ prop = arg.name.text;
132
152
  return;
133
153
  }
134
154
  }
135
155
 
136
- // track(props.a.b.event)
137
- if (ts.isPropertyAccessExpression(arg)) {
138
- let current = arg;
156
+ ts.forEachChild(n, walk);
157
+ }
139
158
 
140
- while (ts.isPropertyAccessExpression(current)) {
141
- result = current.name.text;
142
- return;
143
- }
144
- }
159
+ walk(fn.body);
145
160
 
146
- // track(fn())
147
- if (ts.isCallExpression(arg)) {
148
- const inner = resolveFunctionFromCall(
149
- arg.expression,
150
- checker
151
- );
161
+ wrapperCache.set(tagName, prop);
152
162
 
153
- if (inner?.body) {
154
- visit(inner.body);
155
- if (result) return;
156
- }
157
- }
163
+ if (prop) {
164
+ detectedComponentWrappers.set(tagName, prop);
165
+ return prop;
158
166
  }
159
167
  }
160
168
 
161
- ts.forEachChild(node, visit);
162
- }
163
-
164
- if (fn.body) visit(fn.body);
165
-
166
- return result;
167
- }
168
-
169
- // MAIN SCANNER
170
- export function scanSource(
171
- source: ts.SourceFile,
172
- checker: ts.TypeChecker,
173
- config: EventraConfig,
174
- visited = new Set<string>(),
175
- inheritedParamMap?: Map<string, ts.Expression>,
176
- wrapperCache = new Map<string, string | null>()
177
- ): ScanResult {
178
- const events = new Set<string>();
179
- const detectedFunctionWrappers = new Set<string>();
180
- const detectedComponentWrappers = new Map<string, string>();
181
-
182
- if (visited.has(source.fileName) || visited.size > 200) {
183
- return { events, detectedFunctionWrappers, detectedComponentWrappers };
169
+ wrapperCache.set(tagName, null);
170
+ return null;
184
171
  }
185
172
 
186
- visited.add(source.fileName);
187
-
173
+ // VISITOR
188
174
  function visit(node: ts.Node) {
189
- // JSX WRAPPERS
175
+ // JSX
190
176
  if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
191
177
  const tagName = getJsxTagName(node.tagName);
192
178
 
193
- // skip html
194
179
  if (tagName[0] === tagName[0].toLowerCase()) {
195
180
  ts.forEachChild(node, visit);
196
181
  return;
@@ -198,48 +183,9 @@ export function scanSource(
198
183
 
199
184
  let wrapper = config.wrappers.find(w => w.name === tagName);
200
185
 
201
- // AUTO DETECT
202
186
  if (!wrapper) {
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);
208
-
209
- if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
210
- symbol = checker.getAliasedSymbol(symbol);
211
- }
212
-
213
- if (symbol) {
214
- symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
215
-
216
- for (const decl of symbol.getDeclarations() ?? []) {
217
- let fn: ts.FunctionLikeDeclaration | null = null;
218
-
219
- if (ts.isFunctionDeclaration(decl)) fn = decl;
220
-
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
- }
229
-
230
- if (!fn) continue;
231
-
232
- const prop = findTrackedPropName(fn, checker);
233
- wrapperCache.set(tagName, prop ?? null);
234
-
235
- if (prop) {
236
- wrapper = { name: tagName, prop };
237
- detectedComponentWrappers.set(tagName, prop);
238
- break;
239
- }
240
- }
241
- }
242
- }
187
+ const prop = detectComponentWrapper(node.tagName);
188
+ if (prop) wrapper = { name: tagName, prop };
243
189
  }
244
190
 
245
191
  if (wrapper) {
@@ -252,16 +198,16 @@ export function scanSource(
252
198
  if (!attr.initializer) continue;
253
199
 
254
200
  if (ts.isStringLiteral(attr.initializer)) {
255
- const v = attr.initializer.text;
256
- if (isValidEvent(v)) events.add(v);
201
+ if (isValidEvent(attr.initializer.text)) {
202
+ events.add(attr.initializer.text);
203
+ }
257
204
  }
258
205
 
259
206
  if (ts.isJsxExpression(attr.initializer)) {
260
207
  const expr = attr.initializer.expression;
261
208
  if (!expr) continue;
262
209
 
263
- const res = resolveNodeValue(expr, checker, inheritedParamMap);
264
-
210
+ const res = resolveNodeValue(expr, checker);
265
211
  res.values.forEach(v => {
266
212
  if (isValidEvent(v)) events.add(v);
267
213
  });
@@ -273,83 +219,38 @@ export function scanSource(
273
219
  return;
274
220
  }
275
221
 
276
- // FUNCTION CALLS
222
+ // CALL
277
223
  if (ts.isCallExpression(node)) {
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
- }
291
-
292
- // config wrapper
293
- const manualWrapper = config.functionWrappers.find(
294
- w => w.name === callName
295
- );
296
-
297
- if (manualWrapper) {
298
- isTracking = true;
299
- trackParamIndex = 0;
300
- }
301
-
302
- // resolve function → check body
303
- const resolvedFn = resolveFunctionFromCall(
304
- node.expression,
305
- checker
306
- );
307
-
308
- if (resolvedFn) {
309
- const idx = findTrackParamIndex(resolvedFn);
310
-
311
- if (idx !== null) {
312
- isTracking = true;
313
- trackParamIndex = idx;
314
-
315
- if (!manualWrapper && callName !== "track") {
316
- detectedFunctionWrappers.add(callName);
317
- }
318
- }
319
- }
320
-
321
- if (!isTracking) {
322
- ts.forEachChild(node, visit);
323
- return;
324
- }
224
+ const resolvedFn = resolveFunctionFromCall(node.expression, checker);
325
225
 
326
226
  const paramMap = new Map(inheritedParamMap ?? []);
327
227
 
328
- if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
329
- resolvedFn.parameters.forEach((param, i) => {
228
+ if (resolvedFn) {
229
+ resolvedFn.parameters.forEach((p, i) => {
330
230
  const arg = node.arguments[i];
331
231
  if (!arg) return;
332
232
 
333
- if (ts.isIdentifier(param.name)) {
334
- paramMap.set(param.name.text, arg);
233
+ if (ts.isIdentifier(p.name)) {
234
+ paramMap.set(p.name.text, arg);
335
235
  }
336
236
  });
337
237
  }
338
238
 
339
- if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
340
- const res = resolveNodeValue(
341
- node.arguments[trackParamIndex],
342
- checker,
343
- paramMap
344
- );
239
+ extractTrackCall(node, paramMap);
345
240
 
346
- res.values.forEach(v => {
347
- if (isValidEvent(v)) events.add(v);
348
- });
241
+ if (resolvedFn && isWrapperFunction(resolvedFn)) {
242
+ const name = getCallName(node.expression);
243
+
244
+ if (name && name !== "track") {
245
+ detectedFunctionWrappers.add(name);
246
+ }
247
+
248
+ if (resolvedFn.body) {
249
+ visit(resolvedFn.body);
250
+ }
349
251
  }
350
252
 
351
- // CROSS FILE
352
- if (resolvedFn) {
253
+ if (resolvedFn && resolvedFn.body) {
353
254
  const sf = resolvedFn.getSourceFile();
354
255
 
355
256
  if (!visited.has(sf.fileName)) {
@@ -365,6 +266,11 @@ export function scanSource(
365
266
  res.events.forEach(e => events.add(e));
366
267
  }
367
268
  }
269
+
270
+ node.arguments.forEach(visit);
271
+ visit(node.expression);
272
+
273
+ return;
368
274
  }
369
275
 
370
276
  ts.forEachChild(node, visit);
@@ -243,7 +243,7 @@ export function parseUniversal(
243
243
  parts.push(...dyn.calls);
244
244
 
245
245
  return {
246
- code: [...new Set(parts)].join("\n"),
246
+ code: content,
247
247
  deps: [...new Set(deps.filter(Boolean))],
248
248
  };
249
249
  }