@eventra_dev/eventra-cli 0.1.32 → 0.1.34

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,157 @@
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
10
- function getJsxAttrName(name: ts.JsxAttributeName): string {
11
- if (ts.isIdentifier(name)) return name.text;
12
- if (ts.isJsxNamespacedName(name)) return name.name.text;
7
+ function isValidEvent(v: string) {
8
+ return (
9
+ v &&
10
+ v.length > 1 &&
11
+ v.length < 120 &&
12
+ !v.includes(" ") &&
13
+ /^[a-zA-Z0-9._:-]+$/.test(v)
14
+ );
15
+ }
16
+
17
+ function getCallName(expr: ts.Expression): string {
18
+ if (ts.isIdentifier(expr)) return expr.text;
19
+ if (ts.isPropertyAccessExpression(expr)) return expr.name.text;
13
20
  return "";
14
21
  }
15
22
 
16
23
  function getJsxTagName(tag: ts.JsxTagNameExpression): string {
17
24
  if (ts.isIdentifier(tag)) return tag.text;
18
-
19
- if (ts.isPropertyAccessExpression(tag)) {
20
- return tag.name.text;
21
- }
22
-
23
- return "";
25
+ if (ts.isPropertyAccessExpression(tag)) return tag.name.text;
26
+ return tag.getText();
24
27
  }
25
28
 
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
+ function getJsxAttrName(name: ts.JsxAttributeName): string {
30
+ if (ts.isIdentifier(name)) return name.text;
31
+ if (ts.isJsxNamespacedName(name)) return name.name.text;
29
32
  return "";
30
33
  }
31
34
 
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
- }
35
+ export function scanSource(
36
+ source: ts.SourceFile,
37
+ checker: ts.TypeChecker,
38
+ config: EventraConfig,
39
+ visited = new Set<string>(),
40
+ inheritedParamMap?: Map<string, ts.Expression>,
41
+ wrapperCache = new Map<string, string | null>()
42
+ ): ScanResult {
43
+ const events = new Set<string>();
44
+ const detectedFunctionWrappers = new Set<string>();
45
+ const detectedComponentWrappers = new Map<string, string>();
41
46
 
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;
47
+ if (visited.has(source.fileName)) {
48
+ return { events, detectedFunctionWrappers, detectedComponentWrappers };
49
+ }
50
+ visited.add(source.fileName);
49
51
 
50
- // ({ event }) => ...
51
- if (ts.isObjectBindingPattern(firstParam.name)) {
52
- for (const el of firstParam.name.elements) {
53
- if (!ts.isBindingElement(el)) continue;
52
+ function extractTrackCall(node: ts.CallExpression, paramMap: Map<string, ts.Expression>) {
53
+ const expr = node.expression;
54
54
 
55
- const propName =
56
- el.propertyName?.getText() || el.name.getText();
55
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === "track") {
56
+ const arg = node.arguments[0];
57
+ if (!arg) return;
57
58
 
58
- map.set(el.name.getText(), propName);
59
+ const res = resolveNodeValue(arg, checker, paramMap);
60
+ for (const v of res.values) {
61
+ if (isValidEvent(v)) events.add(v);
62
+ }
59
63
  }
60
64
  }
61
65
 
62
- // (props) => props.event
63
- if (ts.isIdentifier(firstParam.name)) {
64
- map.set(firstParam.name.text, "*");
66
+ function isWrapperFunction(fn: ts.FunctionLikeDeclaration) {
67
+ let found = false;
68
+
69
+ function walk(n: ts.Node) {
70
+ if (found) return;
71
+
72
+ if (ts.isCallExpression(n)) {
73
+ const expr = n.expression;
74
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === "track") {
75
+ found = true;
76
+ return;
77
+ }
78
+ }
79
+ ts.forEachChild(n, walk);
80
+ }
81
+
82
+ if (fn.body) walk(fn.body);
83
+ return found;
65
84
  }
66
85
 
67
- return map;
68
- }
86
+ function detectComponentWrapper(tag: ts.JsxTagNameExpression): string | null {
87
+ const tagName = getJsxTagName(tag);
69
88
 
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;
89
+ if (wrapperCache.has(tagName)) return wrapperCache.get(tagName)!;
76
90
 
77
- function resolveIdentifierToProp(
78
- node: ts.Identifier
79
- ): string | null {
80
- const mapped = propMap.get(node.text);
81
- if (mapped && mapped !== "*") return mapped;
91
+ let symbol = checker.getSymbolAtLocation(tag);
92
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
93
+ symbol = checker.getAliasedSymbol(symbol);
94
+ }
95
+ if (!symbol) {
96
+ wrapperCache.set(tagName, null);
97
+ return null;
98
+ }
82
99
 
83
- const symbol = checker.getSymbolAtLocation(node);
84
- if (!symbol) return null;
100
+ symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
85
101
 
86
102
  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
- }
103
+ let fn: ts.FunctionLikeDeclaration | null = null;
104
+
105
+ if (ts.isFunctionDeclaration(decl)) fn = decl;
95
106
 
96
- // const e = event
97
107
  if (
98
108
  ts.isVariableDeclaration(decl) &&
99
109
  decl.initializer &&
100
- ts.isIdentifier(decl.initializer)
110
+ (ts.isArrowFunction(decl.initializer) ||
111
+ ts.isFunctionExpression(decl.initializer))
101
112
  ) {
102
- const inner = propMap.get(decl.initializer.text);
103
- if (inner) return inner;
113
+ fn = decl.initializer;
104
114
  }
105
- }
106
115
 
