@eventra_dev/eventra-cli 0.1.25 → 0.1.26
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/dist/commands/watch.js +41 -23
- package/dist/engine/callResolver.js +52 -4
- package/dist/engine/engine.js +63 -31
- package/dist/engine/exportResolver.js +16 -4
- package/dist/engine/functionAnalyzer.js +66 -13
- package/dist/engine/graph.js +40 -4
- package/dist/engine/languageService.js +83 -20
- package/dist/engine/resolver.js +29 -21
- package/dist/engine/scanner.js +57 -46
- package/dist/engine/trackingDetector.js +68 -0
- package/dist/utils/processFile.js +0 -9
- package/dist/utils/universal.js +25 -87
- package/package.json +1 -1
- package/src/commands/watch.ts +45 -25
- package/src/engine/callResolver.ts +77 -6
- package/src/engine/engine.ts +77 -32
- package/src/engine/exportResolver.ts +22 -5
- package/src/engine/functionAnalyzer.ts +76 -16
- package/src/engine/graph.ts +45 -6
- package/src/engine/languageService.ts +100 -29
- package/src/engine/resolver.ts +36 -27
- package/src/engine/scanner.ts +72 -50
- package/src/engine/trackingDetector.ts +94 -0
- package/src/types.ts +0 -3
- package/src/utils/processFile.ts +0 -9
- package/src/utils/universal.ts +26 -102
|
@@ -12,20 +12,27 @@ class TSService {
|
|
|
12
12
|
files = new Map();
|
|
13
13
|
fileNames = new Set();
|
|
14
14
|
service;
|
|
15
|
-
compilerOptions
|
|
16
|
-
baseUrl
|
|
17
|
-
paths
|
|
15
|
+
compilerOptions;
|
|
16
|
+
baseUrl;
|
|
17
|
+
paths;
|
|
18
|
+
usage = new Map();
|
|
19
|
+
tick = 0;
|
|
20
|
+
MAX_FILES = 2000;
|
|
18
21
|
constructor(root) {
|
|
19
22
|
this.root = root;
|
|
20
23
|
const configPath = typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
21
24
|
let compilerOptions = {
|
|
22
25
|
allowJs: true,
|
|
23
|
-
jsx: typescript_1.default.JsxEmit.
|
|
26
|
+
jsx: typescript_1.default.JsxEmit.React,
|
|
27
|
+
jsxImportSource: "react",
|
|
24
28
|
moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
|
|
25
29
|
target: typescript_1.default.ScriptTarget.ESNext,
|
|
26
30
|
esModuleInterop: true,
|
|
27
31
|
allowSyntheticDefaultImports: true,
|
|
32
|
+
skipLibCheck: true,
|
|
28
33
|
};
|
|
34
|
+
let baseUrl = root;
|
|
35
|
+
let paths = {};
|
|
29
36
|
if (configPath) {
|
|
30
37
|
const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
31
38
|
const parsed = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, root);
|
|
@@ -33,46 +40,104 @@ class TSService {
|
|
|
33
40
|
...compilerOptions,
|
|
34
41
|
...parsed.options,
|
|
35
42
|
};
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
paths = parsed.options.paths || {};
|
|
44
|
+
baseUrl = parsed.options.baseUrl
|
|
38
45
|
? path_1.default.resolve(root, parsed.options.baseUrl)
|
|
39
46
|
: root;
|
|
40
47
|
}
|
|
41
|
-
else {
|
|
42
|
-
this.baseUrl = root;
|
|
43
|
-
}
|
|
44
48
|
this.compilerOptions = compilerOptions;
|
|
49
|
+
this.baseUrl = baseUrl;
|
|
50
|
+
this.paths = paths;
|
|
45
51
|
const host = {
|
|
46
|
-
getScriptFileNames: () =>
|
|
47
|
-
|
|
52
|
+
getScriptFileNames: () => Array.from(this.fileNames),
|
|
53
|
+
getScriptVersion: (file) => {
|
|
54
|
+
file = this.normalize(file);
|
|
55
|
+
return this.files.get(file)?.version.toString() ?? "0";
|
|
48
56
|
},
|
|
49
|
-
getScriptVersion: (file) => this.files.get(file)?.version.toString() ?? "0",
|
|
50
57
|
getScriptSnapshot: (file) => {
|
|
58
|
+
file = this.normalize(file);
|
|
51
59
|
const f = this.files.get(file);
|
|
52
60
|
if (f) {
|
|
61
|
+
this.touch(file);
|
|
53
62
|
return typescript_1.default.ScriptSnapshot.fromString(f.content);
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
try {
|
|
65
|
+
if (fs_1.default.existsSync(file)) {
|
|
66
|
+
const content = fs_1.default.readFileSync(file, "utf-8");
|
|
67
|
+
this.files.set(file, {
|
|
68
|
+
content,
|
|
69
|
+
version: 1,
|
|
70
|
+
});
|
|
71
|
+
this.fileNames.add(file);
|
|
72
|
+
this.touch(file);
|
|
73
|
+
this.evictIfNeeded();
|
|
74
|
+
return typescript_1.default.ScriptSnapshot.fromString(content);
|
|
75
|
+
}
|
|
57
76
|
}
|
|
58
|
-
|
|
77
|
+
catch { }
|
|
78
|
+
// fallback
|
|
79
|
+
return typescript_1.default.ScriptSnapshot.fromString("");
|
|
59
80
|
},
|
|
60
81
|
getCurrentDirectory: () => this.root,
|
|
61
82
|
getCompilationSettings: () => this.compilerOptions,
|
|
62
83
|
getDefaultLibFileName: (opts) => typescript_1.default.getDefaultLibFilePath(opts),
|
|
63
|
-
readFile: typescript_1.default.sys.readFile,
|
|
64
84
|
fileExists: typescript_1.default.sys.fileExists,
|
|
85
|
+
readFile: typescript_1.default.sys.readFile,
|
|
65
86
|
readDirectory: typescript_1.default.sys.readDirectory,
|
|
66
87
|
};
|
|
67
88
|
this.service = typescript_1.default.createLanguageService(host);
|
|
68
89
|
}
|
|
90
|
+
// INTERNAL
|
|
91
|
+
normalize(file) {
|
|
92
|
+
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
93
|
+
}
|
|
94
|
+
touch(file) {
|
|
95
|
+
this.tick++;
|
|
96
|
+
this.usage.set(file, this.tick);
|
|
97
|
+
}
|
|
98
|
+
evictIfNeeded() {
|
|
99
|
+
if (this.fileNames.size <= this.MAX_FILES)
|
|
100
|
+
return;
|
|
101
|
+
let oldestFile = null;
|
|
102
|
+
let oldestTick = Infinity;
|
|
103
|
+
for (const f of this.fileNames) {
|
|
104
|
+
const t = this.usage.get(f) ?? 0;
|
|
105
|
+
if (t < oldestTick) {
|
|
106
|
+
oldestTick = t;
|
|
107
|
+
oldestFile = f;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (oldestFile) {
|
|
111
|
+
this.files.delete(oldestFile);
|
|
112
|
+
this.fileNames.delete(oldestFile);
|
|
113
|
+
this.usage.delete(oldestFile);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
ensureProgram() {
|
|
117
|
+
try {
|
|
118
|
+
this.service.getProgram();
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
}
|
|
122
|
+
// PUBLIC API
|
|
69
123
|
updateFile(file, content) {
|
|
124
|
+
file = this.normalize(file);
|
|
70
125
|
const prev = this.files.get(file);
|
|
71
126
|
this.files.set(file, {
|
|
72
127
|
content,
|
|
73
128
|
version: prev ? prev.version + 1 : 1,
|
|
74
129
|
});
|
|
75
130
|
this.fileNames.add(file);
|
|
131
|
+
this.touch(file);
|
|
132
|
+
this.evictIfNeeded();
|
|
133
|
+
this.ensureProgram();
|
|
134
|
+
}
|
|
135
|
+
removeFile(file) {
|
|
136
|
+
file = this.normalize(file);
|
|
137
|
+
this.files.delete(file);
|
|
138
|
+
this.fileNames.delete(file);
|
|
139
|
+
this.usage.delete(file);
|
|
140
|
+
this.ensureProgram();
|
|
76
141
|
}
|
|
77
142
|
getProgram() {
|
|
78
143
|
return this.service.getProgram();
|
|
@@ -81,15 +146,13 @@ class TSService {
|
|
|
81
146
|
return this.getProgram()?.getTypeChecker();
|
|
82
147
|
}
|
|
83
148
|
getSourceFile(file) {
|
|
149
|
+
file = this.normalize(file);
|
|
84
150
|
return this.getProgram()?.getSourceFile(file);
|
|
85
151
|
}
|
|
86
152
|
getFileContent(file) {
|
|
153
|
+
file = this.normalize(file);
|
|
87
154
|
return this.files.get(file)?.content;
|
|
88
155
|
}
|
|
89
|
-
removeFile(file) {
|
|
90
|
-
this.files.delete(file);
|
|
91
|
-
this.fileNames.delete(file);
|
|
92
|
-
}
|
|
93
156
|
getModuleResolutionConfig() {
|
|
94
157
|
return {
|
|
95
158
|
baseUrl: this.baseUrl,
|
package/dist/engine/resolver.js
CHANGED
|
@@ -11,7 +11,7 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
11
11
|
return { values: [], dynamic: true };
|
|
12
12
|
}
|
|
13
13
|
seen.add(node);
|
|
14
|
-
//
|
|
14
|
+
// PARAM MAPPING
|
|
15
15
|
if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
16
16
|
return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
|
|
17
17
|
}
|
|
@@ -28,11 +28,14 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
28
28
|
values.push(...res.values);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
return {
|
|
31
|
+
return {
|
|
32
|
+
values: [...new Set(values)],
|
|
33
|
+
dynamic: true
|
|
34
|
+
};
|
|
32
35
|
}
|
|
33
36
|
// TEMPLATE
|
|
34
37
|
if (typescript_1.default.isTemplateExpression(node)) {
|
|
35
|
-
let results = [node.head.text];
|
|
38
|
+
let results = node.head.text ? [node.head.text] : [""];
|
|
36
39
|
for (const span of node.templateSpans) {
|
|
37
40
|
const res = resolveNodeValue(span.expression, checker, paramMap, seen);
|
|
38
41
|
if (res.values.length) {
|
|
@@ -59,16 +62,18 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
59
62
|
let symbol = checker.getSymbolAtLocation(node);
|
|
60
63
|
if (!symbol)
|
|
61
64
|
return { values: [], dynamic: true };
|
|
65
|
+
// INLINE FIRST
|
|
66
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
67
|
+
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
68
|
+
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
62
71
|
// unwrap export
|
|
63
72
|
symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
|
|
64
73
|
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
65
74
|
symbol = checker.getAliasedSymbol(symbol);
|
|
66
75
|
}
|
|
67
|
-
// inline variable
|
|
68
76
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
69
|
-
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
70
|
-
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
71
|
-
}
|
|
72
77
|
if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
|
|
73
78
|
if (typescript_1.default.isStringLiteral(decl.initializer)) {
|
|
74
79
|
return {
|
|
@@ -78,12 +83,12 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
|
-
// UNION TYPES
|
|
82
86
|
const type = checker.getTypeAtLocation(node);
|
|
87
|
+
// UNION
|
|
83
88
|
if (type.isUnion()) {
|
|
84
89
|
const values = [];
|
|
85
90
|
for (const t of type.types) {
|
|
86
|
-
if (t.
|
|
91
|
+
if (t.flags & typescript_1.default.TypeFlags.StringLiteral) {
|
|
87
92
|
values.push(t.value);
|
|
88
93
|
}
|
|
89
94
|
}
|
|
@@ -91,13 +96,16 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
91
96
|
return { values, dynamic: false };
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
|
-
//
|
|
95
|
-
if (type.
|
|
96
|
-
return {
|
|
99
|
+
// STRING LITERAL TYPE
|
|
100
|
+
if (type.flags & typescript_1.default.TypeFlags.StringLiteral) {
|
|
101
|
+
return {
|
|
102
|
+
values: [type.value],
|
|
103
|
+
dynamic: false
|
|
104
|
+
};
|
|
97
105
|
}
|
|
98
106
|
return { values: [], dynamic: true };
|
|
99
107
|
}
|
|
100
|
-
// PROPERTY ACCESS
|
|
108
|
+
// PROPERTY ACCESS
|
|
101
109
|
if (typescript_1.default.isPropertyAccessExpression(node)) {
|
|
102
110
|
const symbol = checker.getSymbolAtLocation(node);
|
|
103
111
|
if (symbol) {
|
|
@@ -116,14 +124,12 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
|
-
// constant enum / readonly
|
|
120
127
|
const constantValue = checker.getConstantValue(node);
|
|
121
128
|
if (typeof constantValue === "string") {
|
|
122
129
|
return { values: [constantValue], dynamic: false };
|
|
123
130
|
}
|
|
124
|
-
// type fallback
|
|
125
131
|
const type = checker.getTypeAtLocation(node);
|
|
126
|
-
if (type.
|
|
132
|
+
if (type.flags & typescript_1.default.TypeFlags.StringLiteral) {
|
|
127
133
|
return {
|
|
128
134
|
values: [type.value],
|
|
129
135
|
dynamic: false,
|
|
@@ -149,16 +155,16 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
}
|
|
152
|
-
|
|
158
|
+
// fallback
|
|
159
|
+
return resolveNodeValue(node.name, checker, paramMap, seen);
|
|
153
160
|
}
|
|
154
|
-
// ARRAY ACCESS
|
|
161
|
+
// ARRAY ACCESS
|
|
155
162
|
if (typescript_1.default.isElementAccessExpression(node)) {
|
|
156
163
|
const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
|
|
157
164
|
if (!node.argumentExpression) {
|
|
158
165
|
return { values: obj.values, dynamic: true };
|
|
159
166
|
}
|
|
160
167
|
const index = resolveNodeValue(node.argumentExpression, checker, paramMap, seen);
|
|
161
|
-
// if index known
|
|
162
168
|
if (index.values.length === 1) {
|
|
163
169
|
const i = Number(index.values[0]);
|
|
164
170
|
if (!Number.isNaN(i) && obj.values[i] !== undefined) {
|
|
@@ -168,7 +174,6 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
168
174
|
};
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
|
-
// fallback → dynamic
|
|
172
177
|
return obj.values.length
|
|
173
178
|
? { values: obj.values, dynamic: true }
|
|
174
179
|
: { values: [], dynamic: true };
|
|
@@ -185,7 +190,10 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
185
190
|
values.push(...res.values);
|
|
186
191
|
}
|
|
187
192
|
}
|
|
188
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
values: [...new Set(values)],
|
|
195
|
+
dynamic: true
|
|
196
|
+
};
|
|
189
197
|
}
|
|
190
198
|
// BINARY ("btn_" + name)
|
|
191
199
|
if (typescript_1.default.isBinaryExpression(node)) {
|
package/dist/engine/scanner.js
CHANGED
|
@@ -10,7 +10,7 @@ const callResolver_1 = require("./callResolver");
|
|
|
10
10
|
const exportResolver_1 = require("./exportResolver");
|
|
11
11
|
const functionAnalyzer_1 = require("./functionAnalyzer");
|
|
12
12
|
const boundary_1 = require("./boundary");
|
|
13
|
-
const
|
|
13
|
+
const trackingDetector_1 = require("./trackingDetector");
|
|
14
14
|
function getCallName(expr) {
|
|
15
15
|
if (typescript_1.default.isIdentifier(expr))
|
|
16
16
|
return expr.text;
|
|
@@ -25,6 +25,16 @@ function getJsxAttrName(name) {
|
|
|
25
25
|
return name.name.text;
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
+
// unwrap
|
|
29
|
+
function unwrap(expr) {
|
|
30
|
+
while (typescript_1.default.isAsExpression(expr) ||
|
|
31
|
+
typescript_1.default.isParenthesizedExpression(expr) ||
|
|
32
|
+
typescript_1.default.isTypeAssertionExpression(expr) ||
|
|
33
|
+
typescript_1.default.isNonNullExpression(expr)) {
|
|
34
|
+
expr = expr.expression;
|
|
35
|
+
}
|
|
36
|
+
return expr;
|
|
37
|
+
}
|
|
28
38
|
function isValidEvent(v) {
|
|
29
39
|
return (v &&
|
|
30
40
|
v.length > 1 &&
|
|
@@ -32,70 +42,75 @@ function isValidEvent(v) {
|
|
|
32
42
|
!v.includes(" ") &&
|
|
33
43
|
/^[a-zA-Z0-9._:-]+$/.test(v));
|
|
34
44
|
}
|
|
35
|
-
function isPropUsedInTracking(fn, prop) {
|
|
45
|
+
function isPropUsedInTracking(fn, checker, prop) {
|
|
36
46
|
let found = false;
|
|
37
47
|
function visit(node) {
|
|
38
48
|
if (typescript_1.default.isCallExpression(node)) {
|
|
39
|
-
|
|
40
|
-
if (!TRACKING_NAMES.has(name)) {
|
|
49
|
+
if (!(0, trackingDetector_1.isTrackingCall)(node, checker)) {
|
|
41
50
|
typescript_1.default.forEachChild(node, visit);
|
|
42
51
|
return;
|
|
43
52
|
}
|
|
44
53
|
for (const arg of node.arguments) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const a = unwrap(arg);
|
|
55
|
+
if ((typescript_1.default.isIdentifier(a) && a.text === prop) ||
|
|
56
|
+
(typescript_1.default.isPropertyAccessExpression(a) &&
|
|
57
|
+
a.name.text === prop)) {
|
|
48
58
|
found = true;
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
typescript_1.default.forEachChild(node, visit);
|
|
53
63
|
}
|
|
54
|
-
|
|
64
|
+
if (fn.body) {
|
|
65
|
+
visit(fn.body);
|
|
66
|
+
}
|
|
55
67
|
return found;
|
|
56
68
|
}
|
|
57
69
|
function scanSource(source, checker, config, visited = new Set(), depth = 0, inheritedParamMap) {
|
|
58
70
|
const events = new Set();
|
|
59
71
|
const detectedFunctionWrappers = new Set();
|
|
60
72
|
const detectedComponentWrappers = new Map();
|
|
61
|
-
if (
|
|
73
|
+
if (visited.has(source.fileName) ||
|
|
74
|
+
depth > 10 ||
|
|
75
|
+
visited.size > 200) {
|
|
62
76
|
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
63
77
|
}
|
|
64
78
|
visited.add(source.fileName);
|
|
65
79
|
function visit(node) {
|
|
66
80
|
// CALL EXPRESSIONS
|
|
67
81
|
if (typescript_1.default.isCallExpression(node)) {
|
|
68
|
-
const
|
|
82
|
+
const expr = unwrap(node.expression);
|
|
83
|
+
const name = getCallName(expr);
|
|
69
84
|
const isKnownWrapper = config.functionWrappers.some(w => w.name === name);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!resolvedFn && typescript_1.default.isPropertyAccessExpression(node.expression)) {
|
|
73
|
-
const symbol = checker.getSymbolAtLocation(node.expression.name);
|
|
74
|
-
if (symbol) {
|
|
75
|
-
const decls = symbol.getDeclarations();
|
|
76
|
-
if (decls?.length) {
|
|
77
|
-
const d = decls[0];
|
|
78
|
-
if (typescript_1.default.isMethodDeclaration(d) ||
|
|
79
|
-
typescript_1.default.isFunctionDeclaration(d) ||
|
|
80
|
-
typescript_1.default.isFunctionExpression(d) ||
|
|
81
|
-
typescript_1.default.isArrowFunction(d)) {
|
|
82
|
-
resolvedFn = d;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
85
|
+
const isTrackingByType = (0, trackingDetector_1.isTrackingCall)(node, checker);
|
|
86
|
+
let resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(expr, checker);
|
|
87
87
|
const isAutoWrapper = resolvedFn && (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn) !== null;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
name.startsWith("$") ||
|
|
88
|
+
let isTracking = isKnownWrapper ||
|
|
89
|
+
isTrackingByType ||
|
|
90
|
+
name.startsWith("$") ||
|
|
91
91
|
(isAutoWrapper &&
|
|
92
92
|
resolvedFn &&
|
|
93
93
|
!(0, boundary_1.isExternalFile)(resolvedFn.getSourceFile().fileName));
|
|
94
|
+
// fallback
|
|
95
|
+
if (!isTracking) {
|
|
96
|
+
if (typescript_1.default.isPropertyAccessExpression(expr) &&
|
|
97
|
+
expr.name.text === "track") {
|
|
98
|
+
isTracking = true;
|
|
99
|
+
}
|
|
100
|
+
else if (typescript_1.default.isIdentifier(expr) &&
|
|
101
|
+
expr.text === "track") {
|
|
102
|
+
isTracking = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
94
105
|
if (!isTracking) {
|
|
95
106
|
typescript_1.default.forEachChild(node, visit);
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
98
|
-
|
|
109
|
+
// detect wrapper
|
|
110
|
+
if (!isKnownWrapper &&
|
|
111
|
+
isAutoWrapper &&
|
|
112
|
+
!isTrackingByType &&
|
|
113
|
+
!name.startsWith("track")) {
|
|
99
114
|
detectedFunctionWrappers.add(name);
|
|
100
115
|
}
|
|
101
116
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
@@ -114,26 +129,22 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
|
|
|
114
129
|
if (resolvedFn) {
|
|
115
130
|
const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
|
|
116
131
|
if (idx !== null && node.arguments[idx]) {
|
|
117
|
-
const res = (0, resolver_1.resolveNodeValue)(node.arguments[idx], checker, paramMap);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
}
|
|
132
|
+
const res = (0, resolver_1.resolveNodeValue)(unwrap(node.arguments[idx]), checker, paramMap);
|
|
133
|
+
res.values.forEach(v => {
|
|
134
|
+
if (isValidEvent(v))
|
|
135
|
+
events.add(v);
|
|
136
|
+
});
|
|
124
137
|
handled = true;
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
// FALLBACK
|
|
128
141
|
if (!handled) {
|
|
129
142
|
for (const arg of node.arguments) {
|
|
130
|
-
const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
}
|
|
143
|
+
const res = (0, resolver_1.resolveNodeValue)(unwrap(arg), checker, paramMap);
|
|
144
|
+
res.values.forEach(v => {
|
|
145
|
+
if (isValidEvent(v))
|
|
146
|
+
events.add(v);
|
|
147
|
+
});
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
// CROSS FILE
|
|
@@ -183,7 +194,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
|
|
|
183
194
|
continue;
|
|
184
195
|
for (const el of param.name.elements) {
|
|
185
196
|
const prop = el.name.getText();
|
|
186
|
-
const isEvent = isPropUsedInTracking(fn, prop) ||
|
|
197
|
+
const isEvent = isPropUsedInTracking(fn, checker, prop) ||
|
|
187
198
|
config.wrappers.some(w => w.name === tagName && w.prop === prop);
|
|
188
199
|
if (!isEvent)
|
|
189
200
|
continue;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isTrackingCall = isTrackingCall;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
function isStringLike(type) {
|
|
9
|
+
return ((type.flags & typescript_1.default.TypeFlags.StringLike) !== 0 ||
|
|
10
|
+
(type.flags & typescript_1.default.TypeFlags.StringLiteral) !== 0);
|
|
11
|
+
}
|
|
12
|
+
function hasTrackLikeSignature(type, checker) {
|
|
13
|
+
const sigs = type.getCallSignatures();
|
|
14
|
+
for (const sig of sigs) {
|
|
15
|
+
const params = sig.getParameters();
|
|
16
|
+
if (!params.length)
|
|
17
|
+
continue;
|
|
18
|
+
const t = checker.getTypeOfSymbolAtLocation(params[0], params[0].valueDeclaration);
|
|
19
|
+
if (isStringLike(t)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function isEventraLikeType(type, checker) {
|
|
26
|
+
const props = type.getProperties();
|
|
27
|
+
for (const prop of props) {
|
|
28
|
+
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
29
|
+
if (!decl)
|
|
30
|
+
continue;
|
|
31
|
+
const t = checker.getTypeOfSymbolAtLocation(prop, decl);
|
|
32
|
+
if (hasTrackLikeSignature(t, checker)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function isTrackingCall(node, checker) {
|
|
39
|
+
const expr = node.expression;
|
|
40
|
+
// fn(...)
|
|
41
|
+
if (typescript_1.default.isIdentifier(expr)) {
|
|
42
|
+
const type = checker.getTypeAtLocation(expr);
|
|
43
|
+
if (hasTrackLikeSignature(type, checker)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// obj.method(...)
|
|
48
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
49
|
+
const objType = checker.getTypeAtLocation(expr.expression);
|
|
50
|
+
// объект похож на tracker
|
|
51
|
+
if (isEventraLikeType(objType, checker)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// fallback
|
|
55
|
+
const methodType = checker.getTypeAtLocation(expr.name);
|
|
56
|
+
if (hasTrackLikeSignature(methodType, checker)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// obj["track"](...)
|
|
61
|
+
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
62
|
+
const objType = checker.getTypeAtLocation(expr.expression);
|
|
63
|
+
if (isEventraLikeType(objType, checker)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.processFile = processFile;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
4
|
const universal_1 = require("./universal");
|
|
9
5
|
function processFile(file, content) {
|
|
10
6
|
const { code, deps } = (0, universal_1.parseUniversal)(content, file);
|
|
11
|
-
const ext = path_1.default.extname(file);
|
|
12
|
-
const virtualFile = /\.(vue|svelte|astro|html)$/i.test(file)
|
|
13
|
-
? file.replace(ext, "") + ".tsx"
|
|
14
|
-
: file;
|
|
15
7
|
return {
|
|
16
8
|
content: code || "",
|
|
17
|
-
virtualFile,
|
|
18
9
|
deps: [...new Set(deps.filter(Boolean))],
|
|
19
10
|
};
|
|
20
11
|
}
|