@eventra_dev/eventra-cli 0.1.30 → 0.1.32
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 +0 -1
- package/dist/engine/callResolver.js +56 -25
- package/dist/engine/engine.js +7 -4
- package/dist/engine/languageService.js +36 -11
- package/dist/engine/scanner.js +224 -27
- package/dist/utils/config.js +0 -1
- package/dist/utils/processFile.js +3 -3
- package/dist/utils/universal.js +121 -53
- package/package.json +1 -1
- package/src/engine/callResolver.ts +61 -28
- package/src/engine/engine.ts +26 -7
- package/src/engine/languageService.ts +50 -16
- package/src/engine/scanner.ts +285 -42
- package/src/types.ts +6 -1
- package/src/utils/config.ts +0 -1
- package/src/utils/processFile.ts +3 -3
- package/src/utils/universal.ts +149 -77
- package/tests/run.ts +0 -1
- package/dist/engine/trackingDetector.js +0 -17
- package/src/engine/trackingDetector.ts +0 -17
package/README.md
CHANGED
|
@@ -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);
|
package/dist/engine/engine.js
CHANGED
|
@@ -14,6 +14,7 @@ const boundary_1 = require("./boundary");
|
|
|
14
14
|
class EventraEngine {
|
|
15
15
|
ts;
|
|
16
16
|
graph;
|
|
17
|
+
wrapperCache = new Map();
|
|
17
18
|
fileResults = new Map();
|
|
18
19
|
fileHash = new Map();
|
|
19
20
|
constructor(root) {
|
|
@@ -54,14 +55,15 @@ class EventraEngine {
|
|
|
54
55
|
if (!source || !checker) {
|
|
55
56
|
return this.empty();
|
|
56
57
|
}
|
|
57
|
-
//
|
|
58
|
+
// update graph
|
|
58
59
|
this.graph.update(file, source);
|
|
59
|
-
const res = (0, scanner_1.scanSource)(source, checker, config);
|
|
60
|
+
const res = (0, scanner_1.scanSource)(source, checker, config, new Set(), undefined, this.wrapperCache);
|
|
60
61
|
this.fileResults.set(file, res);
|
|
61
62
|
return res;
|
|
62
63
|
}
|
|
63
64
|
// UPDATE FILE (incremental)
|
|
64
65
|
updateFile(file, content, config) {
|
|
66
|
+
this.wrapperCache.clear();
|
|
65
67
|
file = this.normalize(file);
|
|
66
68
|
const res = this.scanFile(file, content, config);
|
|
67
69
|
const dependents = this.graph.getAllDependentsDeep(file);
|
|
@@ -89,7 +91,7 @@ class EventraEngine {
|
|
|
89
91
|
if (!source || !checker)
|
|
90
92
|
continue;
|
|
91
93
|
this.graph.update(abs, source);
|
|
92
|
-
const r = (0, scanner_1.scanSource)(source, checker, config);
|
|
94
|
+
const r = (0, scanner_1.scanSource)(source, checker, config, new Set(), undefined, this.wrapperCache);
|
|
93
95
|
this.fileResults.set(abs, r);
|
|
94
96
|
}
|
|
95
97
|
return res;
|
|
@@ -104,6 +106,7 @@ class EventraEngine {
|
|
|
104
106
|
}
|
|
105
107
|
// REMOVE FILE
|
|
106
108
|
removeFile(file, config) {
|
|
109
|
+
this.wrapperCache.clear();
|
|
107
110
|
file = this.normalize(file);
|
|
108
111
|
const dependents = this.graph.getAllDependentsDeep(file);
|
|
109
112
|
this.ts.removeFile(file);
|
|
@@ -138,7 +141,7 @@ class EventraEngine {
|
|
|
138
141
|
wrappers: [],
|
|
139
142
|
functionWrappers: [],
|
|
140
143
|
sync: { include: [], exclude: [] },
|
|
141
|
-
});
|
|
144
|
+
}, new Set(), undefined, this.wrapperCache);
|
|
142
145
|
this.fileResults.set(abs, r);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
@@ -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
|
@@ -8,7 +8,23 @@ const typescript_1 = __importDefault(require("typescript"));
|
|
|
8
8
|
const resolver_1 = require("./resolver");
|
|
9
9
|
const callResolver_1 = require("./callResolver");
|
|
10
10
|
const functionAnalyzer_1 = require("./functionAnalyzer");
|
|
11
|
-
const
|
|
11
|
+
const exportResolver_1 = require("./exportResolver");
|
|
12
|
+
// UTILS
|
|
13
|
+
function getJsxAttrName(name) {
|
|
14
|
+
if (typescript_1.default.isIdentifier(name))
|
|
15
|
+
return name.text;
|
|
16
|
+
if (typescript_1.default.isJsxNamespacedName(name))
|
|
17
|
+
return name.name.text;
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
function getJsxTagName(tag) {
|
|
21
|
+
if (typescript_1.default.isIdentifier(tag))
|
|
22
|
+
return tag.text;
|
|
23
|
+
if (typescript_1.default.isPropertyAccessExpression(tag)) {
|
|
24
|
+
return tag.name.text;
|
|
25
|
+
}
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
12
28
|
function getCallName(expr) {
|
|
13
29
|
if (typescript_1.default.isIdentifier(expr))
|
|
14
30
|
return expr.text;
|
|
@@ -23,33 +39,219 @@ function isValidEvent(v) {
|
|
|
23
39
|
!v.includes(" ") &&
|
|
24
40
|
/^[a-zA-Z0-9._:-]+$/.test(v));
|
|
25
41
|
}
|
|
26
|
-
|
|
42
|
+
// COMPONENT ANALYSIS
|
|
43
|
+
function getComponentPropsMap(fn) {
|
|
44
|
+
const map = new Map();
|
|
45
|
+
const firstParam = fn.parameters[0];
|
|
46
|
+
if (!firstParam)
|
|
47
|
+
return map;
|
|
48
|
+
// ({ event }) => ...
|
|
49
|
+
if (typescript_1.default.isObjectBindingPattern(firstParam.name)) {
|
|
50
|
+
for (const el of firstParam.name.elements) {
|
|
51
|
+
if (!typescript_1.default.isBindingElement(el))
|
|
52
|
+
continue;
|
|
53
|
+
const propName = el.propertyName?.getText() || el.name.getText();
|
|
54
|
+
map.set(el.name.getText(), propName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// (props) => props.event
|
|
58
|
+
if (typescript_1.default.isIdentifier(firstParam.name)) {
|
|
59
|
+
map.set(firstParam.name.text, "*");
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
function findTrackedPropName(fn, checker) {
|
|
64
|
+
const propMap = getComponentPropsMap(fn);
|
|
65
|
+
let result = null;
|
|
66
|
+
function resolveIdentifierToProp(node) {
|
|
67
|
+
const mapped = propMap.get(node.text);
|
|
68
|
+
if (mapped && mapped !== "*")
|
|
69
|
+
return mapped;
|
|
70
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
71
|
+
if (!symbol)
|
|
72
|
+
return null;
|
|
73
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
74
|
+
// const e = props.event
|
|
75
|
+
if (typescript_1.default.isVariableDeclaration(decl) &&
|
|
76
|
+
decl.initializer &&
|
|
77
|
+
typescript_1.default.isPropertyAccessExpression(decl.initializer)) {
|
|
78
|
+
return decl.initializer.name.text;
|
|
79
|
+
}
|
|
80
|
+
// const e = event
|
|
81
|
+
if (typescript_1.default.isVariableDeclaration(decl) &&
|
|
82
|
+
decl.initializer &&
|
|
83
|
+
typescript_1.default.isIdentifier(decl.initializer)) {
|
|
84
|
+
const inner = propMap.get(decl.initializer.text);
|
|
85
|
+
if (inner)
|
|
86
|
+
return inner;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function visit(node) {
|
|
92
|
+
if (result)
|
|
93
|
+
return;
|
|
94
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
95
|
+
const expr = node.expression;
|
|
96
|
+
const isTrack = (typescript_1.default.isIdentifier(expr) && expr.text === "track") ||
|
|
97
|
+
(typescript_1.default.isPropertyAccessExpression(expr) &&
|
|
98
|
+
expr.name.text === "track");
|
|
99
|
+
if (!isTrack) {
|
|
100
|
+
typescript_1.default.forEachChild(node, visit);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const arg of node.arguments) {
|
|
104
|
+
// track(event)
|
|
105
|
+
if (typescript_1.default.isIdentifier(arg)) {
|
|
106
|
+
const prop = resolveIdentifierToProp(arg);
|
|
107
|
+
if (prop) {
|
|
108
|
+
result = prop;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// track(props.a.b.event)
|
|
113
|
+
if (typescript_1.default.isPropertyAccessExpression(arg)) {
|
|
114
|
+
let current = arg;
|
|
115
|
+
while (typescript_1.default.isPropertyAccessExpression(current)) {
|
|
116
|
+
result = current.name.text;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// track(fn())
|
|
121
|
+
if (typescript_1.default.isCallExpression(arg)) {
|
|
122
|
+
const inner = (0, callResolver_1.resolveFunctionFromCall)(arg.expression, checker);
|
|
123
|
+
if (inner?.body) {
|
|
124
|
+
visit(inner.body);
|
|
125
|
+
if (result)
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
typescript_1.default.forEachChild(node, visit);
|
|
132
|
+
}
|
|
133
|
+
if (fn.body)
|
|
134
|
+
visit(fn.body);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
// MAIN SCANNER
|
|
138
|
+
function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
|
|
27
139
|
const events = new Set();
|
|
28
140
|
const detectedFunctionWrappers = new Set();
|
|
29
141
|
const detectedComponentWrappers = new Map();
|
|
30
|
-
if (visited.
|
|
142
|
+
if (visited.has(source.fileName) || visited.size > 200) {
|
|
31
143
|
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
32
144
|
}
|
|
33
145
|
visited.add(source.fileName);
|
|
34
146
|
function visit(node) {
|
|
147
|
+
// JSX WRAPPERS
|
|
148
|
+
if (typescript_1.default.isJsxSelfClosingElement(node) || typescript_1.default.isJsxOpeningElement(node)) {
|
|
149
|
+
const tagName = getJsxTagName(node.tagName);
|
|
150
|
+
// skip html
|
|
151
|
+
if (tagName[0] === tagName[0].toLowerCase()) {
|
|
152
|
+
typescript_1.default.forEachChild(node, visit);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let wrapper = config.wrappers.find(w => w.name === tagName);
|
|
156
|
+
// AUTO DETECT
|
|
157
|
+
if (!wrapper) {
|
|
158
|
+
if (wrapperCache.has(tagName)) {
|
|
159
|
+
const cached = wrapperCache.get(tagName);
|
|
160
|
+
if (cached)
|
|
161
|
+
wrapper = { name: tagName, prop: cached };
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
let symbol = checker.getSymbolAtLocation(node.tagName);
|
|
165
|
+
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
166
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
167
|
+
}
|
|
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
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (wrapper) {
|
|
194
|
+
for (const attr of node.attributes.properties) {
|
|
195
|
+
if (!typescript_1.default.isJsxAttribute(attr))
|
|
196
|
+
continue;
|
|
197
|
+
const name = getJsxAttrName(attr.name);
|
|
198
|
+
if (name !== wrapper.prop)
|
|
199
|
+
continue;
|
|
200
|
+
if (!attr.initializer)
|
|
201
|
+
continue;
|
|
202
|
+
if (typescript_1.default.isStringLiteral(attr.initializer)) {
|
|
203
|
+
const v = attr.initializer.text;
|
|
204
|
+
if (isValidEvent(v))
|
|
205
|
+
events.add(v);
|
|
206
|
+
}
|
|
207
|
+
if (typescript_1.default.isJsxExpression(attr.initializer)) {
|
|
208
|
+
const expr = attr.initializer.expression;
|
|
209
|
+
if (!expr)
|
|
210
|
+
continue;
|
|
211
|
+
const res = (0, resolver_1.resolveNodeValue)(expr, checker, inheritedParamMap);
|
|
212
|
+
res.values.forEach(v => {
|
|
213
|
+
if (isValidEvent(v))
|
|
214
|
+
events.add(v);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
typescript_1.default.forEachChild(node, visit);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// FUNCTION CALLS
|
|
35
223
|
if (typescript_1.default.isCallExpression(node)) {
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
38
240
|
const resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
!
|
|
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
|
+
}
|
|
45
251
|
if (!isTracking) {
|
|
46
252
|
typescript_1.default.forEachChild(node, visit);
|
|
47
253
|
return;
|
|
48
254
|
}
|
|
49
|
-
// авто-детект wrapper
|
|
50
|
-
if (!isKnownWrapper && isAutoWrapper && name !== "track") {
|
|
51
|
-
detectedFunctionWrappers.add(name);
|
|
52
|
-
}
|
|
53
255
|
const paramMap = new Map(inheritedParamMap ?? []);
|
|
54
256
|
if (resolvedFn && typescript_1.default.isFunctionLike(resolvedFn)) {
|
|
55
257
|
resolvedFn.parameters.forEach((param, i) => {
|
|
@@ -61,23 +263,18 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
|
|
|
61
263
|
}
|
|
62
264
|
});
|
|
63
265
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (isValidEvent(v)) {
|
|
71
|
-
events.add(v);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
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
|
+
});
|
|
75
272
|
}
|
|
76
273
|
// CROSS FILE
|
|
77
274
|
if (resolvedFn) {
|
|
78
275
|
const sf = resolvedFn.getSourceFile();
|
|
79
276
|
if (!visited.has(sf.fileName)) {
|
|
80
|
-
const res = scanSource(sf, checker, config, visited, paramMap);
|
|
277
|
+
const res = scanSource(sf, checker, config, visited, paramMap, wrapperCache);
|
|
81
278
|
res.events.forEach(e => events.add(e));
|
|
82
279
|
}
|
|
83
280
|
}
|
package/dist/utils/config.js
CHANGED
|
@@ -17,7 +17,6 @@ function normalizeConfig(config) {
|
|
|
17
17
|
endpoint: config.endpoint ?? "",
|
|
18
18
|
wrappers: config.wrappers ?? [],
|
|
19
19
|
functionWrappers: config.functionWrappers ?? [],
|
|
20
|
-
aliases: config.aliases ?? {},
|
|
21
20
|
sync: config.sync ?? {
|
|
22
21
|
include: [
|
|
23
22
|
"**/*.{ts,tsx,js,jsx,vue,svelte,astro}"
|
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.processFile = processFile;
|
|
4
4
|
const universal_1 = require("./universal");
|
|
5
5
|
function processFile(file, content) {
|
|
6
|
-
const
|
|
6
|
+
const parsed = (0, universal_1.parseUniversal)(content, file);
|
|
7
7
|
return {
|
|
8
|
-
content: code || "",
|
|
9
|
-
deps: [...new Set(deps.filter(Boolean))],
|
|
8
|
+
content: parsed.code || "",
|
|
9
|
+
deps: [...new Set(parsed.deps.filter(Boolean))],
|
|
10
10
|
};
|
|
11
11
|
}
|