@eventra_dev/eventra-cli 0.2.4 → 0.3.2
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 +143 -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 -152
- 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 -197
- 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,17 @@
|
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
10
|
[]()
|
|
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
|
+
Eventra CLI automatically discovers and tracks event usage across your codebase — even through abstractions like wrappers, propagation chains, variables, and cross-file calls.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
## Overview
|
|
17
17
|
|
|
18
|
-
Eventra scans your codebase using a TypeScript-powered engine and extracts event usage
|
|
18
|
+
Eventra scans your JavaScript and TypeScript codebase using a TypeScript-powered semantic engine and extracts analytics event usage statically.
|
|
19
19
|
|
|
20
|
-
It
|
|
20
|
+
It supports wrapper propagation, return propagation, cross-file resolution, and incremental dependency-aware analysis.
|
|
21
|
+
|
|
22
|
+
No runtime instrumentation required.
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -44,83 +46,135 @@ eventra send
|
|
|
44
46
|
|
|
45
47
|
## How it works
|
|
46
48
|
|
|
47
|
-
1. Parses your project using the TypeScript
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
50
|
-
4. Extracts
|
|
49
|
+
1. Parses your project using the TypeScript compiler API
|
|
50
|
+
2. Builds semantic indexes from AST nodes
|
|
51
|
+
3. Resolves wrappers, imports, exports, and propagation chains
|
|
52
|
+
4. Extracts concrete analytics events statically
|
|
53
|
+
5. Incrementally re-analyzes only affected files
|
|
51
54
|
|
|
52
|
-
No runtime
|
|
55
|
+
No runtime SDK hooks.
|
|
56
|
+
No Babel transforms.
|
|
57
|
+
No instrumentation.
|
|
53
58
|
|
|
54
59
|
---
|
|
55
60
|
|
|
56
61
|
## Why Eventra
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
* Prevent missing or inconsistent event tracking
|
|
64
|
+
* Keep analytics schemas synchronized with source code
|
|
65
|
+
* Detect dead or unused events
|
|
66
|
+
* Analyze wrapper-heavy codebases statically
|
|
67
|
+
* Integrate validation into CI/CD pipelines
|
|
68
|
+
* Avoid runtime analytics overhead
|
|
62
69
|
|
|
63
70
|
---
|
|
64
71
|
|
|
65
72
|
## Commands
|
|
66
73
|
|
|
67
74
|
### `eventra init`
|
|
75
|
+
|
|
68
76
|
Creates `eventra.json` configuration file.
|
|
69
77
|
|
|
70
78
|
---
|
|
71
79
|
|
|
72
80
|
### `eventra sync`
|
|
81
|
+
|
|
73
82
|
Scans your project and updates the list of detected events.
|
|
74
83
|
|
|
75
84
|
#### Supports
|
|
76
85
|
|
|
77
|
-
|
|
86
|
+
##### Direct calls
|
|
87
|
+
|
|
78
88
|
```ts
|
|
79
89
|
track("event")
|
|
80
90
|
analytics.track("event")
|
|
81
91
|
```
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
##### Variables
|
|
94
|
+
|
|
84
95
|
```ts
|
|
85
96
|
const EVENT = "click"
|
|
86
97
|
track(EVENT)
|
|
87
98
|
```
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
##### Enums
|
|
101
|
+
|
|
90
102
|
```ts
|
|
91
103
|
track(EVENTS.CLICK)
|
|
92
104
|
```
|
|
93
105
|
|
|
94
|
-
|
|
106
|
+
##### Template strings
|
|
107
|
+
|
|
95
108
|
```ts
|
|
96
109
|
track(`click_${type}`)
|
|
97
110
|
```
|
|
98
111
|
|
|
99
|
-
|
|
112
|
+
##### Conditional expressions
|
|
113
|
+
|
|
100
114
|
```ts
|
|
101
115
|
track(condition ? "a" : "b")
|
|
102
116
|
```
|
|
103
117
|
|
|
104
|
-
|
|
118
|
+
##### Arrays
|
|
119
|
+
|
|
105
120
|
```ts
|
|
106
|
-
track(
|
|
121
|
+
track(["a", "b"])
|
|
107
122
|
```
|
|
108
123
|
|
|
109
|
-
|
|
124
|
+
##### Object payloads
|
|
125
|
+
|
|
110
126
|
```ts
|
|
111
|
-
|
|
127
|
+
track({
|
|
128
|
+
event: "click"
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
##### Function wrappers
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
function trackFeature(name: string) {
|
|
112
136
|
track(name)
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
trackFeature("click")
|
|
116
140
|
```
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
##### Wrapper propagation
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
function trackEvent(props: {
|
|
146
|
+
event: string
|
|
147
|
+
}) {
|
|
148
|
+
track(props.event)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
trackEvent({
|
|
152
|
+
event: "purchase"
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
##### Return propagation
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
function buildEvent(name: string) {
|
|
160
|
+
return name
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
track(buildEvent("signup"))
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
##### Cross-file resolution
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { trackFeature } from "./tracker"
|
|
170
|
+
|
|
171
|
+
trackFeature("purchase")
|
|
172
|
+
```
|
|
120
173
|
|
|
121
174
|
---
|
|
122
175
|
|
|
123
176
|
### `eventra check`
|
|
177
|
+
|
|
124
178
|
Validates detected events against config.
|
|
125
179
|
|
|
126
180
|
```bash
|
|
@@ -128,13 +182,15 @@ eventra check
|
|
|
128
182
|
```
|
|
129
183
|
|
|
130
184
|
Detects:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
185
|
+
|
|
186
|
+
* New events
|
|
187
|
+
* Removed events
|
|
188
|
+
* Invalid or inconsistent tracking usage
|
|
134
189
|
|
|
135
190
|
---
|
|
136
191
|
|
|
137
192
|
### `eventra check --fix`
|
|
193
|
+
|
|
138
194
|
Automatically updates config to match detected events.
|
|
139
195
|
|
|
140
196
|
```bash
|
|
@@ -144,15 +200,19 @@ eventra check --fix
|
|
|
144
200
|
---
|
|
145
201
|
|
|
146
202
|
### `eventra watch`
|
|
147
|
-
|
|
203
|
+
|
|
204
|
+
Runs in watch mode with incremental semantic analysis.
|
|
148
205
|
|
|
149
206
|
```bash
|
|
150
207
|
eventra watch
|
|
151
208
|
```
|
|
152
209
|
|
|
210
|
+
Only affected files are re-analyzed after changes.
|
|
211
|
+
|
|
153
212
|
---
|
|
154
213
|
|
|
155
214
|
### `eventra send`
|
|
215
|
+
|
|
156
216
|
Sends events to Eventra backend.
|
|
157
217
|
|
|
158
218
|
```bash
|
|
@@ -171,25 +231,76 @@ eventra send
|
|
|
171
231
|
"wrappers": [],
|
|
172
232
|
"functionWrappers": [],
|
|
173
233
|
"sync": {
|
|
174
|
-
"include": ["**/*.{ts,tsx,js,jsx
|
|
175
|
-
"exclude": [
|
|
234
|
+
"include": ["**/*.{ts,tsx,js,jsx}"],
|
|
235
|
+
"exclude": [
|
|
236
|
+
"node_modules",
|
|
237
|
+
"dist",
|
|
238
|
+
".next",
|
|
239
|
+
".git"
|
|
240
|
+
]
|
|
176
241
|
}
|
|
177
242
|
}
|
|
178
243
|
```
|
|
179
244
|
|
|
180
245
|
---
|
|
181
246
|
|
|
247
|
+
## Architecture Highlights
|
|
248
|
+
|
|
249
|
+
### Semantic propagation analysis
|
|
250
|
+
|
|
251
|
+
Eventra resolves event propagation through wrappers and returned values.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
function createEvent(name: string) {
|
|
255
|
+
return name
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function trackEvent(name: string) {
|
|
259
|
+
track(createEvent(name))
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### Incremental analysis engine
|
|
266
|
+
|
|
267
|
+
Eventra does NOT rescan the whole project after every update.
|
|
268
|
+
|
|
269
|
+
Instead it:
|
|
270
|
+
|
|
271
|
+
1. Updates changed files
|
|
272
|
+
2. Invalidates affected caches
|
|
273
|
+
3. Rebuilds TypeScript incrementally
|
|
274
|
+
4. Traverses dependency graph
|
|
275
|
+
5. Re-analyzes affected files only
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Static extraction
|
|
280
|
+
|
|
281
|
+
Eventra works entirely statically.
|
|
282
|
+
|
|
283
|
+
No runtime execution.
|
|
284
|
+
No monkey patching.
|
|
285
|
+
No SDK instrumentation.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
182
289
|
## Supported Environments
|
|
183
290
|
|
|
184
|
-
Eventra works with
|
|
291
|
+
Eventra works with:
|
|
185
292
|
|
|
186
|
-
|
|
293
|
+
* JavaScript
|
|
294
|
+
* TypeScript
|
|
295
|
+
* Node.js projects
|
|
296
|
+
* Browser applications
|
|
297
|
+
* Monorepos
|
|
187
298
|
|
|
188
299
|
---
|
|
189
300
|
|
|
190
301
|
## Requirements
|
|
191
302
|
|
|
192
|
-
|
|
303
|
+
* Node.js 18+
|
|
193
304
|
|
|
194
305
|
---
|
|
195
306
|
|
|
@@ -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
|
+
}
|