107
- return null;
108
- }
116
+ if (!fn || !fn.body) continue;
109
117
 
110
- function visit(node: ts.Node) {
111
- if (result) return;
112
-
113
- if (ts.isCallExpression(node)) {
114
- const expr = node.expression;
115
-
116
- const isTrack =
117
- (ts.isIdentifier(expr) && expr.text === "track") ||
118
- (ts.isPropertyAccessExpression(expr) &&
119
- expr.name.text === "track");
120
-
121
- if (!isTrack) {
122
- ts.forEachChild(node, visit);
123
- return;
124
- }
118
+ let prop: string | null = null;
125
119
 
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;
132
- return;
133
- }
134
- }
120
+ function walk(n: ts.Node) {
121
+ if (prop) return;
135
122
 
136
- // track(props.a.b.event)
137
- if (ts.isPropertyAccessExpression(arg)) {
138
- let current = arg;
123
+ if (ts.isCallExpression(n)) {
124
+ const expr = n.expression;
139
125
 
140
- while (ts.isPropertyAccessExpression(current)) {
141
- result = current.name.text;
142
- return;
126
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === "track") {
127
+ const arg = n.arguments[0];
128
+ if (ts.isPropertyAccessExpression(arg)) {
129
+ prop = arg.name.text;
130
+ return;
131
+ }
143
132
  }
144
133
  }
134
+ ts.forEachChild(n, walk);
135
+ }
145
136
 
146
- // track(fn())
147
- if (ts.isCallExpression(arg)) {
148
- const inner = resolveFunctionFromCall(
149
- arg.expression,
150
- checker
151
- );
137
+ walk(fn.body);
138
+ wrapperCache.set(tagName, prop);
152
139
 
153
- if (inner?.body) {
154
- visit(inner.body);
155
- if (result) return;
156
- }
157
- }
140
+ if (prop) {
141
+ detectedComponentWrappers.set(tagName, prop);
142
+ return prop;
158
143
  }
159
144
  }
160
145
 
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 };
146
+ wrapperCache.set(tagName, null);
147
+ return null;
184
148
  }
185
149
 
