@eventra_dev/eventra-cli 0.1.31 → 0.1.33
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/ARCHITECTURE.md +88 -0
- package/dist/engine/callResolver.js +56 -25
- package/dist/engine/languageService.js +36 -11
- package/dist/engine/scanner.js +74 -71
- package/dist/utils/universal.js +73 -21
- package/package.json +1 -1
- package/src/engine/callResolver.ts +61 -28
- package/src/engine/languageService.ts +50 -16
- package/src/engine/scanner.ts +83 -94
- package/src/utils/universal.ts +90 -21
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Eventra CLI Architecture
|
|
2
|
+
|
|
3
|
+
## High-Level Flow
|
|
4
|
+
|
|
5
|
+
FILES → parseUniversal → TSService → scanSource → Engine → CLI
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Diagram
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
+-------------------+
|
|
13
|
+
| Source Files |
|
|
14
|
+
+---------+---------+
|
|
15
|
+
|
|
|
16
|
+
v
|
|
17
|
+
+-------------------+
|
|
18
|
+
| parseUniversal |
|
|
19
|
+
| (normalize input) |
|
|
20
|
+
+---------+---------+
|
|
21
|
+
|
|
|
22
|
+
v
|
|
23
|
+
+-------------------+
|
|
24
|
+
| TSService |
|
|
25
|
+
| (AST + Types) |
|
|
26
|
+
+---------+---------+
|
|
27
|
+
|
|
|
28
|
+
v
|
|
29
|
+
+-------------------+
|
|
30
|
+
| scanSource |
|
|
31
|
+
| (core analyzer) |
|
|
32
|
+
+----+------+-------+
|
|
33
|
+
| |
|
|
34
|
+
| |
|
|
35
|
+
v v
|
|
36
|
+
+----------------+----------------+
|
|
37
|
+
| |
|
|
38
|
+
+-------------------+ +-----------------------+
|
|
39
|
+
| Function Wrappers | | Component Wrappers |
|
|
40
|
+
+-------------------+ +-----------------------+
|
|
41
|
+
| |
|
|
42
|
+
+---------------+-----------------+
|
|
43
|
+
|
|
|
44
|
+
v
|
|
45
|
+
+-------------------+
|
|
46
|
+
| resolveNodeValue |
|
|
47
|
+
| (extract values) |
|
|
48
|
+
+---------+---------+
|
|
49
|
+
|
|
|
50
|
+
v
|
|
51
|
+
+-------------------+
|
|
52
|
+
| EventraEngine |
|
|
53
|
+
| (orchestration) |
|
|
54
|
+
+---------+---------+
|
|
55
|
+
|
|
|
56
|
+
v
|
|
57
|
+
+-------------------+
|
|
58
|
+
| CLI (sync/check) |
|
|
59
|
+
+-------------------+
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Modules
|
|
65
|
+
|
|
66
|
+
### Parser
|
|
67
|
+
Extracts JS from any framework (Vue, Svelte, Astro, HTML).
|
|
68
|
+
|
|
69
|
+
### TSService
|
|
70
|
+
Provides TypeScript AST and type resolution.
|
|
71
|
+
|
|
72
|
+
### Scanner
|
|
73
|
+
Finds:
|
|
74
|
+
- track() calls
|
|
75
|
+
- wrappers
|
|
76
|
+
- values
|
|
77
|
+
|
|
78
|
+
### Engine
|
|
79
|
+
Handles:
|
|
80
|
+
- caching
|
|
81
|
+
- dependency graph
|
|
82
|
+
- incremental updates
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Goal
|
|
87
|
+
|
|
88
|
+
Static analytics extraction without runtime.
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveFunctionFromCall = resolveFunctionFromCall;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
// HELPERS
|
|
8
9
|
function isFunctionLike(node) {
|
|
9
10
|
return (typescript_1.default.isFunctionDeclaration(node) ||
|
|
10
11
|
typescript_1.default.isMethodDeclaration(node) ||
|
|
@@ -12,32 +13,33 @@ function isFunctionLike(node) {
|
|
|
12
13
|
typescript_1.default.isFunctionExpression(node) ||
|
|
13
14
|
typescript_1.default.isArrowFunction(node));
|
|
14
15
|
}
|
|
16
|
+
// symbol → function
|
|
15
17
|
function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
18
|
+
if (!symbol)
|
|
19
|
+
return null;
|
|
16
20
|
if (visited.has(symbol))
|
|
17
21
|
return null;
|
|
18
|
-
if (depth >
|
|
22
|
+
if (depth > 10)
|
|
19
23
|
return null;
|
|
20
24
|
visited.add(symbol);
|
|
21
|
-
// unwrap
|
|
25
|
+
// Alias unwrap (import/export)
|
|
22
26
|
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// re-export support
|
|
27
|
-
if (symbol.exports) {
|
|
28
|
-
for (const exp of symbol.exports.values()) {
|
|
29
|
-
const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
|
|
30
|
-
if (resolved)
|
|
31
|
-
return resolved;
|
|
27
|
+
try {
|
|
28
|
+
const aliased = checker.getAliasedSymbol(symbol);
|
|
29
|
+
return resolveFromSymbol(aliased, checker, visited, depth + 1);
|
|
32
30
|
}
|
|
31
|
+
catch { }
|
|
33
32
|
}
|
|
33
|
+
// Declarations
|
|
34
34
|
const decls = symbol.getDeclarations() ?? [];
|
|
35
35
|
for (const decl of decls) {
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* direct function
|
|
38
|
+
*/
|
|
37
39
|
if (isFunctionLike(decl)) {
|
|
38
40
|
return decl;
|
|
39
41
|
}
|
|
40
|
-
// VARIABLE → FUNCTION
|
|
42
|
+
// VARIABLE → FUNCTION const fn = () => {}
|
|
41
43
|
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
42
44
|
if (isFunctionLike(decl.initializer)) {
|
|
43
45
|
return decl.initializer;
|
|
@@ -49,17 +51,18 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
49
51
|
return resolved;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
|
-
//
|
|
54
|
+
// CLASS METHOD
|
|
55
|
+
if (typescript_1.default.isMethodDeclaration(decl)) {
|
|
56
|
+
return decl;
|
|
57
|
+
}
|
|
58
|
+
// interface / type method
|
|
53
59
|
if (typescript_1.default.isMethodSignature(decl)) {
|
|
54
60
|
const parent = decl.parent;
|
|
55
61
|
if (typescript_1.default.isInterfaceDeclaration(parent) ||
|
|
56
62
|
typescript_1.default.isTypeLiteralNode(parent)) {
|
|
57
|
-
const name = decl.name.getText();
|
|
58
63
|
const type = checker.getTypeAtLocation(parent);
|
|
59
64
|
const props = type.getProperties();
|
|
60
65
|
for (const prop of props) {
|
|
61
|
-
if (prop.name !== name)
|
|
62
|
-
continue;
|
|
63
66
|
const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
|
|
64
67
|
if (resolved)
|
|
65
68
|
return resolved;
|
|
@@ -67,21 +70,34 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
// fallback recursion
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
const resolved = resolveFromSymbol(
|
|
73
|
+
const inner = checker.getSymbolAtLocation(decl);
|
|
74
|
+
if (inner && inner !== symbol) {
|
|
75
|
+
const resolved = resolveFromSymbol(inner, checker, visited, depth + 1);
|
|
76
|
+
if (resolved)
|
|
77
|
+
return resolved;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Exports (re-export)
|
|
81
|
+
const exports = symbol.exports;
|
|
82
|
+
if (exports) {
|
|
83
|
+
for (const exp of exports.values()) {
|
|
84
|
+
const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
|
|
73
85
|
if (resolved)
|
|
74
86
|
return resolved;
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
return null;
|
|
78
90
|
}
|
|
91
|
+
// MAIN FUNCTION
|
|
79
92
|
function resolveFunctionFromCall(expr, checker) {
|
|
80
|
-
//
|
|
93
|
+
// obj.method()
|
|
81
94
|
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
82
95
|
let symbol = checker.getSymbolAtLocation(expr.name);
|
|
83
96
|
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
84
|
-
|
|
97
|
+
try {
|
|
98
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
85
101
|
}
|
|
86
102
|
if (symbol) {
|
|
87
103
|
const resolved = resolveFromSymbol(symbol, checker);
|
|
@@ -89,7 +105,7 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
89
105
|
return resolved;
|
|
90
106
|
}
|
|
91
107
|
}
|
|
92
|
-
//
|
|
108
|
+
// obj["method"]()
|
|
93
109
|
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
94
110
|
const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
|
|
95
111
|
if (symbol) {
|
|
@@ -98,7 +114,7 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
98
114
|
return resolved;
|
|
99
115
|
}
|
|
100
116
|
}
|
|
101
|
-
//
|
|
117
|
+
// Call signatures
|
|
102
118
|
const type = checker.getTypeAtLocation(expr);
|
|
103
119
|
const signatures = type.getCallSignatures();
|
|
104
120
|
for (const sig of signatures) {
|
|
@@ -117,7 +133,22 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
117
133
|
}
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
|
-
// fallback
|
|
136
|
+
// Identifier fallback
|
|
137
|
+
if (typescript_1.default.isIdentifier(expr)) {
|
|
138
|
+
let symbol = checker.getSymbolAtLocation(expr);
|
|
139
|
+
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
140
|
+
try {
|
|
141
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
142
|
+
}
|
|
143
|
+
catch { }
|
|
144
|
+
}
|
|
145
|
+
if (symbol) {
|
|
146
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
147
|
+
if (resolved)
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// LAST fallback
|
|
121
152
|
const symbol = checker.getSymbolAtLocation(expr);
|
|
122
153
|
if (symbol) {
|
|
123
154
|
return resolveFromSymbol(symbol, checker);
|
|
@@ -23,6 +23,7 @@ class TSService {
|
|
|
23
23
|
const configPath = typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
24
24
|
let compilerOptions = {
|
|
25
25
|
allowJs: true,
|
|
26
|
+
allowNonTsExtensions: true,
|
|
26
27
|
jsx: typescript_1.default.JsxEmit.React,
|
|
27
28
|
jsxImportSource: "react",
|
|
28
29
|
moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
|
|
@@ -49,7 +50,12 @@ class TSService {
|
|
|
49
50
|
this.baseUrl = baseUrl;
|
|
50
51
|
this.paths = paths;
|
|
51
52
|
const host = {
|
|
52
|
-
getScriptFileNames: () =>
|
|
53
|
+
getScriptFileNames: () => {
|
|
54
|
+
if (this.fileNames.size === 0) {
|
|
55
|
+
return typescript_1.default.sys.readDirectory(this.root, [".ts", ".tsx", ".js", ".jsx"], undefined, undefined);
|
|
56
|
+
}
|
|
57
|
+
return Array.from(this.fileNames);
|
|
58
|
+
},
|
|
53
59
|
getScriptVersion: (file) => {
|
|
54
60
|
file = this.normalize(file);
|
|
55
61
|
return this.files.get(file)?.version.toString() ?? "0";
|
|
@@ -61,6 +67,20 @@ class TSService {
|
|
|
61
67
|
this.touch(file);
|
|
62
68
|
return typescript_1.default.ScriptSnapshot.fromString(f.content);
|
|
63
69
|
}
|
|
70
|
+
if (file.endsWith(".tsx")) {
|
|
71
|
+
const original = file.replace(/\.tsx$/, "");
|
|
72
|
+
if (fs_1.default.existsSync(original)) {
|
|
73
|
+
const content = fs_1.default.readFileSync(original, "utf-8");
|
|
74
|
+
this.files.set(file, {
|
|
75
|
+
content,
|
|
76
|
+
version: 1,
|
|
77
|
+
});
|
|
78
|
+
this.fileNames.add(file);
|
|
79
|
+
this.touch(file);
|
|
80
|
+
this.evictIfNeeded();
|
|
81
|
+
return typescript_1.default.ScriptSnapshot.fromString(content);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
64
84
|
try {
|
|
65
85
|
if (fs_1.default.existsSync(file)) {
|
|
66
86
|
const content = fs_1.default.readFileSync(file, "utf-8");
|
|
@@ -75,14 +95,19 @@ class TSService {
|
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
catch { }
|
|
78
|
-
// fallback
|
|
79
98
|
return typescript_1.default.ScriptSnapshot.fromString("");
|
|
80
99
|
},
|
|
81
100
|
getCurrentDirectory: () => this.root,
|
|
82
101
|
getCompilationSettings: () => this.compilerOptions,
|
|
83
102
|
getDefaultLibFileName: (opts) => typescript_1.default.getDefaultLibFilePath(opts),
|
|
84
|
-
fileExists:
|
|
85
|
-
|
|
103
|
+
fileExists: (file) => {
|
|
104
|
+
file = this.normalize(file);
|
|
105
|
+
return this.files.has(file) || typescript_1.default.sys.fileExists(file);
|
|
106
|
+
},
|
|
107
|
+
readFile: (file) => {
|
|
108
|
+
file = this.normalize(file);
|
|
109
|
+
return this.files.get(file)?.content ?? typescript_1.default.sys.readFile(file);
|
|
110
|
+
},
|
|
86
111
|
readDirectory: typescript_1.default.sys.readDirectory,
|
|
87
112
|
};
|
|
88
113
|
this.service = typescript_1.default.createLanguageService(host);
|
|
@@ -98,24 +123,24 @@ class TSService {
|
|
|
98
123
|
evictIfNeeded() {
|
|
99
124
|
if (this.fileNames.size <= this.MAX_FILES)
|
|
100
125
|
return;
|
|
101
|
-
let
|
|
126
|
+
let oldest = null;
|
|
102
127
|
let oldestTick = Infinity;
|
|
103
128
|
for (const f of this.fileNames) {
|
|
104
129
|
const t = this.usage.get(f) ?? 0;
|
|
105
130
|
if (t < oldestTick) {
|
|
106
131
|
oldestTick = t;
|
|
107
|
-
|
|
132
|
+
oldest = f;
|
|
108
133
|
}
|
|
109
134
|
}
|
|
110
|
-
if (
|
|
111
|
-
this.files.delete(
|
|
112
|
-
this.fileNames.delete(
|
|
113
|
-
this.usage.delete(
|
|
135
|
+
if (oldest) {
|
|
136
|
+
this.files.delete(oldest);
|
|
137
|
+
this.fileNames.delete(oldest);
|
|
138
|
+
this.usage.delete(oldest);
|
|
114
139
|
}
|
|
115
140
|
}
|
|
116
141
|
ensureProgram() {
|
|
117
142
|
try {
|
|
118
|
-
this.service.getProgram();
|
|
143
|
+
this.service.getProgram()?.getTypeChecker();
|
|
119
144
|
}
|
|
120
145
|
catch { }
|
|
121
146
|
}
|
package/dist/engine/scanner.js
CHANGED
|
@@ -9,7 +9,6 @@ const resolver_1 = require("./resolver");
|
|
|
9
9
|
const callResolver_1 = require("./callResolver");
|
|
10
10
|
const functionAnalyzer_1 = require("./functionAnalyzer");
|
|
11
11
|
const exportResolver_1 = require("./exportResolver");
|
|
12
|
-
const boundary_1 = require("./boundary");
|
|
13
12
|
// UTILS
|
|
14
13
|
function getJsxAttrName(name) {
|
|
15
14
|
if (typescript_1.default.isIdentifier(name))
|
|
@@ -136,67 +135,58 @@ function findTrackedPropName(fn, checker) {
|
|
|
136
135
|
return result;
|
|
137
136
|
}
|
|
138
137
|
// MAIN SCANNER
|
|
139
|
-
function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache) {
|
|
140
|
-
wrapperCache = wrapperCache ?? new Map();
|
|
138
|
+
function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
|
|
141
139
|
const events = new Set();
|
|
142
140
|
const detectedFunctionWrappers = new Set();
|
|
143
141
|
const detectedComponentWrappers = new Map();
|
|
144
|
-
if (visited.
|
|
142
|
+
if (visited.has(source.fileName) || visited.size > 200) {
|
|
145
143
|
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
146
144
|
}
|
|
147
145
|
visited.add(source.fileName);
|
|
148
146
|
function visit(node) {
|
|
149
147
|
// JSX WRAPPERS
|
|
150
|
-
if (typescript_1.default.isJsxSelfClosingElement(node) ||
|
|
151
|
-
typescript_1.default.isJsxOpeningElement(node)) {
|
|
148
|
+
if (typescript_1.default.isJsxSelfClosingElement(node) || typescript_1.default.isJsxOpeningElement(node)) {
|
|
152
149
|
const tagName = getJsxTagName(node.tagName);
|
|
153
150
|
// skip html
|
|
154
|
-
if (
|
|
151
|
+
if (tagName[0] === tagName[0].toLowerCase()) {
|
|
155
152
|
typescript_1.default.forEachChild(node, visit);
|
|
156
153
|
return;
|
|
157
154
|
}
|
|
158
|
-
let wrapper = config.wrappers.find(
|
|
155
|
+
let wrapper = config.wrappers.find(w => w.name === tagName);
|
|
156
|
+
// AUTO DETECT
|
|
159
157
|
if (!wrapper) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
164
|
-
symbol = checker.getAliasedSymbol(symbol);
|
|
165
|
-
}
|
|
166
|
-
symbol =
|
|
167
|
-
(0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
|
|
168
|
-
cacheKey = checker.getFullyQualifiedName(symbol);
|
|
169
|
-
}
|
|
170
|
-
if (wrapperCache?.has(cacheKey)) {
|
|
171
|
-
const cached = wrapperCache.get(cacheKey);
|
|
172
|
-
if (cached) {
|
|
158
|
+
if (wrapperCache.has(tagName)) {
|
|
159
|
+
const cached = wrapperCache.get(tagName);
|
|
160
|
+
if (cached)
|
|
173
161
|
wrapper = { name: tagName, prop: cached };
|
|
174
|
-
}
|
|
175
162
|
}
|
|
176
|
-
else
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (typescript_1.default.isFunctionDeclaration(decl))
|
|
181
|
-
fn = decl;
|
|
182
|
-
if (typescript_1.default.isVariableDeclaration(decl) &&
|
|
183
|
-
decl.initializer &&
|
|
184
|
-
(typescript_1.default.isArrowFunction(decl.initializer) ||
|
|
185
|
-
typescript_1.default.isFunctionExpression(decl.initializer))) {
|
|
186
|
-
fn = decl.initializer;
|
|
187
|
-
}
|
|
188
|
-
if (!fn)
|
|
189
|
-
continue;
|
|
190
|
-
const prop = findTrackedPropName(fn, checker);
|
|
191
|
-
if (prop) {
|
|
192
|
-
detected = prop;
|
|
193
|
-
break;
|
|
194
|
-
}
|
|
163
|
+
else {
|
|
164
|
+
let symbol = checker.getSymbolAtLocation(node.tagName);
|
|
165
|
+
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
166
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
195
167
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
168
|
+
if (symbol) {
|
|
169
|
+
symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
|
|
170
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
171
|
+
let fn = null;
|
|
172
|
+
if (typescript_1.default.isFunctionDeclaration(decl))
|
|
173
|
+
fn = decl;
|
|
174
|
+
if (typescript_1.default.isVariableDeclaration(decl) &&
|
|
175
|
+
decl.initializer &&
|
|
176
|
+
(typescript_1.default.isArrowFunction(decl.initializer) ||
|
|
177
|
+
typescript_1.default.isFunctionExpression(decl.initializer))) {
|
|
178
|
+
fn = decl.initializer;
|
|
179
|
+
}
|
|
180
|
+
if (!fn)
|
|
181
|
+
continue;
|
|
182
|
+
const prop = findTrackedPropName(fn, checker);
|
|
183
|
+
wrapperCache.set(tagName, prop ?? null);
|
|
184
|
+
if (prop) {
|
|
185
|
+
wrapper = { name: tagName, prop };
|
|
186
|
+
detectedComponentWrappers.set(tagName, prop);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
200
190
|
}
|
|
201
191
|
}
|
|
202
192
|
}
|
|
@@ -210,16 +200,16 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
|
|
|
210
200
|
if (!attr.initializer)
|
|
211
201
|
continue;
|
|
212
202
|
if (typescript_1.default.isStringLiteral(attr.initializer)) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
203
|
+
const v = attr.initializer.text;
|
|
204
|
+
if (isValidEvent(v))
|
|
205
|
+
events.add(v);
|
|
216
206
|
}
|
|
217
207
|
if (typescript_1.default.isJsxExpression(attr.initializer)) {
|
|
218
208
|
const expr = attr.initializer.expression;
|
|
219
209
|
if (!expr)
|
|
220
210
|
continue;
|
|
221
211
|
const res = (0, resolver_1.resolveNodeValue)(expr, checker, inheritedParamMap);
|
|
222
|
-
res.values.forEach(
|
|
212
|
+
res.values.forEach(v => {
|
|
223
213
|
if (isValidEvent(v))
|
|
224
214
|
events.add(v);
|
|
225
215
|
});
|
|
@@ -229,24 +219,39 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
|
|
|
229
219
|
typescript_1.default.forEachChild(node, visit);
|
|
230
220
|
return;
|
|
231
221
|
}
|
|
232
|
-
// FUNCTION
|
|
222
|
+
// FUNCTION CALLS
|
|
233
223
|
if (typescript_1.default.isCallExpression(node)) {
|
|
234
|
-
|
|
235
|
-
|
|
224
|
+
let trackParamIndex = null;
|
|
225
|
+
let isTracking = false;
|
|
226
|
+
const callName = getCallName(node.expression);
|
|
227
|
+
// direct .track()
|
|
228
|
+
if (typescript_1.default.isPropertyAccessExpression(node.expression) &&
|
|
229
|
+
node.expression.name.text === "track") {
|
|
230
|
+
isTracking = true;
|
|
231
|
+
trackParamIndex = 0;
|
|
232
|
+
}
|
|
233
|
+
// config wrapper
|
|
234
|
+
const manualWrapper = config.functionWrappers.find(w => w.name === callName);
|
|
235
|
+
if (manualWrapper) {
|
|
236
|
+
isTracking = true;
|
|
237
|
+
trackParamIndex = 0;
|
|
238
|
+
}
|
|
239
|
+
// resolve function → check body
|
|
236
240
|
const resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
!
|
|
241
|
+
if (resolvedFn) {
|
|
242
|
+
const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
|
|
243
|
+
if (idx !== null) {
|
|
244
|
+
isTracking = true;
|
|
245
|
+
trackParamIndex = idx;
|
|
246
|
+
if (!manualWrapper && callName !== "track") {
|
|
247
|
+
detectedFunctionWrappers.add(callName);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
243
251
|
if (!isTracking) {
|
|
244
252
|
typescript_1.default.forEachChild(node, visit);
|
|
245
253
|
return;
|
|
246
254
|
}
|
|
247
|
-
if (!isKnownWrapper && isAutoWrapper && name !== "track") {
|
|
248
|
-
detectedFunctionWrappers.add(name);
|
|
249
|
-
}
|
|
250
255
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
251
256
|
if (resolvedFn && typescript_1.default.isFunctionLike(resolvedFn)) {
|
|
252
257
|
resolvedFn.parameters.forEach((param, i) => {
|
|
@@ -258,21 +263,19 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
|
|
|
258
263
|
}
|
|
259
264
|
});
|
|
260
265
|
}
|
|
261
|
-
if (
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
events.add(v);
|
|
268
|
-
});
|
|
269
|
-
}
|
|
266
|
+
if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
|
|
267
|
+
const res = (0, resolver_1.resolveNodeValue)(node.arguments[trackParamIndex], checker, paramMap);
|
|
268
|
+
res.values.forEach(v => {
|
|
269
|
+
if (isValidEvent(v))
|
|
270
|
+
events.add(v);
|
|
271
|
+
});
|
|
270
272
|
}
|
|
273
|
+
// CROSS FILE
|
|
271
274
|
if (resolvedFn) {
|
|
272
275
|
const sf = resolvedFn.getSourceFile();
|
|
273
276
|
if (!visited.has(sf.fileName)) {
|
|
274
277
|
const res = scanSource(sf, checker, config, visited, paramMap, wrapperCache);
|
|
275
|
-
res.events.forEach(
|
|
278
|
+
res.events.forEach(e => events.add(e));
|
|
276
279
|
}
|
|
277
280
|
}
|
|
278
281
|
}
|