@eventra_dev/eventra-cli 0.1.7 → 0.1.8

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.
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isExternalFile = isExternalFile;
7
+ const path_1 = __importDefault(require("path"));
8
+ function isExternalFile(file) {
9
+ const normalized = path_1.default.resolve(file);
10
+ return (normalized.includes(`${path_1.default.sep}node_modules${path_1.default.sep}`) ||
11
+ normalized.includes(`${path_1.default.sep}dist${path_1.default.sep}`) ||
12
+ normalized.includes(`${path_1.default.sep}build${path_1.default.sep}`) ||
13
+ normalized.endsWith(".d.ts"));
14
+ }
@@ -12,20 +12,23 @@ function isFunctionLike(node) {
12
12
  typescript_1.default.isFunctionExpression(node) ||
13
13
  typescript_1.default.isArrowFunction(node));
14
14
  }
15
- function resolveFromSymbol(symbol, checker, visited = new Set()) {
15
+ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
16
16
  if (visited.has(symbol))
17
17
  return null;
18
+ if (depth > 5)
19
+ return null;
18
20
  visited.add(symbol);
19
- // alias unwrap
21
+ // unwrap alias
20
22
  if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
21
23
  const aliased = checker.getAliasedSymbol(symbol);
22
- return resolveFromSymbol(aliased, checker, visited);
24
+ return resolveFromSymbol(aliased, checker, visited, depth + 1);
23
25
  }
24
26
  const decls = symbol.getDeclarations() ?? [];
