@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.
- package/README.md +154 -32
- package/dist/analysis/cache/evaluationCache.js +21 -0
- package/dist/analysis/cache/resolvedCallCache.js +21 -0
- package/dist/analysis/cache/resolvedExportCache.js +21 -0
- package/dist/analysis/cache/semanticDependencyGraph.js +25 -0
- package/dist/analysis/cache/symbolInvalidation.js +20 -0
- package/dist/analysis/extractor/extractor.js +47 -0
- package/dist/analysis/extractor/propagationExtractor.js +52 -0
- package/dist/analysis/pipeline/semanticPass.js +2 -0
- package/dist/analysis/resolver/callResolver.js +125 -0
- package/dist/analysis/resolver/exportResolver.js +75 -0
- package/dist/analysis/resolver/resolver.js +133 -0
- package/dist/analysis/scanner/analyzer/propagationAnalyzer.js +193 -0
- package/dist/analysis/scanner/analyzer/returnPropagationAnalyzer.js +105 -0
- package/dist/analysis/scanner/scanner.js +117 -0
- package/dist/analysis/scanner/sinkDetector.js +91 -0
- package/dist/analysis/shared/evaluationContext.js +9 -0
- package/dist/analysis/shared/propagation.js +3 -0
- package/dist/analysis/shared/returnPropagation.js +2 -0
- package/dist/analysis/shared/types.js +2 -0
- package/dist/analysis/shared/utils.js +62 -0
- package/dist/analysis/symbols/symbolUtils.js +68 -0
- package/dist/analysis/symbols/wrapperRegistry.js +55 -0
- package/dist/cli/check.js +73 -0
- package/dist/{commands → cli}/init.js +1 -1
- package/dist/{commands → cli}/send.js +1 -1
- package/dist/cli/sync.js +56 -0
- package/dist/{commands → cli}/watch.js +41 -25
- package/dist/compiler/compilerContext.js +204 -0
- package/dist/compiler/documentRegistry.js +78 -0
- package/dist/compiler/importGraph.js +83 -0
- package/dist/compiler/scheduler.js +29 -0
- package/dist/core/EventraEngine.js +234 -0
- package/dist/core/projectScanner.js +73 -0
- package/dist/{utils → filesystem}/getVirtualFile.js +1 -3
- package/dist/filesystem/processFile.js +38 -0
- package/dist/index.js +5 -5
- package/dist/shared/buildSyntheticTsx.js +20 -0
- package/dist/shared/templateExpressionExtractor.js +22 -0
- package/package.json +32 -9
- package/.github/workflows/release.yml +0 -43
- package/ARCHITECTURE.md +0 -88
- package/assets/eventra-icon-animated.svg +0 -19
- package/dist/commands/check.js +0 -78
- package/dist/commands/sync.js +0 -79
- package/dist/engine/boundary.js +0 -14
- package/dist/engine/callResolver.js +0 -58
- package/dist/engine/engine.js +0 -154
- package/dist/engine/exportResolver.js +0 -46
- package/dist/engine/functionAnalyzer.js +0 -71
- package/dist/engine/graph.js +0 -168
- package/dist/engine/languageService.js +0 -162
- package/dist/engine/resolver.js +0 -149
- package/dist/engine/scanner.js +0 -230
- package/dist/utils/processFile.js +0 -28
- package/dist/utils/universal.js +0 -190
- package/src/commands/check.ts +0 -91
- package/src/commands/init.ts +0 -174
- package/src/commands/send.ts +0 -213
- package/src/commands/sync.ts +0 -106
- package/src/commands/watch.ts +0 -144
- package/src/engine/boundary.ts +0 -12
- package/src/engine/callResolver.ts +0 -65
- package/src/engine/engine.ts +0 -227
- package/src/engine/exportResolver.ts +0 -56
- package/src/engine/functionAnalyzer.ts +0 -81
- package/src/engine/graph.ts +0 -193
- package/src/engine/hash.ts +0 -5
- package/src/engine/languageService.ts +0 -209
- package/src/engine/resolver.ts +0 -177
- package/src/engine/scanner.ts +0 -298
- package/src/index.ts +0 -49
- package/src/types.ts +0 -35
- package/src/utils/config.ts +0 -53
- package/src/utils/getVirtualFile.ts +0 -5
- package/src/utils/processFile.ts +0 -29
- package/src/utils/universal.ts +0 -249
- package/tests/fixtures/backend/express/app.ts +0 -233
- package/tests/fixtures/backend/nest/service.ts +0 -70
- package/tests/fixtures/backend/node/index.ts +0 -63
- package/tests/fixtures/frontend/next/page.tsx +0 -101
- package/tests/fixtures/frontend/react/App.tsx +0 -104
- package/tests/fixtures/frontend/vue/App.vue +0 -83
- package/tests/fixtures/wrappers/component/test.tsx +0 -62
- package/tests/fixtures/wrappers/function/test.ts +0 -60
- package/tests/run.ts +0 -106
- package/tsconfig.json +0 -14
- /package/dist/{utils → config}/config.js +0 -0
- /package/dist/{engine → shared}/hash.js +0 -0
package/README.md
CHANGED
|
@@ -9,15 +9,28 @@
|
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
10
|
[]()
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="./assets/eventra-icon-animated.svg" width="120">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
# Eventra CLI
|
|
17
|
+
|
|
18
|
+
[](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
|
|
19
|
+
[](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
|
|
20
|
+
[](https://www.typescriptlang.org/)
|
|
21
|
+
[]()
|
|
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
|
|
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
|
-
|
|
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
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
50
|
-
4. Extracts
|
|
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
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
97
|
+
##### Direct calls
|
|
98
|
+
|
|
78
99
|
```ts
|
|
79
100
|
track("event")
|
|
80
101
|
analytics.track("event")
|
|
81
102
|
```
|
|
82
103
|
|
|
83
|
-
|
|
104
|
+
##### Variables
|
|
105
|
+
|
|
84
106
|
```ts
|
|
85
107
|
const EVENT = "click"
|
|
86
108
|
track(EVENT)
|
|
87
109
|
```
|
|
88
110
|
|
|
89
|
-
|
|
111
|
+
##### Enums
|
|
112
|
+
|
|
90
113
|
```ts
|
|
91
114
|
track(EVENTS.CLICK)
|
|
92
115
|
```
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
##### Template strings
|
|
118
|
+
|
|
95
119
|
```ts
|
|
96
120
|
track(`click_${type}`)
|
|
97
121
|
```
|
|
98
122
|
|
|
99
|
-
|
|
123
|
+
##### Conditional expressions
|
|
124
|
+
|
|
100
125
|
```ts
|
|
101
126
|
track(condition ? "a" : "b")
|
|
102
127
|
```
|
|
103
128
|
|
|
104
|
-
|
|
129
|
+
##### Arrays
|
|
130
|
+
|
|
105
131
|
```ts
|
|
106
|
-
track(
|
|
132
|
+
track(["a", "b"])
|
|
107
133
|
```
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
##### Object payloads
|
|
136
|
+
|
|
110
137
|
```ts
|
|
111
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
|
175
|
-
"exclude": [
|
|
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
|
|
302
|
+
Eventra works with:
|
|
185
303
|
|
|
186
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|