@eventra_dev/eventra-cli 0.2.3 → 0.3.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.
Files changed (89) hide show
  1. package/README.md +154 -32
  2. package/dist/analysis/cache/evaluationCache.js +21 -0
  3. package/dist/analysis/cache/resolvedCallCache.js +21 -0
  4. package/dist/analysis/cache/resolvedExportCache.js +21 -0
  5. package/dist/analysis/cache/semanticDependencyGraph.js +25 -0
  6. package/dist/analysis/cache/symbolInvalidation.js +20 -0
  7. package/dist/analysis/extractor/extractor.js +47 -0
  8. package/dist/analysis/extractor/propagationExtractor.js +52 -0
  9. package/dist/analysis/pipeline/semanticPass.js +2 -0
  10. package/dist/analysis/resolver/callResolver.js +125 -0
  11. package/dist/analysis/resolver/exportResolver.js +75 -0
  12. package/dist/analysis/resolver/resolver.js +133 -0
  13. package/dist/analysis/scanner/analyzer/propagationAnalyzer.js +193 -0
  14. package/dist/analysis/scanner/analyzer/returnPropagationAnalyzer.js +105 -0
  15. package/dist/analysis/scanner/scanner.js +117 -0
  16. package/dist/analysis/scanner/sinkDetector.js +91 -0
  17. package/dist/analysis/shared/evaluationContext.js +9 -0
  18. package/dist/analysis/shared/propagation.js +3 -0
  19. package/dist/analysis/shared/returnPropagation.js +2 -0
  20. package/dist/analysis/shared/types.js +2 -0
  21. package/dist/analysis/shared/utils.js +62 -0
  22. package/dist/analysis/symbols/symbolUtils.js +68 -0
  23. package/dist/analysis/symbols/wrapperRegistry.js +55 -0
  24. package/dist/cli/check.js +73 -0
  25. package/dist/{commands → cli}/init.js +1 -1
  26. package/dist/{commands → cli}/send.js +1 -1
  27. package/dist/cli/sync.js +56 -0
  28. package/dist/{commands → cli}/watch.js +41 -25
  29. package/dist/compiler/compilerContext.js +204 -0
  30. package/dist/compiler/documentRegistry.js +78 -0
  31. package/dist/compiler/importGraph.js +83 -0
  32. package/dist/compiler/scheduler.js +29 -0
  33. package/dist/core/EventraEngine.js +234 -0
  34. package/dist/core/projectScanner.js +73 -0
  35. package/dist/{utils → filesystem}/getVirtualFile.js +1 -3
  36. package/dist/filesystem/processFile.js +38 -0
  37. package/dist/index.js +5 -5
  38. package/dist/shared/buildSyntheticTsx.js +20 -0
  39. package/dist/shared/templateExpressionExtractor.js +22 -0
  40. package/package.json +32 -9
  41. package/.github/workflows/release.yml +0 -43
  42. package/ARCHITECTURE.md +0 -88
  43. package/assets/eventra-icon-animated.svg +0 -19
  44. package/dist/commands/check.js +0 -78
  45. package/dist/commands/sync.js +0 -79
  46. package/dist/engine/boundary.js +0 -14
  47. package/dist/engine/callResolver.js +0 -58
  48. package/dist/engine/engine.js +0 -154
  49. package/dist/engine/exportResolver.js +0 -46
  50. package/dist/engine/functionAnalyzer.js +0 -71
  51. package/dist/engine/graph.js +0 -168
  52. package/dist/engine/languageService.js +0 -162
  53. package/dist/engine/resolver.js +0 -149
  54. package/dist/engine/scanner.js +0 -230
  55. package/dist/utils/processFile.js +0 -28
  56. package/dist/utils/universal.js +0 -190
  57. package/src/commands/check.ts +0 -91
  58. package/src/commands/init.ts +0 -174
  59. package/src/commands/send.ts +0 -213
  60. package/src/commands/sync.ts +0 -106
  61. package/src/commands/watch.ts +0 -144
  62. package/src/engine/boundary.ts +0 -12
  63. package/src/engine/callResolver.ts +0 -65
  64. package/src/engine/engine.ts +0 -227
  65. package/src/engine/exportResolver.ts +0 -56
  66. package/src/engine/functionAnalyzer.ts +0 -81
  67. package/src/engine/graph.ts +0 -193
  68. package/src/engine/hash.ts +0 -5
  69. package/src/engine/languageService.ts +0 -209
  70. package/src/engine/resolver.ts +0 -177
  71. package/src/engine/scanner.ts +0 -298
  72. package/src/index.ts +0 -49
  73. package/src/types.ts +0 -35
  74. package/src/utils/config.ts +0 -53
  75. package/src/utils/getVirtualFile.ts +0 -5
  76. package/src/utils/processFile.ts +0 -29
  77. package/src/utils/universal.ts +0 -249
  78. package/tests/fixtures/backend/express/app.ts +0 -233
  79. package/tests/fixtures/backend/nest/service.ts +0 -70
  80. package/tests/fixtures/backend/node/index.ts +0 -63
  81. package/tests/fixtures/frontend/next/page.tsx +0 -101
  82. package/tests/fixtures/frontend/react/App.tsx +0 -104
  83. package/tests/fixtures/frontend/vue/App.vue +0 -83
  84. package/tests/fixtures/wrappers/component/test.tsx +0 -62
  85. package/tests/fixtures/wrappers/function/test.ts +0 -60
  86. package/tests/run.ts +0 -106
  87. package/tsconfig.json +0 -14
  88. /package/dist/{utils → config}/config.js +0 -0
  89. /package/dist/{engine → shared}/hash.js +0 -0