25
27
  for (const decl of decls) {
26
28
  if (isFunctionLike(decl)) {
27
29
  return decl;
28
30
  }
31
+ // MethodSignature
29
32
  if (typescript_1.default.isMethodSignature(decl)) {
30
33
  const parent = decl.parent;
31
34
  if (typescript_1.default.isInterfaceDeclaration(parent) || typescript_1.default.isTypeLiteralNode(parent)) {
@@ -35,16 +38,16 @@ function resolveFromSymbol(symbol, checker, visited = new Set()) {
35
38
  for (const prop of props) {
36
39
  if (prop.name !== name)
37
40
  continue;
38
- const resolved = resolveFromSymbol(prop, checker, visited);
41
+ const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
39
42
  if (resolved)
40
43
  return resolved;
41
44
  }
42
45
  }
43
46
  }
44
- // fallback
47
+ // SAFE fallback
45
48
  const s = checker.getSymbolAtLocation(decl);
46
- if (s) {
47
- const resolved = resolveFromSymbol(s, checker, visited);
49
+ if (s && s !== symbol) {
50
+ const resolved = resolveFromSymbol(s, checker, visited, depth + 1);
48
51
  if (resolved)
49
52
  return resolved;
50
53
  }
@@ -61,7 +64,7 @@ function resolveFunctionFromCall(expr, checker) {
61
64
  if (isFunctionLike(decl)) {
62
65
  return decl;
63
66
  }
64
- // MethodSignature case
67
+ // interface method
65
68
  if (typescript_1.default.isMethodSignature(decl)) {
66
69
  const symbol = checker.getSymbolAtLocation(decl.name);
67
70
  if (symbol) {
@@ -5,6 +5,7 @@ const languageService_1 = require("./languageService");
5
5
  const scanner_1 = require("./scanner");
6
6
  const graph_1 = require("./graph");
7
7
  const hash_1 = require("./hash");
8
+ const boundary_1 = require("./boundary");
8
9
  class EventraEngine {
9
10
  ts;
10
11
  graph;
@@ -15,8 +16,10 @@ class EventraEngine {
15
16
  const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
16
17
  this.graph = new graph_1.DependencyGraph(baseUrl, paths);
17
18
  }
19
+ // SCAN FILE
18
20
  scanFile(file, content, config) {
19
21
  const h = (0, hash_1.hash)(content);
22
+ // skip unchanged
20
23
  if (this.fileHash.get(file) === h) {
21
24
  return (this.fileResults.get(file) ?? {
22
25
  events: new Set(),
@@ -35,24 +38,39 @@ class EventraEngine {
35
38
  detectedComponentWrappers: new Map(),
36
39
  };
37
40
  }
41
+ // update dependency graph
38
42
  this.graph.update(file, source);
39
43
  const res = (0, scanner_1.scanSource)(source, checker, config);
40
44
  this.fileResults.set(file, res);
41
45
  return res;
42
46
  }
47
+ // UPDATE FILE (incremental)
43
48
  updateFile(file, content, config) {
44
49
  const res = this.scanFile(file, content, config);
45
50
  const dependents = this.graph.getAllDependentsDeep(file);
46
51
  for (const dep of dependents) {
52
+ // skip external files
53
+ if ((0, boundary_1.isExternalFile)(dep))
54
+ continue;
47
55
  const source = this.ts.getSourceFile(dep);
48
56
  const checker = this.ts.getChecker();
49
57
  if (!source || !checker)
50
58
  continue;
59
+ const depContent = this.ts.getFileContent(dep);
60
+ if (!depContent)
61
+ continue;
62
+ const h = (0, hash_1.hash)(depContent);
63
+ // skip unchanged dependents
64
+ if (this.fileHash.get(dep) === h)
65
+ continue;
66
+ this.fileHash.set(dep, h);
67
+ this.graph.update(dep, source);
51
68
  const r = (0, scanner_1.scanSource)(source, checker, config);
52
69
  this.fileResults.set(dep, r);
53
70
  }
54
71
  return res;
55
72
  }
73
+ // GET ALL EVENTS
56
74
  getAllEvents() {
57
75
  const all = new Set();
58
76
  for (const r of this.fileResults.values()) {
@@ -60,10 +78,36 @@ class EventraEngine {
60
78
  }
61
79
  return [...all];
62
80
  }
63
- removeFile(file) {
81
+ // REMOVE FILE
82
+ removeFile(file, config) {
83
+ this.ts.removeFile(file);
64
84
  this.fileResults.delete(file);
65
85
  this.fileHash.delete(file);
66
86
  this.graph.remove(file);
87
+ const dependents = this.graph.getAllDependentsDeep(file);
88
+ for (const dep of dependents) {
89
+ if ((0, boundary_1.isExternalFile)(dep))
90
+ continue;
91
+ const source = this.ts.getSourceFile(dep);
92
+ const checker = this.ts.getChecker();
93
+ if (!source || !checker)
94
+ continue;
95
+ const content = this.ts.getFileContent(dep);
96
+ if (!content)
97
+ continue;
98
+ const h = (0, hash_1.hash)(content);
99
+ // skip unchanged
100
+ if (this.fileHash.get(dep) === h)
101
+ continue;
102
+ this.fileHash.set(dep, h);
103
+ const r = (0, scanner_1.scanSource)(source, checker, config ?? {
104
+ events: [],
105
+ wrappers: [],
106
+ functionWrappers: [],
107
+ sync: { include: [], exclude: [] },
108
+ });
109
+ this.fileResults.set(dep, r);
110
+ }
67
111
  }
68
112
  }
69
113
  exports.EventraEngine = EventraEngine;
@@ -48,6 +48,9 @@ class TSService {
48
48
  },
49
49
  getScriptVersion: (file) => this.files.get(file)?.version.toString() ?? "0",
50
50
  getScriptSnapshot: (file) => {
51
+ if (!this.fileNames.has(file)) {
52
+ return undefined;
53
+ }
51
54
  const f = this.files.get(file);
52
55
  if (f) {
53
56
  return typescript_1.default.ScriptSnapshot.fromString(f.content);
@@ -83,6 +86,13 @@ class TSService {
83
86
  getSourceFile(file) {
84
87
  return this.getProgram()?.getSourceFile(file);
85
88
  }
89
+ getFileContent(file) {
90
+ return this.files.get(file)?.content;
91
+ }
92
+ removeFile(file) {
93
+ this.files.delete(file);
94
+ this.fileNames.delete(file);
95
+ }
86
96
  getModuleResolutionConfig() {
87
97
  return {
88
98
  baseUrl: this.baseUrl,
@@ -12,7 +12,7 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
12
12
  seen.add(node);
13
13
  // param mapping
14
14
  if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
15
- return resolveNodeValue(paramMap.get(node.text), checker, paramMap);
15
+ return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
16
16
  }
17
17
  if (typescript_1.default.isStringLiteral(node)) {
18
18
  return { values: [node.text], dynamic: false };
@@ -37,13 +37,16 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
37
37
  let symbol = checker.getSymbolAtLocation(node);
38
38
  if (!symbol)
39
39
  return null;
40
+ // variable inline resolve FIRST
40
41
  for (const decl of symbol.getDeclarations() ?? []) {
41
42
  if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
42
43
  return resolveNodeValue(decl.initializer, checker, paramMap, seen);
43
44
  }
44
45
  }
46
+ const original = symbol;
45
47
  symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
46
- if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
48
+ // avoid alias loop
49
+ if (symbol !== original && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
47
50
  symbol = checker.getAliasedSymbol(symbol);
48
51
  }
49
52
  for (const decl of symbol.getDeclarations() ?? []) {
@@ -9,6 +9,7 @@ const resolver_1 = require("./resolver");
9
9
  const callResolver_1 = require("./callResolver");
10
10
  const exportResolver_1 = require("./exportResolver");
11
11
  const functionAnalyzer_1 = require("./functionAnalyzer");
12
+ const boundary_1 = require("./boundary");
12
13
  const TRACKING_NAMES = new Set(["track", "event", "capture", "send"]);
13
14
  function getCallName(expr) {
14
15
  if (typescript_1.default.isIdentifier(expr))
@@ -40,8 +41,7 @@ function isPropUsedInTracking(fn, prop) {
40
41
  return;
41
42
  for (const arg of node.arguments) {
42
43
  if ((typescript_1.default.isIdentifier(arg) && arg.text === prop) ||
43
- (typescript_1.default.isPropertyAccessExpression(arg) &&
44
- arg.name.text === prop)) {
44
+ (typescript_1.default.isPropertyAccessExpression(arg) && arg.name.text === prop)) {
45
45
  found = true;
46
46
  }
47
47
  }
@@ -68,13 +68,10 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0) {
68
68
  const isAutoWrapper = fn && (0, functionAnalyzer_1.findTrackParamIndex)(fn) !== null;
69
69
  const isTracking = isKnownWrapper ||
70
70
  TRACKING_NAMES.has(name) ||
71
- isAutoWrapper;
71
+ (isAutoWrapper && fn && !(0, boundary_1.isExternalFile)(fn.getSourceFile().fileName));
72
72
  if (!isTracking)
73
73
  return;
74
- // AUTO DETECT WRAPPER
75
- if (!isKnownWrapper &&
76
- fn &&
77
- !TRACKING_NAMES.has(name)) {
74
+ if (!isKnownWrapper && isAutoWrapper && !TRACKING_NAMES.has(name)) {
78
75
  detectedFunctionWrappers.add(name);
79
76
  }
80
77
  const paramMap = new Map();
@@ -100,7 +97,6 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0) {
100
97
  handled = true;
101
98
  }
102
99
  }
103
- // fallback
104
100
  if (!handled) {
105
101
  for (const arg of node.arguments) {
106
102
  const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
@@ -110,7 +106,6 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0) {
110
106
  });
111
107
  }
112
108
  }
113
- // cross-file traversal
114
109
  if (fn) {
115
110
  const sf = fn.getSourceFile();
116
111
  if (!visited.has(sf.fileName)) {
@@ -119,7 +114,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0) {
119
114
  }
120
115
  }
121
116
  }
122
- // JSX COMPONENTS
117
+ // JSX
123
118
  if (typescript_1.default.isJsxSelfClosingElement(node) ||
124
119
  typescript_1.default.isJsxOpeningElement(node)) {
125
120
  const tagNode = typescript_1.default.isPropertyAccessExpression(node.tagName)
@@ -127,41 +122,30 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0) {
127
122
  : node.tagName;
128
123
  let symbol = checker.getSymbolAtLocation(tagNode);
129
124
  if (symbol) {
130
- const resolved = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker);
131
- if (resolved)
132
- symbol = resolved;
125
+ symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
133
126
  }
134
127
  if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
135
- const aliased = checker.getAliasedSymbol(symbol);
136
- if (aliased)
137
- symbol = aliased;
128
+ symbol = checker.getAliasedSymbol(symbol);
138
129
  }
139
130
  if (!symbol)
140
131
  return;
141
132
  const tagName = typescript_1.default.isPropertyAccessExpression(node.tagName)
142
133
  ? node.tagName.name.getText()
143
134
  : node.tagName.getText();
144
- const declarations = symbol.getDeclarations();
145
- if (!declarations)
146
- return;
147
- for (const decl of declarations) {
135
+ for (const decl of symbol.getDeclarations() ?? []) {
148
136
  let fn = null;
149
- if (typescript_1.default.isFunctionDeclaration(decl)) {
137
+ if (typescript_1.default.isFunctionDeclaration(decl))
150
138
  fn = decl;
151
- }
152
139
  if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
153
140
  const init = decl.initializer;
154
- if (typescript_1.default.isArrowFunction(init) ||
155
- typescript_1.default.isFunctionExpression(init)) {
141
+ if (typescript_1.default.isArrowFunction(init) || typescript_1.default.isFunctionExpression(init)) {
156
142
  fn = init;
157
143
  }
158
144
  }
159
145
  if (!fn)
160
146
  continue;
161
147
  const param = fn.parameters[0];
162
- if (!param)
163
- continue;
164
- if (!typescript_1.default.isObjectBindingPattern(param.name))
148
+ if (!param || !typescript_1.default.isObjectBindingPattern(param.name))
165
149
  continue;
166
150
  for (const el of param.name.elements) {
167
151
  const prop = el.name.getText();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -0,0 +1,12 @@
1
+ import path from "path";
2
+
3
+ export function isExternalFile(file: string) {
4
+ const normalized = path.resolve(file);
5
+
6
+ return (
7
+ normalized.includes(`${path.sep}node_modules${path.sep}`) ||
8
+ normalized.includes(`${path.sep}dist${path.sep}`) ||
9
+ normalized.includes(`${path.sep}build${path.sep}`) ||
10
+ normalized.endsWith(".d.ts")
11
+ );
12
+ }
@@ -15,15 +15,18 @@ function isFunctionLike(
15
15
  function resolveFromSymbol(
16
16
  symbol: ts.Symbol,
17
17
  checker: ts.TypeChecker,
18
- visited = new Set<ts.Symbol>()
18
+ visited = new Set<ts.Symbol>(),
19
+ depth = 0
19
20
  ): ts.FunctionLikeDeclaration | null {
20
21
  if (visited.has(symbol)) return null;
22
+ if (depth > 5) return null;
23
+
21
24
  visited.add(symbol);
22
25
 
23
- // alias unwrap
26
+ // unwrap alias
24
27
  if (symbol.flags & ts.SymbolFlags.Alias) {
25
28
  const aliased = checker.getAliasedSymbol(symbol);
26
- return resolveFromSymbol(aliased, checker, visited);
29
+ return resolveFromSymbol(aliased, checker, visited, depth + 1);
27
30
  }
28
31
 
29
32
  const decls = symbol.getDeclarations() ?? [];
@@ -33,6 +36,7 @@ function resolveFromSymbol(
33
36
  return decl;
34
37
  }
35
38
 
39
+ // MethodSignature
36
40
  if (ts.isMethodSignature(decl)) {
37
41
  const parent = decl.parent;
38
42
 
@@ -45,16 +49,16 @@ function resolveFromSymbol(
45
49
  for (const prop of props) {
46
50
  if (prop.name !== name) continue;
47
51
 
48
- const resolved = resolveFromSymbol(prop, checker, visited);
52
+ const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
49
53
  if (resolved) return resolved;
50
54
  }
51
55
  }
52
56
  }
53
57
 
54
- // fallback
58
+ // SAFE fallback
55
59
  const s = checker.getSymbolAtLocation(decl);
56
- if (s) {
57
- const resolved = resolveFromSymbol(s, checker, visited);
60
+ if (s && s !== symbol) {
61
+ const resolved = resolveFromSymbol(s, checker, visited, depth + 1);
58
62
  if (resolved) return resolved;
59
63
  }
60
64
  }
@@ -71,14 +75,13 @@ export function resolveFunctionFromCall(
71
75
 
72
76
  for (const sig of signatures) {
73
77
  const decl = sig.getDeclaration();
74
-
75
78
  if (!decl) continue;
76
79
 
77
80
  if (isFunctionLike(decl)) {
78
81
  return decl;
79
82
  }
80
83
 
81
- // MethodSignature case
84
+ // interface method
82
85
  if (ts.isMethodSignature(decl)) {
83
86
  const symbol = checker.getSymbolAtLocation(decl.name);
84
87
 
@@ -3,6 +3,7 @@ import { ScanResult, scanSource } from "./scanner";
3
3
  import { DependencyGraph } from "./graph";
4
4
  import { hash } from "./hash";
5
5
  import { EventraConfig } from "../types";
6
+ import { isExternalFile } from "./boundary";
6
7
 
7
8
  export class EventraEngine {
8
9
  private ts: TSService;
@@ -18,9 +19,15 @@ export class EventraEngine {
18
19
  this.graph = new DependencyGraph(baseUrl, paths);
19
20
  }
20
21
 
21
- scanFile(file: string, content: string, config: EventraConfig): ScanResult {
22
+ // SCAN FILE
23
+ scanFile(
24
+ file: string,
25
+ content: string,
26
+ config: EventraConfig
27
+ ): ScanResult {
22
28
  const h = hash(content);
23
29
 
30
+ // skip unchanged
24
31
  if (this.fileHash.get(file) === h) {
25
32
  return (
26
33
  this.fileResults.get(file) ?? {
@@ -45,6 +52,7 @@ export class EventraEngine {
45
52
  };
46
53
  }
47
54
 
55
+ // update dependency graph
48
56
  this.graph.update(file, source);
49
57
 
50
58
  const res = scanSource(source, checker, config);
@@ -54,17 +62,37 @@ export class EventraEngine {
54
62
  return res;
55
63
  }
56
64
 
57
- updateFile(file: string, content: string, config: EventraConfig) {
65
+ // UPDATE FILE (incremental)
66
+ updateFile(
67
+ file: string,
68
+ content: string,
69
+ config: EventraConfig
70
+ ) {
58
71
  const res = this.scanFile(file, content, config);
59
72
 
60
73
  const dependents = this.graph.getAllDependentsDeep(file);
61
74
 
62
75
  for (const dep of dependents) {
76
+ // skip external files
77
+ if (isExternalFile(dep)) continue;
78
+
63
79
  const source = this.ts.getSourceFile(dep);
64
80
  const checker = this.ts.getChecker();
65
81
 
66
82
  if (!source || !checker) continue;
67
83
 
84
+ const depContent = this.ts.getFileContent(dep);
85
+ if (!depContent) continue;
86
+
87
+ const h = hash(depContent);
88
+
89
+ // skip unchanged dependents
90
+ if (this.fileHash.get(dep) === h) continue;
91
+
92
+ this.fileHash.set(dep, h);
93
+
94
+ this.graph.update(dep, source);
95
+
68
96
  const r = scanSource(source, checker, config);
69
97
  this.fileResults.set(dep, r);
70
98
  }
@@ -72,6 +100,7 @@ export class EventraEngine {
72
100
  return res;
73
101
  }
74
102
 
103
+ // GET ALL EVENTS
75
104
  getAllEvents() {
76
105
  const all = new Set<string>();
77
106
 
@@ -82,9 +111,44 @@ export class EventraEngine {
82
111
  return [...all];
83
112
  }
84
113
 
85
- removeFile(file: string) {
114
+ // REMOVE FILE
115
+ removeFile(file: string, config?: EventraConfig) {
116
+ this.ts.removeFile(file);
86
117
  this.fileResults.delete(file);
87
118
  this.fileHash.delete(file);
88
119
  this.graph.remove(file);
120
+ const dependents = this.graph.getAllDependentsDeep(file);
121
+
122
+ for (const dep of dependents) {
123
+ if (isExternalFile(dep)) continue;
124
+
125
+ const source = this.ts.getSourceFile(dep);
126
+ const checker = this.ts.getChecker();
127
+
128
+ if (!source || !checker) continue;
129
+
130
+ const content = this.ts.getFileContent(dep);
131
+ if (!content) continue;
132
+
133
+ const h = hash(content);
134
+
135
+ // skip unchanged
136
+ if (this.fileHash.get(dep) === h) continue;
137
+
138
+ this.fileHash.set(dep, h);
139
+
140
+ const r = scanSource(
141
+ source,
142
+ checker,
143
+ config ?? {
144
+ events: [],
145
+ wrappers: [],
146
+ functionWrappers: [],
147
+ sync: { include: [], exclude: [] },
148
+ }
149
+ );
150
+
151
+ this.fileResults.set(dep, r);
152
+ }
89
153
  }
90
154
  }
@@ -62,6 +62,10 @@ export class TSService {
62
62
  this.files.get(file)?.version.toString() ?? "0",
63
63
 
64
64
  getScriptSnapshot: (file) => {
65
+ if (!this.fileNames.has(file)) {
66
+ return undefined;
67
+ }
68
+
65
69
  const f = this.files.get(file);
66
70
 
67
71
  if (f) {
@@ -113,6 +117,15 @@ export class TSService {
113
117
  return this.getProgram()?.getSourceFile(file);
114
118
  }
115
119
 
120
+ getFileContent(file: string) {
121
+ return this.files.get(file)?.content;
122
+ }
123
+
124
+ removeFile(file: string) {
125
+ this.files.delete(file);
126
+ this.fileNames.delete(file);
127
+ }
128
+
116
129
  getModuleResolutionConfig() {
117
130
  return {
118
131
  baseUrl: this.baseUrl,
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- import {resolveExportedSymbol} from "./exportResolver";
2
+ import { resolveExportedSymbol } from "./exportResolver";
3
3
 
4
4
  export type ResolveResult = {
5
5
  values: string[];
@@ -21,7 +21,8 @@ export function resolveNodeValue(
21
21
  return resolveNodeValue(
22
22
  paramMap.get(node.text)!,
23
23
  checker,
24
- paramMap
24
+ paramMap,
25
+ seen
25
26
  );
26
27
  }
27
28
 
@@ -53,6 +54,8 @@ export function resolveNodeValue(
53
54
  if (ts.isIdentifier(node)) {
54
55
  let symbol = checker.getSymbolAtLocation(node);
55
56
  if (!symbol) return null;
57
+
58
+ // variable inline resolve FIRST
56
59
  for (const decl of symbol.getDeclarations() ?? []) {
57
60
  if (ts.isVariableDeclaration(decl) && decl.initializer) {
58
61
  return resolveNodeValue(
@@ -64,15 +67,23 @@ export function resolveNodeValue(
64
67
  }
65
68
  }
66
69
 
70
+ const original = symbol;
71
+
67
72
  symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
68
73
 
69
- if (symbol.flags & ts.SymbolFlags.Alias) {
74
+ // avoid alias loop
75
+ if (symbol !== original && (symbol.flags & ts.SymbolFlags.Alias)) {
70
76
  symbol = checker.getAliasedSymbol(symbol);
71
77
  }
72
78
 
73
79
  for (const decl of symbol.getDeclarations() ?? []) {
74
80
  if (ts.isVariableDeclaration(decl) && decl.initializer) {
75
- return resolveNodeValue(decl.initializer, checker, paramMap, seen);
81
+ return resolveNodeValue(
82
+ decl.initializer,
83
+ checker,
84
+ paramMap,
85
+ seen
86
+ );
76
87
  }
77
88
 
78
89
  if (ts.isEnumMember(decl) && decl.initializer) {
@@ -4,6 +4,7 @@ import { resolveFunctionFromCall } from "./callResolver";
4
4
  import { resolveExportedSymbol } from "./exportResolver";
5
5
  import { findTrackParamIndex } from "./functionAnalyzer";
6
6
  import { EventraConfig } from "../types";
7
+ import { isExternalFile } from "./boundary";
7
8
 
8
9
  const TRACKING_NAMES = new Set(["track", "event", "capture", "send"]);
9
10
 
@@ -44,8 +45,7 @@ function isPropUsedInTracking(
44
45
  for (const arg of node.arguments) {
45
46
  if (
46
47
  (ts.isIdentifier(arg) && arg.text === prop) ||
47
- (ts.isPropertyAccessExpression(arg) &&
48
- arg.name.text === prop)
48
+ (ts.isPropertyAccessExpression(arg) && arg.name.text === prop)
49
49
  ) {
50
50
  found = true;
51
51
  }
@@ -99,16 +99,11 @@ export function scanSource(
99
99
  const isTracking =
100
100
  isKnownWrapper ||
101
101
  TRACKING_NAMES.has(name) ||
102
- isAutoWrapper;
102
+ (isAutoWrapper && fn && !isExternalFile(fn.getSourceFile().fileName));
103
103
 
104
104
  if (!isTracking) return;
105
105
 
106
- // AUTO DETECT WRAPPER
107
- if (
108
- !isKnownWrapper &&
109
- fn &&
110
- !TRACKING_NAMES.has(name)
111
- ) {
106
+ if (!isKnownWrapper && isAutoWrapper && !TRACKING_NAMES.has(name)) {
112
107
  detectedFunctionWrappers.add(name);
113
108
  }
114
109
 
@@ -145,7 +140,6 @@ export function scanSource(
145
140
  }
146
141
  }
147
142
 
148
- // fallback
149
143
  if (!handled) {
150
144
  for (const arg of node.arguments) {
151
145
  const res = resolveNodeValue(arg, checker, paramMap);
@@ -156,7 +150,6 @@ export function scanSource(
156
150
  }
157
151
  }
158
152
 
159
- // cross-file traversal
160
153
  if (fn) {
161
154
  const sf = fn.getSourceFile();
162
155
 
@@ -174,7 +167,7 @@ export function scanSource(
174
167
  }
175
168
  }
176
169
 
177
- // JSX COMPONENTS
170
+ // JSX
178
171
  if (
179
172
  ts.isJsxSelfClosingElement(node) ||
180
173
  ts.isJsxOpeningElement(node)
@@ -187,13 +180,11 @@ export function scanSource(
187
180
  let symbol = checker.getSymbolAtLocation(tagNode);
188
181
 
189
182
  if (symbol) {
190
- const resolved = resolveExportedSymbol(symbol, checker);
191
- if (resolved) symbol = resolved;
183
+ symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
192
184
  }
193
185
 
194
186
  if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
195
- const aliased = checker.getAliasedSymbol(symbol);
196
- if (aliased) symbol = aliased;
187
+ symbol = checker.getAliasedSymbol(symbol);
197
188
  }
198
189
 
199
190
  if (!symbol) return;
@@ -203,23 +194,14 @@ export function scanSource(
203
194
  ? node.tagName.name.getText()
204
195
  : node.tagName.getText();
205
196
 
206
- const declarations = symbol.getDeclarations();
207
- if (!declarations) return;
208
-
209
- for (const decl of declarations) {
197
+ for (const decl of symbol.getDeclarations() ?? []) {
210
198
  let fn: ts.FunctionLikeDeclaration | null = null;
211
199
 
212
- if (ts.isFunctionDeclaration(decl)) {
213
- fn = decl;
214
- }
200
+ if (ts.isFunctionDeclaration(decl)) fn = decl;
215
201
 
216
202
  if (ts.isVariableDeclaration(decl) && decl.initializer) {
217
203
  const init = decl.initializer;
218
-
219
- if (
220
- ts.isArrowFunction(init) ||
221
- ts.isFunctionExpression(init)
222
- ) {
204
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
223
205
  fn = init;
224
206
  }
225
207
  }
@@ -227,9 +209,7 @@ export function scanSource(
227
209
  if (!fn) continue;
228
210
 
229
211
  const param = fn.parameters[0];
230
- if (!param) continue;
231
-
232
- if (!ts.isObjectBindingPattern(param.name)) continue;
212
+ if (!param || !ts.isObjectBindingPattern(param.name)) continue;
233
213
 
234
214
  for (const el of param.name.elements) {
235
215
  const prop = el.name.getText();