@eventra_dev/eventra-cli 0.1.33 → 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.
@@ -7,23 +7,13 @@ exports.scanSource = scanSource;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
8
  const resolver_1 = require("./resolver");
9
9
  const callResolver_1 = require("./callResolver");
10
- const functionAnalyzer_1 = require("./functionAnalyzer");
11
10
  const exportResolver_1 = require("./exportResolver");
12
- // UTILS
13
- function getJsxAttrName(name) {
14
- if (typescript_1.default.isIdentifier(name))
15
- return name.text;
16
- if (typescript_1.default.isJsxNamespacedName(name))
17
- return name.name.text;
18
- return "";
19
- }
20
- function getJsxTagName(tag) {
21
- if (typescript_1.default.isIdentifier(tag))
22
- return tag.text;
23
- if (typescript_1.default.isPropertyAccessExpression(tag)) {
24
- return tag.name.text;
25
- }
26
- return "";
11
+ function isValidEvent(v) {
12
+ return (v &&
13
+ v.length > 1 &&
14
+ v.length < 120 &&
15
+ !v.includes(" ") &&
16
+ /^[a-zA-Z0-9._:-]+$/.test(v));
27
17
  }
28
18
  function getCallName(expr) {
29
19
  if (typescript_1.default.isIdentifier(expr))
@@ -32,163 +22,123 @@ function getCallName(expr) {
32
22
  return expr.name.text;
33
23
  return "";
34
24
  }
35
- function isValidEvent(v) {
36
- return (v &&
37
- v.length > 1 &&
38
- v.length < 100 &&
39
- !v.includes(" ") &&
40
- /^[a-zA-Z0-9._:-]+$/.test(v));
25
+ function getJsxTagName(tag) {
26
+ if (typescript_1.default.isIdentifier(tag))
27
+ return tag.text;
28
+ if (typescript_1.default.isPropertyAccessExpression(tag))
29
+ return tag.name.text;
30
+ return tag.getText();
41
31
  }
42
- // COMPONENT ANALYSIS
43
- function getComponentPropsMap(fn) {
44
- const map = new Map();
45
- const firstParam = fn.parameters[0];
46
- if (!firstParam)
47
- return map;
48
- // ({ event }) => ...
49
- if (typescript_1.default.isObjectBindingPattern(firstParam.name)) {
50
- for (const el of firstParam.name.elements) {
51
- if (!typescript_1.default.isBindingElement(el))
52
- continue;
53
- const propName = el.propertyName?.getText() || el.name.getText();
54
- map.set(el.name.getText(), propName);
32
+ function getJsxAttrName(name) {
33
+ if (typescript_1.default.isIdentifier(name))
34
+ return name.text;
35
+ if (typescript_1.default.isJsxNamespacedName(name))
36
+ return name.name.text;
37
+ return "";
38
+ }
39
+ function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
40
+ const events = new Set();
41
+ const detectedFunctionWrappers = new Set();
42
+ const detectedComponentWrappers = new Map();
43
+ if (visited.has(source.fileName)) {
44
+ return { events, detectedFunctionWrappers, detectedComponentWrappers };
45
+ }
46
+ visited.add(source.fileName);
47
+ function extractTrackCall(node, paramMap) {
48
+ const expr = node.expression;
49
+ if (typescript_1.default.isPropertyAccessExpression(expr) && expr.name.text === "track") {
50
+ const arg = node.arguments[0];
51
+ if (!arg)
52
+ return;
53
+ const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
54
+ for (const v of res.values) {
55
+ if (isValidEvent(v))
56
+ events.add(v);
57
+ }
55
58
  }
56
59
  }
57
- // (props) => props.event
58
- if (typescript_1.default.isIdentifier(firstParam.name)) {
59
- map.set(firstParam.name.text, "*");
60
+ function isWrapperFunction(fn) {
61
+ let found = false;
62
+ function walk(n) {
63
+ if (found)
64
+ return;
65
+ if (typescript_1.default.isCallExpression(n)) {
66
+ const expr = n.expression;
67
+ if (typescript_1.default.isPropertyAccessExpression(expr) && expr.name.text === "track") {
68
+ found = true;
69
+ return;
70
+ }
71
+ }
72
+ typescript_1.default.forEachChild(n, walk);
73
+ }
74
+ if (fn.body)
75
+ walk(fn.body);
76
+ return found;
60
77
  }
61
- return map;
62
- }
63
- function findTrackedPropName(fn, checker) {
64
- const propMap = getComponentPropsMap(fn);
65
- let result = null;
66
- function resolveIdentifierToProp(node) {
67
- const mapped = propMap.get(node.text);
68
- if (mapped && mapped !== "*")
69
- return mapped;
70
- const symbol = checker.getSymbolAtLocation(node);
71
- if (!symbol)
78
+ function detectComponentWrapper(tag) {
79
+ const tagName = getJsxTagName(tag);
80
+ if (wrapperCache.has(tagName))
81
+ return wrapperCache.get(tagName);
82
+ let symbol = checker.getSymbolAtLocation(tag);
83
+ if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
84
+ symbol = checker.getAliasedSymbol(symbol);
85
+ }
86
+ if (!symbol) {
87
+ wrapperCache.set(tagName, null);
72
88
  return null;
89
+ }
90
+ symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
73
91
  for (const decl of symbol.getDeclarations() ?? []) {
74
- // const e = props.event
75
- if (typescript_1.default.isVariableDeclaration(decl) &&
76
- decl.initializer &&
77
- typescript_1.default.isPropertyAccessExpression(decl.initializer)) {
78
- return decl.initializer.name.text;
79
- }
80
- // const e = event
92
+ let fn = null;
93
+ if (typescript_1.default.isFunctionDeclaration(decl))
94
+ fn = decl;
81
95
  if (typescript_1.default.isVariableDeclaration(decl) &&
82
96
  decl.initializer &&
83
- typescript_1.default.isIdentifier(decl.initializer)) {
84
- const inner = propMap.get(decl.initializer.text);
85
- if (inner)
86
- return inner;
87
- }
88
- }
89
- return null;
90
- }
91
- function visit(node) {
92
- if (result)
93
- return;
94
- if (typescript_1.default.isCallExpression(node)) {
95
- const expr = node.expression;
96
- const isTrack = (typescript_1.default.isIdentifier(expr) && expr.text === "track") ||
97
- (typescript_1.default.isPropertyAccessExpression(expr) &&
98
- expr.name.text === "track");
99
- if (!isTrack) {
100
- typescript_1.default.forEachChild(node, visit);
101
- return;
97
+ (typescript_1.default.isArrowFunction(decl.initializer) ||
98
+ typescript_1.default.isFunctionExpression(decl.initializer))) {
99
+ fn = decl.initializer;
102
100
  }
103
- for (const arg of node.arguments) {
104
- // track(event)
105
- if (typescript_1.default.isIdentifier(arg)) {
106
- const prop = resolveIdentifierToProp(arg);
107
- if (prop) {
108
- result = prop;
109
- return;
110
- }
111
- }
112
- // track(props.a.b.event)
113
- if (typescript_1.default.isPropertyAccessExpression(arg)) {
114
- let current = arg;
115
- while (typescript_1.default.isPropertyAccessExpression(current)) {
116
- result = current.name.text;
117
- return;
118
- }
119
- }
120
- // track(fn())
121
- if (typescript_1.default.isCallExpression(arg)) {
122
- const inner = (0, callResolver_1.resolveFunctionFromCall)(arg.expression, checker);
123
- if (inner?.body) {
124
- visit(inner.body);
125
- if (result)
101
+ if (!fn || !fn.body)
102
+ continue;
103
+ let prop = null;
104
+ function walk(n) {
105
+ if (prop)
106
+ return;
107
+ if (typescript_1.default.isCallExpression(n)) {
108
+ const expr = n.expression;
109
+ if (typescript_1.default.isPropertyAccessExpression(expr) && expr.name.text === "track") {
110
+ const arg = n.arguments[0];
111
+ if (typescript_1.default.isPropertyAccessExpression(arg)) {
112
+ prop = arg.name.text;
126
113
  return;
114
+ }
127
115
  }
128
116
  }
117
+ typescript_1.default.forEachChild(n, walk);
118
+ }
119
+ walk(fn.body);
120
+ wrapperCache.set(tagName, prop);
121
+ if (prop) {
122
+ detectedComponentWrappers.set(tagName, prop);
123
+ return prop;
129
124
  }
130
125
  }
131
- typescript_1.default.forEachChild(node, visit);
132
- }
133
- if (fn.body)
134
- visit(fn.body);
135
- return result;
136
- }
137
- // MAIN SCANNER
138
- function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
139
- const events = new Set();
140
- const detectedFunctionWrappers = new Set();
141
- const detectedComponentWrappers = new Map();
142
- if (visited.has(source.fileName) || visited.size > 200) {
143
- return { events, detectedFunctionWrappers, detectedComponentWrappers };
126
+ wrapperCache.set(tagName, null);
127
+ return null;
144
128
  }
145
- visited.add(source.fileName);
146
129
  function visit(node) {
147
- // JSX WRAPPERS
130
+ // JSX
148
131
  if (typescript_1.default.isJsxSelfClosingElement(node) || typescript_1.default.isJsxOpeningElement(node)) {
149
132
  const tagName = getJsxTagName(node.tagName);
150
- // skip html
151
133
  if (tagName[0] === tagName[0].toLowerCase()) {
152
134
  typescript_1.default.forEachChild(node, visit);
153
135
  return;
154
136
  }
155
137
  let wrapper = config.wrappers.find(w => w.name === tagName);
156
- // AUTO DETECT
157
138
  if (!wrapper) {
158
- if (wrapperCache.has(tagName)) {
159
- const cached = wrapperCache.get(tagName);
160
- if (cached)
161
- wrapper = { name: tagName, prop: cached };
162
- }
163
- else {
164
- let symbol = checker.getSymbolAtLocation(node.tagName);
165
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
166
- symbol = checker.getAliasedSymbol(symbol);
167
- }
168
- if (symbol) {
169
- symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
170
- for (const decl of symbol.getDeclarations() ?? []) {
171
- let fn = null;
172
- if (typescript_1.default.isFunctionDeclaration(decl))
173
- fn = decl;
174
- if (typescript_1.default.isVariableDeclaration(decl) &&
175
- decl.initializer &&
176
- (typescript_1.default.isArrowFunction(decl.initializer) ||
177
- typescript_1.default.isFunctionExpression(decl.initializer))) {
178
- fn = decl.initializer;
179
- }
180
- if (!fn)
181
- continue;
182
- const prop = findTrackedPropName(fn, checker);
183
- wrapperCache.set(tagName, prop ?? null);
184
- if (prop) {
185
- wrapper = { name: tagName, prop };
186
- detectedComponentWrappers.set(tagName, prop);
187
- break;
188
- }
189
- }
190
- }
191
- }
139
+ const prop = detectComponentWrapper(node.tagName);
140
+ if (prop)
141
+ wrapper = { name: tagName, prop };
192
142
  }
193
143
  if (wrapper) {
194
144
  for (const attr of node.attributes.properties) {
@@ -200,15 +150,15 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
200
150
  if (!attr.initializer)
201
151
  continue;
202
152
  if (typescript_1.default.isStringLiteral(attr.initializer)) {
203
- const v = attr.initializer.text;
204
- if (isValidEvent(v))
205
- events.add(v);
153
+ if (isValidEvent(attr.initializer.text)) {
154
+ events.add(attr.initializer.text);
155
+ }
206
156
  }
207
157
  if (typescript_1.default.isJsxExpression(attr.initializer)) {
208
158
  const expr = attr.initializer.expression;
209
159
  if (!expr)
210
160
  continue;
211
- const res = (0, resolver_1.resolveNodeValue)(expr, checker, inheritedParamMap);
161
+ const res = (0, resolver_1.resolveNodeValue)(expr, checker);
212
162
  res.values.forEach(v => {
213
163
  if (isValidEvent(v))
214
164
  events.add(v);
@@ -219,65 +169,37 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
219
169
  typescript_1.default.forEachChild(node, visit);
220
170
  return;
221
171
  }
222
- // FUNCTION CALLS
172
+ // CALL
223
173
  if (typescript_1.default.isCallExpression(node)) {
224
- let trackParamIndex = null;
225
- let isTracking = false;
226
- const callName = getCallName(node.expression);
227
- // direct .track()
228
- if (typescript_1.default.isPropertyAccessExpression(node.expression) &&
229
- node.expression.name.text === "track") {
230
- isTracking = true;
231
- trackParamIndex = 0;
232
- }
233
- // config wrapper
234
- const manualWrapper = config.functionWrappers.find(w => w.name === callName);
235
- if (manualWrapper) {
236
- isTracking = true;
237
- trackParamIndex = 0;
238
- }
239
- // resolve function → check body
240
174
  const resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
241
- if (resolvedFn) {
242
- const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
243
- if (idx !== null) {
244
- isTracking = true;
245
- trackParamIndex = idx;
246
- if (!manualWrapper && callName !== "track") {
247
- detectedFunctionWrappers.add(callName);
248
- }
249
- }
250
- }
251
- if (!isTracking) {
252
- typescript_1.default.forEachChild(node, visit);
253
- return;
254
- }
255
175
  const paramMap = new Map(inheritedParamMap ?? []);
256
- if (resolvedFn && typescript_1.default.isFunctionLike(resolvedFn)) {
257
- resolvedFn.parameters.forEach((param, i) => {
176
+ if (resolvedFn) {
177
+ resolvedFn.parameters.forEach((p, i) => {
258
178
  const arg = node.arguments[i];
259
179
  if (!arg)
260
180
  return;
261
- if (typescript_1.default.isIdentifier(param.name)) {
262
- paramMap.set(param.name.text, arg);
181
+ if (typescript_1.default.isIdentifier(p.name)) {
182
+ paramMap.set(p.name.text, arg);
263
183
  }
264
184
  });
265
185
  }
266
- if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
267
- const res = (0, resolver_1.resolveNodeValue)(node.arguments[trackParamIndex], checker, paramMap);
268
- res.values.forEach(v => {
269
- if (isValidEvent(v))
270
- events.add(v);
271
- });
272
- }
273
- // CROSS FILE
274
- if (resolvedFn) {
275
- const sf = resolvedFn.getSourceFile();
276
- if (!visited.has(sf.fileName)) {
277
- const res = scanSource(sf, checker, config, visited, paramMap, wrapperCache);
278
- res.events.forEach(e => events.add(e));
186
+ // direct track
187
+ extractTrackCall(node, paramMap);
188
+ // wrapper
189
+ if (resolvedFn && isWrapperFunction(resolvedFn)) {
190
+ const name = getCallName(node.expression);
191
+ if (name && name !== "track") {
192
+ detectedFunctionWrappers.add(name);
193
+ }
194
+ if (resolvedFn.body) {
195
+ visit(resolvedFn.body);
279
196
  }
280
197
  }
198
+ for (const arg of node.arguments) {
199
+ visit(arg);
200
+ }
201
+ visit(node.expression);
202
+ return;
281
203
  }
282
204
  typescript_1.default.forEachChild(node, visit);
283
205
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,200 +1,65 @@
1
1
  import ts from "typescript";
2
2
 
3
- // HELPERS
4
- function isFunctionLike(
5
- node: ts.Node
6
- ): node is ts.FunctionLikeDeclaration {
3
+ function isFn(node: ts.Node): node is ts.FunctionLikeDeclaration {
7
4
  return (
8
5
  ts.isFunctionDeclaration(node) ||
9
6
  ts.isMethodDeclaration(node) ||
10
- ts.isConstructorDeclaration(node) ||
11
7
  ts.isFunctionExpression(node) ||
12
8
  ts.isArrowFunction(node)
13
9
  );
14
10
  }
15
11
 
16
- // symbol → function
17
12
  function resolveFromSymbol(
18
13
  symbol: ts.Symbol,
19
14
  checker: ts.TypeChecker,
20
- visited = new Set<ts.Symbol>(),
21
- depth = 0
15
+ seen = new Set<ts.Symbol>()
22
16
  ): ts.FunctionLikeDeclaration | null {
23
- if (!symbol) return null;
24
- if (visited.has(symbol)) return null;
25
- if (depth > 10) return null;
17
+ if (seen.has(symbol)) return null;
18
+ seen.add(symbol);
26
19
 
27
- visited.add(symbol);
28
-
29
- // Alias unwrap (import/export)
30
20
  if (symbol.flags & ts.SymbolFlags.Alias) {
31
- try {
32
- const aliased = checker.getAliasedSymbol(symbol);
33
- return resolveFromSymbol(aliased, checker, visited, depth + 1);
34
- } catch {}
21
+ return resolveFromSymbol(checker.getAliasedSymbol(symbol), checker, seen);
35
22
  }
36
23
 
37
- // Declarations
38
- const decls = symbol.getDeclarations() ?? [];
24
+ for (const d of symbol.getDeclarations() ?? []) {
25
+ if (isFn(d)) return d;
39
26
 
40
- for (const decl of decls) {
41
- /**
42
- * direct function
43
- */
44
- if (isFunctionLike(decl)) {
45
- return decl;
46
- }
27
+ if (ts.isVariableDeclaration(d) && d.initializer) {
28
+ if (isFn(d.initializer)) return d.initializer;
47
29
 
48
- // VARIABLE → FUNCTION const fn = () => {}
49
- if (ts.isVariableDeclaration(decl) && decl.initializer) {
50
- if (isFunctionLike(decl.initializer)) {
51
- return decl.initializer;
30
+ const s = checker.getSymbolAtLocation(d.initializer);
31
+ if (s) {
32
+ const r = resolveFromSymbol(s, checker, seen);
33
+ if (r) return r;
52
34
  }
53
-
54
- const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
55
- if (innerSymbol && innerSymbol !== symbol) {
56
- const resolved = resolveFromSymbol(
57
- innerSymbol,
58
- checker,
59
- visited,
60
- depth + 1
61
- );
62
- if (resolved) return resolved;
63
- }
64
- }
65
-
66
- // CLASS METHOD
67
- if (ts.isMethodDeclaration(decl)) {
68
- return decl;
69
- }
70
-
71
- // interface / type method
72
- if (ts.isMethodSignature(decl)) {
73
- const parent = decl.parent;
74
-
75
- if (
76
- ts.isInterfaceDeclaration(parent) ||
77
- ts.isTypeLiteralNode(parent)
78
- ) {
79
- const type = checker.getTypeAtLocation(parent);
80
- const props = type.getProperties();
81
-
82
- for (const prop of props) {
83
- const resolved = resolveFromSymbol(
84
- prop,
85
- checker,
86
- visited,
87
- depth + 1
88
- );
89
- if (resolved) return resolved;
90
- }
91
- }
92
- }
93
-
94
- // fallback recursion
95
- const inner = checker.getSymbolAtLocation(decl);
96
- if (inner && inner !== symbol) {
97
- const resolved = resolveFromSymbol(
98
- inner,
99
- checker,
100
- visited,
101
- depth + 1
102
- );
103
- if (resolved) return resolved;
104
- }
105
- }
106
-
107
- // Exports (re-export)
108
- const exports = (symbol as any).exports;
109
- if (exports) {
110
- for (const exp of exports.values()) {
111
- const resolved = resolveFromSymbol(
112
- exp,
113
- checker,
114
- visited,
115
- depth + 1
116
- );
117
- if (resolved) return resolved;
118
35
  }
119
36
  }
120
37
 
121
38
  return null;
122
39
  }
123
40
 
124
- // MAIN FUNCTION
125
41
  export function resolveFunctionFromCall(
126
42
  expr: ts.Expression,
127
43
  checker: ts.TypeChecker
128
44
  ): ts.FunctionLikeDeclaration | null {
129
- // obj.method()
45
+ // foo.bar()
130
46
  if (ts.isPropertyAccessExpression(expr)) {
131
- let symbol = checker.getSymbolAtLocation(expr.name);
132
-
133
- if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
134
- try {
135
- symbol = checker.getAliasedSymbol(symbol);
136
- } catch {}
137
- }
138
-
139
- if (symbol) {
140
- const resolved = resolveFromSymbol(symbol, checker);
141
- if (resolved) return resolved;
47
+ const s = checker.getSymbolAtLocation(expr.name);
48
+ if (s) {
49
+ const r = resolveFromSymbol(s, checker);
50
+ if (r) return r;
142
51
  }
143
52
  }
144
53
 
145
- // obj["method"]()
146
- if (ts.isElementAccessExpression(expr)) {
147
- const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
148
-
149
- if (symbol) {
150
- const resolved = resolveFromSymbol(symbol, checker);
151
- if (resolved) return resolved;
152
- }
153
- }
154
-
155
- // Call signatures
54
+ // direct call
156
55
  const type = checker.getTypeAtLocation(expr);
157
- const signatures = type.getCallSignatures();
158
-
159
- for (const sig of signatures) {
160
- const decl = sig.getDeclaration();
161
- if (!decl) continue;
162
-
163
- if (isFunctionLike(decl)) {
164
- return decl;
165
- }
166
-
167
- if (ts.isMethodSignature(decl)) {
168
- const symbol = checker.getSymbolAtLocation(decl.name);
169
-
170
- if (symbol) {
171
- const resolved = resolveFromSymbol(symbol, checker);
172
- if (resolved) return resolved;
173
- }
174
- }
56
+ for (const sig of type.getCallSignatures()) {
57
+ const d = sig.getDeclaration();
58
+ if (d && isFn(d)) return d;
175
59
  }
176
60
 
177
- // Identifier fallback
178
- if (ts.isIdentifier(expr)) {
179
- let symbol = checker.getSymbolAtLocation(expr);
180
-
181
- if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
182
- try {
183
- symbol = checker.getAliasedSymbol(symbol);
184
- } catch {}
185
- }
186
-
187
- if (symbol) {
188
- const resolved = resolveFromSymbol(symbol, checker);
189
- if (resolved) return resolved;
190
- }
191
- }
192
-
193
- // LAST fallback
194
- const symbol = checker.getSymbolAtLocation(expr);
195
- if (symbol) {
196
- return resolveFromSymbol(symbol, checker);
197
- }
61
+ const s = checker.getSymbolAtLocation(expr);
62
+ if (s) return resolveFromSymbol(s, checker);
198
63
 
199
64
  return null;
200
65
  }