package/README.md CHANGED
@@ -9,15 +9,28 @@
9
9
  [![TypeScript](https://img.shields.io/badge/typescript-ready-blue.svg)](https://www.typescriptlang.org/)
10
10
  [![License](https://img.shields.io/npm/l/@eventra_dev/eventra-cli)]()
11
11
 
12
- Eventra CLI automatically discovers and tracks event usage across your codebase — even through abstractions like wrappers, variables, and cross-file calls.
12
+ <p align="center">
13
+ <img src="./assets/eventra-icon-animated.svg" width="120">
14
+ </p>
15
+
16
+ # Eventra CLI
17
+
18
+ [![npm version](https://img.shields.io/npm/v/@eventra_dev/eventra-cli.svg)](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
19
+ [![npm downloads](https://img.shields.io/npm/dm/@eventra_dev/eventra-cli.svg)](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
20
+ [![TypeScript](https://img.shields.io/badge/typescript-ready-blue.svg)](https://www.typescriptlang.org/)
21
+ [![License](https://img.shields.io/npm/l/@eventra_dev/eventra-cli)]()
22
+
23
+ Eventra CLI automatically discovers and tracks event usage across your codebase — even through abstractions like wrappers, propagation chains, variables, and cross-file calls.
13
24
 
14
25
  ---
15
26
 
16
27
  ## Overview
17
28
 
18
- Eventra scans your codebase using a TypeScript-powered engine and extracts event usage — even through abstractions like wrappers, variables, and cross-file calls.
29
+ Eventra scans your JavaScript and TypeScript codebase using a TypeScript-powered semantic engine and extracts analytics event usage statically.
30
+
31
+ It supports wrapper propagation, return propagation, cross-file resolution, and incremental dependency-aware analysis.
19
32
 
20
- It requires no runtime instrumentation and fits naturally into CI/CD workflows.
33
+ No runtime instrumentation required.
21
34
 
22
35
  ---
23
36
 
@@ -44,83 +57,135 @@ eventra send
44
57
 
45
58
  ## How it works
46
59
 
47
- 1. Parses your project using the TypeScript AST
48
- 2. Resolves function calls, imports, and dependencies
49
- 3. Tracks event propagation through variables and wrappers
50
- 4. Extracts event names and aggregates them
60
+ 1. Parses your project using the TypeScript compiler API
61
+ 2. Builds semantic indexes from AST nodes
62
+ 3. Resolves wrappers, imports, exports, and propagation chains
63
+ 4. Extracts concrete analytics events statically
64
+ 5. Incrementally re-analyzes only affected files
51
65
 
52
- No runtime required. No SDK needed.
66
+ No runtime SDK hooks.
67
+ No Babel transforms.
68
+ No instrumentation.
53
69
 
54
70
  ---
55
71
 
56
72
  ## Why Eventra
57
73
 
58
- - Prevent missing or inconsistent event tracking
59
- - Keep your analytics schema in sync with code
60
- - Detect dead or unused events
61
- - Integrate validation into CI pipelines
74
+ * Prevent missing or inconsistent event tracking
75
+ * Keep analytics schemas synchronized with source code
76
+ * Detect dead or unused events
77
+ * Analyze wrapper-heavy codebases statically
78
+ * Integrate validation into CI/CD pipelines
79
+ * Avoid runtime analytics overhead
62
80
 
63
81
  ---
64
82
 
65
83
  ## Commands
66
84
 
67
85
  ### `eventra init`
86
+
68
87
  Creates `eventra.json` configuration file.
69
88
 
70
89
  ---
71
90
 
72
91
  ### `eventra sync`
92
+
73
93
  Scans your project and updates the list of detected events.
74
94
 
75
95
  #### Supports
76
96
 
77
- - Direct calls
97
+ ##### Direct calls
98
+
78
99
  ```ts
79
100
  track("event")
80
101
  analytics.track("event")
81
102
  ```
82
103
 
83
- - Variables
104
+ ##### Variables
105
+
84
106
  ```ts
85
107
  const EVENT = "click"
86
108
  track(EVENT)
87
109
  ```
88
110
 
89
- - Enums
111
+ ##### Enums
112
+
90
113
  ```ts
91
114
  track(EVENTS.CLICK)
92
115
  ```
93
116
 
94
- - Template strings
117
+ ##### Template strings
118
+
95
119
  ```ts
96
120
  track(`click_${type}`)
97
121
  ```
98
122
 
99
- - Conditionals
123
+ ##### Conditional expressions
124
+
100
125
  ```ts
101
126
  track(condition ? "a" : "b")
102
127
  ```
103
128
 
104
- - Object payloads
129
+ ##### Arrays
130
+
105
131
  ```ts
106
- track({ event: "click" })
132
+ track(["a", "b"])
107
133
  ```
108
134
 
109
- - Function wrappers
135
+ ##### Object payloads
136
+
110
137
  ```ts
111
- const trackFeature = (name: string) => {
138
+ track({
139
+ event: "click"
140
+ })
141
+ ```
142
+
143
+ ##### Function wrappers
144
+
145
+ ```ts
146
+ function trackFeature(name: string) {
112
147
  track(name)
113
148
  }
114
149
 
115
150
  trackFeature("click")
116
151
  ```
117
152
 
118
- - Cross-file event resolution
119
- - Framework templates (Vue, Svelte, Astro)
153
+ ##### Wrapper propagation
154
+
155
+ ```ts
156
+ function trackEvent(props: {
157
+ event: string
158
+ }) {
159
+ track(props.event)
160
+ }
161
+
162
+ trackEvent({
163
+ event: "purchase"
164
+ })
165
+ ```
166
+
167
+ ##### Return propagation
168
+
169
+ ```ts
170
+ function buildEvent(name: string) {
171
+ return name
172
+ }
173
+
174
+ track(buildEvent("signup"))
175
+ ```
176
+
177
+ ##### Cross-file resolution
178
+
179
+ ```ts
180
+ import { trackFeature } from "./tracker"
181
+
182
+ trackFeature("purchase")
183
+ ```
120
184
 
121
185
  ---
122
186
 
123
187
  ### `eventra check`
188
+
124
189
  Validates detected events against config.
125
190
 
126
191
  ```bash
@@ -128,13 +193,15 @@ eventra check
128
193
  ```
129
194
 
130
195
  Detects:
131
- - New events
132
- - Removed events
133
- - Inconsistent tracking usage
196
+
197
+ * New events
198
+ * Removed events
199
+ * Invalid or inconsistent tracking usage
134
200
 
135
201
  ---
136
202
 
137
203
  ### `eventra check --fix`
204
+
138
205
  Automatically updates config to match detected events.
139
206
 
140
207
  ```bash
@@ -144,15 +211,19 @@ eventra check --fix
144
211
  ---
145
212
 
146
213
  ### `eventra watch`
147
- Runs in watch mode and updates events in real time.
214
+
215
+ Runs in watch mode with incremental semantic analysis.
148
216
 
149
217
  ```bash
150
218
  eventra watch
151
219
  ```
152
220
 
221
+ Only affected files are re-analyzed after changes.
222
+
153
223
  ---
154
224
 
155
225
  ### `eventra send`
226
+
156
227
  Sends events to Eventra backend.
157
228
 
158
229
  ```bash
@@ -171,25 +242,76 @@ eventra send
171
242
  "wrappers": [],
172
243
  "functionWrappers": [],
173
244
  "sync": {
174
- "include": ["**/*.{ts,tsx,js,jsx,vue,svelte,astro}"],
175
- "exclude": ["node_modules", "dist", ".next", ".git"]
245
+ "include": ["**/*.{ts,tsx,js,jsx}"],
246
+ "exclude": [
247
+ "node_modules",
248
+ "dist",
249
+ ".next",
250
+ ".git"
251
+ ]
176
252
  }
177
253
  }
178
254
  ```
179
255
 
180
256
  ---
181
257
 
258
+ ## Architecture Highlights
259
+
260
+ ### Semantic propagation analysis
261
+
262
+ Eventra resolves event propagation through wrappers and returned values.
263
+
264
+ ```ts
265
+ function createEvent(name: string) {
266
+ return name
267
+ }
268
+
269
+ function trackEvent(name: string) {
270
+ track(createEvent(name))
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ### Incremental analysis engine
277
+
278
+ Eventra does NOT rescan the whole project after every update.
279
+
280
+ Instead it:
281
+
282
+ 1. Updates changed files
283
+ 2. Invalidates affected caches
284
+ 3. Rebuilds TypeScript incrementally
285
+ 4. Traverses dependency graph
286
+ 5. Re-analyzes affected files only
287
+
288
+ ---
289
+
290
+ ### Static extraction
291
+
292
+ Eventra works entirely statically.
293
+
294
+ No runtime execution.
295
+ No monkey patching.
296
+ No SDK instrumentation.
297
+
298
+ ---
299
+
182
300
  ## Supported Environments
183
301
 
184
- Eventra works with **any JavaScript or TypeScript codebase**.
302
+ Eventra works with:
185
303
 
186
- No framework limitations.
304
+ * JavaScript
305
+ * TypeScript
306
+ * Node.js projects
307
+ * Browser applications
308
+ * Monorepos
187
309
 
188
310
  ---
189
311
 
190
312
  ## Requirements
191
313
 
192
- - Node.js 18+
314
+ * Node.js 18+
193
315
 
194
316
  ---
195
317
 
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // src/analysis/cache/evaluationCache.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.EvaluationCache = void 0;
5
+ class EvaluationCache {
6
+ cache = new WeakMap();
7
+ get(symbol) {
8
+ return this.cache.get(symbol);
9
+ }
10
+ set(symbol, result) {
11
+ this.cache.set(symbol, result);
12
+ }
13
+ delete(symbol) {
14
+ this.cache.delete(symbol);
15
+ }
16
+ clear() {
17
+ this.cache =
18
+ new WeakMap();
19
+ }
20
+ }
21
+ exports.EvaluationCache = EvaluationCache;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // src/analysis/cache/resolvedCallCache.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ResolvedCallCache = void 0;
5
+ class ResolvedCallCache {
6
+ cache = new WeakMap();
7
+ get(symbol) {
8
+ return this.cache.get(symbol);
9
+ }
10
+ set(symbol, value) {
11
+ this.cache.set(symbol, value);
12
+ }
13
+ delete(symbol) {
14
+ this.cache.delete(symbol);
15
+ }
16
+ clear() {
17
+ this.cache =
18
+ new WeakMap();
19
+ }
20
+ }
21
+ exports.ResolvedCallCache = ResolvedCallCache;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // src/analysis/cache/resolvedExportCache.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ResolvedExportCache = void 0;
5
+ class ResolvedExportCache {
6
+ cache = new WeakMap();
7
+ get(symbol) {
8
+ return this.cache.get(symbol);
9
+ }
10
+ set(symbol, resolved) {
11
+ this.cache.set(symbol, resolved);
12
+ }
13
+ delete(symbol) {
14
+ this.cache.delete(symbol);
15
+ }
16
+ clear() {
17
+ this.cache =
18
+ new WeakMap();
19
+ }
20
+ }
21
+ exports.ResolvedExportCache = ResolvedExportCache;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SemanticDependencyGraph = void 0;
4
+ class SemanticDependencyGraph {
5
+ dependencies = new Map();
6
+ addDependency(from, to) {
7
+ let deps = this.dependencies.get(from);
8
+ if (!deps) {
9
+ deps =
10
+ new Set();
11
+ this.dependencies.set(from, deps);
12
+ }
13
+ deps.add(to);
14
+ }
15
+ getDependents(file) {
16
+ const result = new Set();
17
+ for (const [from, deps,] of this.dependencies) {
18
+ if (deps.has(file)) {
19
+ result.add(from);
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ }
25
+ exports.SemanticDependencyGraph = SemanticDependencyGraph;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ // src/analysis/cache/symbolInvalidation.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.invalidateSourceFileSymbols = invalidateSourceFileSymbols;
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ function invalidateSourceFileSymbols(sourceFile, checker, evaluationCache, resolvedCallCache, resolvedExportCache) {
10
+ function visit(node) {
11
+ const symbol = checker.getSymbolAtLocation(node);
12
+ if (symbol) {
13
+ evaluationCache.delete(symbol);
14
+ resolvedCallCache.delete(symbol);
15
+ resolvedExportCache.delete(symbol);
16
+ }
17
+ typescript_1.default.forEachChild(node, visit);
18
+ }
19
+ visit(sourceFile);
20
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // src/analysis/extractor/extractor.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.extractEvents = extractEvents;
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ const resolver_1 = require("../resolver/resolver");
10
+ const evaluationContext_1 = require("../shared/evaluationContext");
11
+ const propagationExtractor_1 = require("./propagationExtractor");
12
+ function isValidEvent(value) {
13
+ return (value.length > 0
14
+ && value.length < 160
15
+ && /^[a-zA-Z0-9:_./-]+$/.test(value));
16
+ }
17
+ function extractEvents(index, checker, _config, evaluationCache, exportCache, resolvedCallCache, wrapperRegistry) {
18
+ const events = new Set();
19
+ //
20
+ // direct track sinks
21
+ //
22
+ for (const call of index.trackCalls) {
23
+ for (const argument of call.trackedArguments) {
24
+ const resolved = (0, resolver_1.resolveNodeValue)(argument, checker, (0, evaluationContext_1.createEvaluationContext)(), new Set(), evaluationCache, resolvedCallCache, exportCache);
25
+ for (const value of resolved.values) {
26
+ if (isValidEvent(value)) {
27
+ events.add(value);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ //
33
+ // wrapper propagation
34
+ //
35
+ function visit(node) {
36
+ if (typescript_1.default.isCallExpression(node)) {
37
+ (0, propagationExtractor_1.extractPropagationEvents)(node, checker, wrapperRegistry, resolvedCallCache, evaluationCache, exportCache, events, new Set());
38
+ }
39
+ typescript_1.default.forEachChild(node, visit);
40
+ }
41
+ visit(index.sourceFile);
42
+ return {
43
+ events,
44
+ detectedFunctionWrappers: new Set(index.wrappers.map(wrapper => wrapper.symbol.getName())),
45
+ detectedComponentWrappers: new Map(),
46
+ };
47
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // src/analysis/extractor/propagationExtractor.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.extractPropagationEvents = extractPropagationEvents;
5
+ const callResolver_1 = require("../resolver/callResolver");
6
+ const resolver_1 = require("../resolver/resolver");
7
+ const evaluationContext_1 = require("../shared/evaluationContext");
8
+ function isValidEvent(value) {
9
+ return (value.length > 0
10
+ && value.length < 160
11
+ && /^[a-zA-Z0-9:_./-]+$/.test(value));
12
+ }
13
+ function extractPropagationEvents(call, checker, wrapperRegistry, resolvedCallCache, evaluationCache, exportCache, events, visited) {
14
+ const resolved = (0, callResolver_1.resolveFunctionFromCall)(call.expression, checker, resolvedCallCache, exportCache);
15
+ if (!resolved) {
16
+ return;
17
+ }
18
+ const semantic = wrapperRegistry.get(resolved, checker, exportCache);
19
+ if (!semantic) {
20
+ return;
21
+ }
22
+ const signature = checker.getSignatureFromDeclaration(resolved);
23
+ if (!signature) {
24
+ return;
25
+ }
26
+ if (visited.has(signature)) {
27
+ return;
28
+ }
29
+ visited.add(signature);
30
+ for (const propagation of semantic.propagations) {
31
+ extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, exportCache, events);
32
+ }
33
+ }
34
+ function extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, exportCache, events) {
35
+ const argument = call.arguments[propagation.sourceParameterIndex];
36
+ if (!argument) {
37
+ return;
38
+ }
39
+ const context = (0, evaluationContext_1.createEvaluationContext)();
40
+ const parameterSymbol = checker.getSymbolAtLocation(propagation
41
+ .sourceParameter
42
+ .name);
43
+ if (parameterSymbol) {
44
+ context.parameterBindings.set(parameterSymbol, argument);
45
+ }
46
+ const resolved = (0, resolver_1.resolveNodeValue)(propagation.targetNode, checker, context, new Set(), evaluationCache, resolvedCallCache, exportCache);
47
+ for (const value of resolved.values) {
48
+ if (isValidEvent(value)) {
49
+ events.add(value);
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ // src/analysis/resolver/callResolver.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.resolveFunctionFromCall = resolveFunctionFromCall;
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ const exportResolver_1 = require("./exportResolver");
10
+ function isFunctionLike(node) {
11
+ return (typescript_1.default.isFunctionDeclaration(node)
12
+ || typescript_1.default.isMethodDeclaration(node)
13
+ || typescript_1.default.isArrowFunction(node)
14
+ || typescript_1.default.isFunctionExpression(node));
15
+ }
16
+ function resolveDeclaration(declaration) {
17
+ //
18
+ // function foo() {}
19
+ //
20
+ if (isFunctionLike(declaration)) {
21
+ return declaration;
22
+ }
23
+ //
24
+ // const foo = () => {}
25
+ //
26
+ if (typescript_1.default.isVariableDeclaration(declaration)
27
+ && declaration.initializer
28
+ && isFunctionLike(declaration.initializer)) {
29
+ return declaration.initializer;
30
+ }
31
+ //
32
+ // class field = () => {}
33
+ //
34
+ if (typescript_1.default.isPropertyDeclaration(declaration)
35
+ && declaration.initializer
36
+ && isFunctionLike(declaration.initializer)) {
37
+ return declaration.initializer;
38
+ }
39
+ return null;
40
+ }
41
+ function getExpressionSymbol(expression, checker) {
42
+ //
43
+ // analytics.track()
44
+ //
45
+ if (typescript_1.default.isPropertyAccessExpression(expression)) {
46
+ return checker.getSymbolAtLocation(expression.name);
47
+ }
48
+ //
49
+ // analytics?.track()
50
+ //
51
+ if (typescript_1.default.isPropertyAccessChain(expression)) {
52
+ return checker.getSymbolAtLocation(expression.name);
53
+ }
54
+ //
55
+ // analytics["track"]()
56
+ //
57
+ if (typescript_1.default.isElementAccessExpression(expression)
58
+ && expression.argumentExpression) {
59
+ return checker.getSymbolAtLocation(expression.argumentExpression);
60
+ }
61
+ //
62
+ // track()
63
+ //
64
+ return checker.getSymbolAtLocation(expression);
65
+ }
66
+ function resolveFunctionFromCall(expression, checker, callCache, exportCache) {
67
+ let symbol = getExpressionSymbol(expression, checker);
68
+ if (!symbol) {
69
+ return null;
70
+ }
71
+ //
72
+ // export unwrap
73
+ //
74
+ symbol =
75
+ (0, exportResolver_1.resolveExportedSymbol)(symbol, checker, exportCache)
76
+ ?? symbol;
77
+ //
78
+ // alias unwrap
79
+ //
80
+ if (symbol.flags
81
+ & typescript_1.default.SymbolFlags.Alias) {
82
+ symbol =
83
+ checker.getAliasedSymbol(symbol);
84
+ }
85
+ //
86
+ // cache
87
+ //
88
+ const cached = callCache.get(symbol);
89
+ if (cached !== undefined) {
90
+ return cached;
91
+ }
92
+ //
93
+ // declaration resolution
94
+ //
95
+ const declarations = symbol.getDeclarations()
96
+ ?? [];
97
+ for (const declaration of declarations) {
98
+ const resolved = resolveDeclaration(declaration);
99
+ if (!resolved) {
100
+ continue;
101
+ }
102
+ callCache.set(symbol, resolved);
103
+ return resolved;
104
+ }
105
+ //
106
+ // fallback:
107
+ // type signatures
108
+ //
109
+ const type = checker.getTypeOfSymbolAtLocation(symbol, declarations[0]
110
+ ?? expression);
111
+ const signatures = checker.getSignaturesOfType(type, typescript_1.default.SignatureKind.Call);
112
+ for (const signature of signatures) {
113
+ const declaration = signature.declaration;
114
+ if (declaration
115
+ && isFunctionLike(declaration)) {
116
+ callCache.set(symbol, declaration);
117
+ return declaration;
118
+ }
119
+ }
120
+ //
121
+ // unresolved
122
+ //
123
+ callCache.set(symbol, null);
124
+ return null;
125
+ }