186
- visited.add(source.fileName);
187
-
188
150
  function visit(node: ts.Node) {
189
- // JSX WRAPPERS
151
+ // JSX
190
152
  if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
191
153
  const tagName = getJsxTagName(node.tagName);
192
154
 
193
- // skip html
194
155
  if (tagName[0] === tagName[0].toLowerCase()) {
195
156
  ts.forEachChild(node, visit);
196
157
  return;
@@ -198,48 +159,9 @@ export function scanSource(
198
159
 
199
160
  let wrapper = config.wrappers.find(w => w.name === tagName);
200
161
 
201
- // AUTO DETECT
202
162
  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
- }
163
+ const prop = detectComponentWrapper(node.tagName);
164
+ if (prop) wrapper = { name: tagName, prop };
243
165
  }
244
166
 
245
167
  if (wrapper) {
@@ -252,16 +174,16 @@ export function scanSource(
252
174
  if (!attr.initializer) continue;
253
175
 
254
176
  if (ts.isStringLiteral(attr.initializer)) {
255
- const v = attr.initializer.text;
256
- if (isValidEvent(v)) events.add(v);
177
+ if (isValidEvent(attr.initializer.text)) {
178
+ events.add(attr.initializer.text);
179
+ }
257
180
  }
258
181
 
259
182
  if (ts.isJsxExpression(attr.initializer)) {
260
183
  const expr = attr.initializer.expression;
261
184
  if (!expr) continue;
262
185
 
263
- const res = resolveNodeValue(expr, checker, inheritedParamMap);
264
-
186
+ const res = resolveNodeValue(expr, checker);
265
187
  res.values.forEach(v => {
266
188
  if (isValidEvent(v)) events.add(v);
267
189
  });
@@ -273,98 +195,42 @@ export function scanSource(
273
195
  return;
274
196
  }
275
197
 
276
- // FUNCTION CALLS
198
+ // CALL
277
199
  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
- }
200
+ const resolvedFn = resolveFunctionFromCall(node.expression, checker);
325
201
 
326
202
  const paramMap = new Map(inheritedParamMap ?? []);
327
203
 
328
- if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
329
- resolvedFn.parameters.forEach((param, i) => {
204
+ if (resolvedFn) {
205
+ resolvedFn.parameters.forEach((p, i) => {
330
206
  const arg = node.arguments[i];
331
207
  if (!arg) return;
332
-
333
- if (ts.isIdentifier(param.name)) {
334
- paramMap.set(param.name.text, arg);
208
+ if (ts.isIdentifier(p.name)) {
209
+ paramMap.set(p.name.text, arg);
335
210
  }
336
211
  });
337
212
  }
338
213
 
339
- if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
340
- const res = resolveNodeValue(
341
- node.arguments[trackParamIndex],
342
- checker,
343
- paramMap
344
- );
214
+ // direct track
215
+ extractTrackCall(node, paramMap);
345
216
 
346
- res.values.forEach(v => {
347
- if (isValidEvent(v)) events.add(v);
348
- });
217
+ // wrapper
218
+ if (resolvedFn && isWrapperFunction(resolvedFn)) {
219
+ const name = getCallName(node.expression);
220
+ if (name && name !== "track") {
221
+ detectedFunctionWrappers.add(name);
222
+ }
223
+ if (resolvedFn.body) {
224
+ visit(resolvedFn.body);
225
+ }
349
226
  }
350
227
 
351
- // CROSS FILE
352
- if (resolvedFn) {
353
- const sf = resolvedFn.getSourceFile();
354
-
355
- if (!visited.has(sf.fileName)) {
356
- const res = scanSource(
357
- sf,
358
- checker,
359
- config,
360
- visited,
361
- paramMap,
362
- wrapperCache
363
- );
364
-
365
- res.events.forEach(e => events.add(e));
366
- }
228
+ for (const arg of node.arguments) {
229
+ visit(arg);
367
230
  }
231
+ visit(node.expression);
232
+
233
+ return;
368
234
  }
369
235
 
370
236
  ts.forEachChild(node, visit);