@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
package/dist/commands/watch.js
CHANGED
|
@@ -13,6 +13,9 @@ const config_1 = require("../utils/config");
|
|
|
13
13
|
const engine_1 = require("../engine/engine");
|
|
14
14
|
const processFile_1 = require("../utils/processFile");
|
|
15
15
|
const getVirtualFile_1 = require("../utils/getVirtualFile");
|
|
16
|
+
function normalize(file) {
|
|
17
|
+
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
18
|
+
}
|
|
16
19
|
async function watch() {
|
|
17
20
|
const config = await (0, config_1.loadConfig)();
|
|
18
21
|
if (!config)
|
|
@@ -29,28 +32,29 @@ async function watch() {
|
|
|
29
32
|
// COLLECT + CACHE
|
|
30
33
|
for (const file of files) {
|
|
31
34
|
try {
|
|
32
|
-
const
|
|
35
|
+
const abs = normalize(file);
|
|
36
|
+
const raw = await promises_1.default.readFile(abs, "utf-8");
|
|
33
37
|
if (raw.length > 2_000_000)
|
|
34
|
-
continue;
|
|
35
|
-
const parsed = (0, processFile_1.processFile)(
|
|
36
|
-
cache.set(
|
|
37
|
-
toScan.add(
|
|
38
|
+
continue;
|
|
39
|
+
const parsed = (0, processFile_1.processFile)(abs, raw);
|
|
40
|
+
cache.set(abs, parsed);
|
|
41
|
+
toScan.add(abs);
|
|
38
42
|
for (const dep of parsed.deps) {
|
|
39
|
-
|
|
40
|
-
toScan.add(absDep);
|
|
43
|
+
toScan.add(normalize(dep));
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
catch {
|
|
44
47
|
console.log(chalk_1.default.gray(`skip: ${file}`));
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
|
-
// PRELOAD
|
|
50
|
+
// PRELOAD
|
|
48
51
|
for (const file of toScan) {
|
|
49
52
|
try {
|
|
50
53
|
let parsed = cache.get(file);
|
|
51
54
|
if (!parsed) {
|
|
52
55
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
53
56
|
parsed = (0, processFile_1.processFile)(file, raw);
|
|
57
|
+
cache.set(file, parsed);
|
|
54
58
|
}
|
|
55
59
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
|
|
56
60
|
engine.preloadFile?.(virtual, parsed.content);
|
|
@@ -68,7 +72,7 @@ async function watch() {
|
|
|
68
72
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
|
|
69
73
|
engine.scanFile(virtual, parsed.content, config);
|
|
70
74
|
for (const dep of parsed.deps) {
|
|
71
|
-
watchedDeps.add(
|
|
75
|
+
watchedDeps.add(normalize(dep));
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
catch { }
|
|
@@ -94,19 +98,33 @@ async function watch() {
|
|
|
94
98
|
const parsed = (0, processFile_1.processFile)(item.real, raw);
|
|
95
99
|
cache.set(item.real, parsed);
|
|
96
100
|
engine.updateFile(item.virtual, parsed.content, config);
|
|
97
|
-
//
|
|
101
|
+
// DEPENDENCY DIFF
|
|
102
|
+
const nextDeps = new Set();
|
|
98
103
|
for (const dep of parsed.deps) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
nextDeps.add(normalize(dep));
|
|
105
|
+
}
|
|
106
|
+
// add new deps
|
|
107
|
+
for (const dep of nextDeps) {
|
|
108
|
+
if (!watchedDeps.has(dep)) {
|
|
109
|
+
watcher.add(dep);
|
|
110
|
+
watchedDeps.add(dep);
|
|
103
111
|
}
|
|
112
|
+
}
|
|
113
|
+
// remove old deps
|
|
114
|
+
for (const dep of [...watchedDeps]) {
|
|
115
|
+
if (!nextDeps.has(dep)) {
|
|
116
|
+
watcher.unwatch(dep);
|
|
117
|
+
watchedDeps.delete(dep);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// preload deps
|
|
121
|
+
for (const dep of nextDeps) {
|
|
104
122
|
try {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
cache.set(
|
|
108
|
-
const
|
|
109
|
-
engine.updateFile(
|
|
123
|
+
const raw = await promises_1.default.readFile(dep, "utf-8");
|
|
124
|
+
const parsedDep = (0, processFile_1.processFile)(dep, raw);
|
|
125
|
+
cache.set(dep, parsedDep);
|
|
126
|
+
const virtual = (0, getVirtualFile_1.getVirtualFile)(dep);
|
|
127
|
+
engine.updateFile(virtual, parsedDep.content, config);
|
|
110
128
|
}
|
|
111
129
|
catch { }
|
|
112
130
|
}
|
|
@@ -130,13 +148,13 @@ async function watch() {
|
|
|
130
148
|
};
|
|
131
149
|
// SCHEDULER
|
|
132
150
|
const schedule = (file) => {
|
|
133
|
-
const abs =
|
|
151
|
+
const abs = normalize(file);
|
|
134
152
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
|
|
135
153
|
console.log(chalk_1.default.gray(`change: ${file}`));
|
|
136
154
|
queued.set(abs, { real: abs, virtual });
|
|
137
155
|
if (timer)
|
|
138
156
|
clearTimeout(timer);
|
|
139
|
-
timer = setTimeout(run,
|
|
157
|
+
timer = setTimeout(run, 200);
|
|
140
158
|
};
|
|
141
159
|
// WATCHER
|
|
142
160
|
const watcher = chokidar_1.default.watch(config.sync.include, {
|
|
@@ -148,7 +166,7 @@ async function watch() {
|
|
|
148
166
|
.on("add", schedule)
|
|
149
167
|
.on("change", schedule)
|
|
150
168
|
.on("unlink", async (file) => {
|
|
151
|
-
const abs =
|
|
169
|
+
const abs = normalize(file);
|
|
152
170
|
console.log(chalk_1.default.gray(`remove: ${file}`));
|
|
153
171
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
|
|
154
172
|
engine.removeFile(virtual, config);
|
|
@@ -162,7 +180,7 @@ async function watch() {
|
|
|
162
180
|
.on("error", (err) => {
|
|
163
181
|
console.error(chalk_1.default.red("Watcher error:"), err);
|
|
164
182
|
});
|
|
165
|
-
// WATCH DEPS
|
|
183
|
+
// WATCH DEPS INIT
|
|
166
184
|
for (const dep of watchedDeps) {
|
|
167
185
|
watcher.add(dep);
|
|
168
186
|
}
|
|
@@ -23,15 +23,37 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
23
23
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
24
24
|
return resolveFromSymbol(aliased, checker, visited, depth + 1);
|
|
25
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;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
26
34
|
const decls = symbol.getDeclarations() ?? [];
|
|
27
35
|
for (const decl of decls) {
|
|
36
|
+
// direct function
|
|
28
37
|
if (isFunctionLike(decl)) {
|
|
29
38
|
return decl;
|
|
30
39
|
}
|
|
31
|
-
//
|
|
40
|
+
// VARIABLE → FUNCTION
|
|
41
|
+
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
42
|
+
if (isFunctionLike(decl.initializer)) {
|
|
43
|
+
return decl.initializer;
|
|
44
|
+
}
|
|
45
|
+
const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
|
|
46
|
+
if (innerSymbol && innerSymbol !== symbol) {
|
|
47
|
+
const resolved = resolveFromSymbol(innerSymbol, checker, visited, depth + 1);
|
|
48
|
+
if (resolved)
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// MethodSignature (interface / type)
|
|
32
53
|
if (typescript_1.default.isMethodSignature(decl)) {
|
|
33
54
|
const parent = decl.parent;
|
|
34
|
-
if (typescript_1.default.isInterfaceDeclaration(parent) ||
|
|
55
|
+
if (typescript_1.default.isInterfaceDeclaration(parent) ||
|
|
56
|
+
typescript_1.default.isTypeLiteralNode(parent)) {
|
|
35
57
|
const name = decl.name.getText();
|
|
36
58
|
const type = checker.getTypeAtLocation(parent);
|
|
37
59
|
const props = type.getProperties();
|
|
@@ -44,7 +66,7 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
44
66
|
}
|
|
45
67
|
}
|
|
46
68
|
}
|
|
47
|
-
//
|
|
69
|
+
// fallback recursion
|
|
48
70
|
const s = checker.getSymbolAtLocation(decl);
|
|
49
71
|
if (s && s !== symbol) {
|
|
50
72
|
const resolved = resolveFromSymbol(s, checker, visited, depth + 1);
|
|
@@ -55,6 +77,28 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
55
77
|
return null;
|
|
56
78
|
}
|
|
57
79
|
function resolveFunctionFromCall(expr, checker) {
|
|
80
|
+
// tracker.track
|
|
81
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
82
|
+
let symbol = checker.getSymbolAtLocation(expr.name);
|
|
83
|
+
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
84
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
85
|
+
}
|
|
86
|
+
if (symbol) {
|
|
87
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
88
|
+
if (resolved)
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// tracker["track"]
|
|
93
|
+
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
94
|
+
const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
|
|
95
|
+
if (symbol) {
|
|
96
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
97
|
+
if (resolved)
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// normal call signatures
|
|
58
102
|
const type = checker.getTypeAtLocation(expr);
|
|
59
103
|
const signatures = type.getCallSignatures();
|
|
60
104
|
for (const sig of signatures) {
|
|
@@ -64,7 +108,6 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
64
108
|
if (isFunctionLike(decl)) {
|
|
65
109
|
return decl;
|
|
66
110
|
}
|
|
67
|
-
// interface method
|
|
68
111
|
if (typescript_1.default.isMethodSignature(decl)) {
|
|
69
112
|
const symbol = checker.getSymbolAtLocation(decl.name);
|
|
70
113
|
if (symbol) {
|
|
@@ -74,5 +117,10 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
74
117
|
}
|
|
75
118
|
}
|
|
76
119
|
}
|
|
120
|
+
// fallback
|
|
121
|
+
const symbol = checker.getSymbolAtLocation(expr);
|
|
122
|
+
if (symbol) {
|
|
123
|
+
return resolveFromSymbol(symbol, checker);
|
|
124
|
+
}
|
|
77
125
|
return null;
|
|
78
126
|
}
|
package/dist/engine/engine.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.EventraEngine = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
4
9
|
const languageService_1 = require("./languageService");
|
|
5
10
|
const scanner_1 = require("./scanner");
|
|
6
11
|
const graph_1 = require("./graph");
|
|
@@ -16,31 +21,40 @@ class EventraEngine {
|
|
|
16
21
|
const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
|
|
17
22
|
this.graph = new graph_1.DependencyGraph(baseUrl, paths);
|
|
18
23
|
}
|
|
24
|
+
normalize(file) {
|
|
25
|
+
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
26
|
+
}
|
|
27
|
+
empty() {
|
|
28
|
+
return {
|
|
29
|
+
events: new Set(),
|
|
30
|
+
detectedFunctionWrappers: new Set(),
|
|
31
|
+
detectedComponentWrappers: new Map(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// PRELOAD
|
|
19
35
|
preloadFile(file, content) {
|
|
36
|
+
file = this.normalize(file);
|
|
20
37
|
this.ts.updateFile(file, content);
|
|
38
|
+
const source = this.ts.getSourceFile(file);
|
|
39
|
+
if (source) {
|
|
40
|
+
this.graph.update(file, source);
|
|
41
|
+
}
|
|
21
42
|
}
|
|
22
43
|
// SCAN FILE
|
|
23
44
|
scanFile(file, content, config) {
|
|
45
|
+
file = this.normalize(file);
|
|
24
46
|
const h = (0, hash_1.hash)(content);
|
|
25
|
-
// skip unchanged
|
|
26
47
|
if (this.fileHash.get(file) === h) {
|
|
27
|
-
return
|
|
28
|
-
events: new Set(),
|
|
29
|
-
detectedFunctionWrappers: new Set(),
|
|
30
|
-
detectedComponentWrappers: new Map(),
|
|
31
|
-
});
|
|
48
|
+
return this.fileResults.get(file) ?? this.empty();
|
|
32
49
|
}
|
|
33
50
|
this.fileHash.set(file, h);
|
|
34
51
|
this.ts.updateFile(file, content);
|
|
35
52
|
const source = this.ts.getSourceFile(file);
|
|
36
53
|
const checker = this.ts.getChecker();
|
|
37
54
|
if (!source || !checker) {
|
|
38
|
-
return
|
|
39
|
-
events: new Set(),
|
|
40
|
-
detectedFunctionWrappers: new Set(),
|
|
41
|
-
detectedComponentWrappers: new Map(),
|
|
42
|
-
};
|
|
55
|
+
return this.empty();
|
|
43
56
|
}
|
|
57
|
+
// обновляем graph
|
|
44
58
|
this.graph.update(file, source);
|
|
45
59
|
const res = (0, scanner_1.scanSource)(source, checker, config);
|
|
46
60
|
this.fileResults.set(file, res);
|
|
@@ -48,26 +62,35 @@ class EventraEngine {
|
|
|
48
62
|
}
|
|
49
63
|
// UPDATE FILE (incremental)
|
|
50
64
|
updateFile(file, content, config) {
|
|
65
|
+
file = this.normalize(file);
|
|
51
66
|
const res = this.scanFile(file, content, config);
|
|
52
67
|
const dependents = this.graph.getAllDependentsDeep(file);
|
|
53
68
|
for (const dep of dependents) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const depContent = this.ts.getFileContent(dep);
|
|
57
|
-
if (!depContent)
|
|
69
|
+
const abs = this.normalize(dep);
|
|
70
|
+
if ((0, boundary_1.isExternalFile)(abs))
|
|
58
71
|
continue;
|
|
72
|
+
let depContent = this.ts.getFileContent(abs);
|
|
73
|
+
if (!depContent) {
|
|
74
|
+
try {
|
|
75
|
+
depContent = fs_1.default.readFileSync(abs, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
59
81
|
const h = (0, hash_1.hash)(depContent);
|
|
60
|
-
if (this.fileHash.get(
|
|
82
|
+
if (this.fileHash.get(abs) === h) {
|
|
61
83
|
continue;
|
|
62
|
-
|
|
63
|
-
this.
|
|
64
|
-
|
|
84
|
+
}
|
|
85
|
+
this.fileHash.set(abs, h);
|
|
86
|
+
this.ts.updateFile(abs, depContent);
|
|
87
|
+
const source = this.ts.getSourceFile(abs);
|
|
65
88
|
const checker = this.ts.getChecker();
|
|
66
89
|
if (!source || !checker)
|
|
67
90
|
continue;
|
|
68
|
-
this.graph.update(
|
|
91
|
+
this.graph.update(abs, source);
|
|
69
92
|
const r = (0, scanner_1.scanSource)(source, checker, config);
|
|
70
|
-
this.fileResults.set(
|
|
93
|
+
this.fileResults.set(abs, r);
|
|
71
94
|
}
|
|
72
95
|
return res;
|
|
73
96
|
}
|
|
@@ -81,33 +104,42 @@ class EventraEngine {
|
|
|
81
104
|
}
|
|
82
105
|
// REMOVE FILE
|
|
83
106
|
removeFile(file, config) {
|
|
107
|
+
file = this.normalize(file);
|
|
108
|
+
const dependents = this.graph.getAllDependentsDeep(file);
|
|
84
109
|
this.ts.removeFile(file);
|
|
85
110
|
this.fileResults.delete(file);
|
|
86
111
|
this.fileHash.delete(file);
|
|
87
112
|
this.graph.remove(file);
|
|
88
|
-
const dependents = this.graph.getAllDependentsDeep(file);
|
|
89
113
|
for (const dep of dependents) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const content = this.ts.getFileContent(dep);
|
|
93
|
-
if (!content)
|
|
114
|
+
const abs = this.normalize(dep);
|
|
115
|
+
if ((0, boundary_1.isExternalFile)(abs))
|
|
94
116
|
continue;
|
|
117
|
+
let content = this.ts.getFileContent(abs);
|
|
118
|
+
if (!content) {
|
|
119
|
+
try {
|
|
120
|
+
content = fs_1.default.readFileSync(abs, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
95
126
|
const h = (0, hash_1.hash)(content);
|
|
96
|
-
if (this.fileHash.get(
|
|
127
|
+
if (this.fileHash.get(abs) === h)
|
|
97
128
|
continue;
|
|
98
|
-
this.fileHash.set(
|
|
99
|
-
this.ts.updateFile(
|
|
100
|
-
const source = this.ts.getSourceFile(
|
|
129
|
+
this.fileHash.set(abs, h);
|
|
130
|
+
this.ts.updateFile(abs, content);
|
|
131
|
+
const source = this.ts.getSourceFile(abs);
|
|
101
132
|
const checker = this.ts.getChecker();
|
|
102
133
|
if (!source || !checker)
|
|
103
134
|
continue;
|
|
135
|
+
this.graph.update(abs, source);
|
|
104
136
|
const r = (0, scanner_1.scanSource)(source, checker, config ?? {
|
|
105
137
|
events: [],
|
|
106
138
|
wrappers: [],
|
|
107
139
|
functionWrappers: [],
|
|
108
140
|
sync: { include: [], exclude: [] },
|
|
109
141
|
});
|
|
110
|
-
this.fileResults.set(
|
|
142
|
+
this.fileResults.set(abs, r);
|
|
111
143
|
}
|
|
112
144
|
}
|
|
113
145
|
}
|
|
@@ -5,21 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveExportedSymbol = resolveExportedSymbol;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
function isRealDeclaration(node) {
|
|
9
|
+
return (typescript_1.default.isFunctionDeclaration(node) ||
|
|
10
|
+
typescript_1.default.isVariableDeclaration(node) ||
|
|
11
|
+
typescript_1.default.isClassDeclaration(node));
|
|
12
|
+
}
|
|
8
13
|
function resolveExportedSymbol(symbol, checker, visited = new Set()) {
|
|
9
14
|
if (visited.has(symbol))
|
|
10
15
|
return null;
|
|
11
16
|
visited.add(symbol);
|
|
17
|
+
// unwrap alias
|
|
12
18
|
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
13
19
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
14
20
|
return resolveExportedSymbol(aliased, checker, visited);
|
|
15
21
|
}
|
|
16
|
-
|
|
22
|
+
// export symbol
|
|
23
|
+
const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
|
|
24
|
+
if (exportSymbol && exportSymbol !== symbol) {
|
|
25
|
+
return resolveExportedSymbol(exportSymbol, checker, visited);
|
|
26
|
+
}
|
|
27
|
+
const decls = symbol.getDeclarations() ?? [];
|
|
28
|
+
if (decls.some(isRealDeclaration)) {
|
|
17
29
|
return symbol;
|
|
18
30
|
}
|
|
19
|
-
|
|
20
|
-
for (const decl of
|
|
31
|
+
// exports (re-export)
|
|
32
|
+
for (const decl of decls) {
|
|
21
33
|
const source = decl.getSourceFile();
|
|
22
|
-
const moduleSymbol =
|
|
34
|
+
const moduleSymbol = source.symbol;
|
|
23
35
|
if (!moduleSymbol)
|
|
24
36
|
continue;
|
|
25
37
|
const exports = checker.getExportsOfModule(moduleSymbol);
|
|
@@ -10,19 +10,81 @@ function findTrackParamIndex(fn) {
|
|
|
10
10
|
fn.parameters.forEach((p, i) => {
|
|
11
11
|
if (typescript_1.default.isIdentifier(p.name)) {
|
|
12
12
|
paramIndex.set(p.name.text, i);
|
|
13
|
+
// default param: (name = "x")
|
|
14
|
+
if (p.initializer && typescript_1.default.isStringLiteral(p.initializer)) {
|
|
15
|
+
if (i === 0)
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
13
18
|
}
|
|
14
19
|
});
|
|
15
20
|
let result = null;
|
|
21
|
+
function unwrap(node) {
|
|
22
|
+
// as / parentheses
|
|
23
|
+
while (typescript_1.default.isAsExpression(node) ||
|
|
24
|
+
typescript_1.default.isParenthesizedExpression(node)) {
|
|
25
|
+
node = node.expression;
|
|
26
|
+
}
|
|
27
|
+
return node;
|
|
28
|
+
}
|
|
29
|
+
function checkArg(arg) {
|
|
30
|
+
arg = unwrap(arg);
|
|
31
|
+
// identifier → param
|
|
32
|
+
if (typescript_1.default.isIdentifier(arg)) {
|
|
33
|
+
const idx = paramIndex.get(arg.text);
|
|
34
|
+
if (idx !== undefined) {
|
|
35
|
+
result = idx;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// string literal
|
|
40
|
+
if (typescript_1.default.isStringLiteral(arg)) {
|
|
41
|
+
result = 0;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// template `${name}`
|
|
45
|
+
if (typescript_1.default.isTemplateExpression(arg)) {
|
|
46
|
+
for (const span of arg.templateSpans) {
|
|
47
|
+
if (typescript_1.default.isIdentifier(span.expression)) {
|
|
48
|
+
const idx = paramIndex.get(span.expression.text);
|
|
49
|
+
if (idx !== undefined) {
|
|
50
|
+
result = idx;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
result = 0;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// object { event: name }
|
|
59
|
+
if (typescript_1.default.isObjectLiteralExpression(arg)) {
|
|
60
|
+
for (const prop of arg.properties) {
|
|
61
|
+
if (!typescript_1.default.isPropertyAssignment(prop))
|
|
62
|
+
continue;
|
|
63
|
+
const name = prop.name.getText();
|
|
64
|
+
if (["event", "name", "type"].includes(name)) {
|
|
65
|
+
return checkArg(prop.initializer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// call: String(name)
|
|
70
|
+
if (typescript_1.default.isCallExpression(arg)) {
|
|
71
|
+
for (const inner of arg.arguments) {
|
|
72
|
+
if (checkArg(inner))
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
16
78
|
function visit(node) {
|
|
79
|
+
if (result !== null)
|
|
80
|
+
return;
|
|
17
81
|
if (typescript_1.default.isCallExpression(node)) {
|
|
18
82
|
let isTrackCall = false;
|
|
19
83
|
if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
|
|
20
|
-
|
|
21
|
-
if (method === "track") {
|
|
84
|
+
if (node.expression.name.text === "track") {
|
|
22
85
|
isTrackCall = true;
|
|
23
86
|
}
|
|
24
87
|
}
|
|
25
|
-
// fallback
|
|
26
88
|
if (typescript_1.default.isIdentifier(node.expression)) {
|
|
27
89
|
if (node.expression.text === "track") {
|
|
28
90
|
isTrackCall = true;
|
|
@@ -30,17 +92,8 @@ function findTrackParamIndex(fn) {
|
|
|
30
92
|
}
|
|
31
93
|
if (isTrackCall) {
|
|
32
94
|
for (const arg of node.arguments) {
|
|
33
|
-
if (
|
|
34
|
-
const idx = paramIndex.get(arg.text);
|
|
35
|
-
if (idx !== undefined) {
|
|
36
|
-
result = idx;
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (typescript_1.default.isStringLiteral(arg)) {
|
|
41
|
-
result = 0;
|
|
95
|
+
if (checkArg(arg))
|
|
42
96
|
return;
|
|
43
|
-
}
|
|
44
97
|
}
|
|
45
98
|
}
|
|
46
99
|
}
|
package/dist/engine/graph.js
CHANGED
|
@@ -18,13 +18,16 @@ class DependencyGraph {
|
|
|
18
18
|
this.paths = paths;
|
|
19
19
|
}
|
|
20
20
|
normalize(file) {
|
|
21
|
-
return path_1.default.resolve(file);
|
|
21
|
+
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
22
22
|
}
|
|
23
|
+
// MODULE RESOLVE
|
|
23
24
|
resolveModule(module, from) {
|
|
25
|
+
// relative import
|
|
24
26
|
if (module.startsWith(".")) {
|
|
25
27
|
const abs = path_1.default.resolve(path_1.default.dirname(from), module);
|
|
26
28
|
return this.resolveFile(abs);
|
|
27
29
|
}
|
|
30
|
+
// tsconfig paths
|
|
28
31
|
for (const key in this.paths) {
|
|
29
32
|
const pattern = key.replace(/\*/g, "(.*)");
|
|
30
33
|
const regex = new RegExp("^" + pattern + "$");
|
|
@@ -43,9 +46,19 @@ class DependencyGraph {
|
|
|
43
46
|
return resolved;
|
|
44
47
|
}
|
|
45
48
|
}
|
|
49
|
+
// node resolution (safe)
|
|
50
|
+
try {
|
|
51
|
+
const resolved = require.resolve(module, {
|
|
52
|
+
paths: [path_1.default.dirname(from)],
|
|
53
|
+
});
|
|
54
|
+
return this.normalize(resolved);
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
// fallback
|
|
46
58
|
const abs = path_1.default.resolve(this.baseUrl, module);
|
|
47
59
|
return this.resolveFile(abs);
|
|
48
60
|
}
|
|
61
|
+
// FILE RESOLVE
|
|
49
62
|
resolveFile(base) {
|
|
50
63
|
if (this.fileCache.has(base)) {
|
|
51
64
|
return this.fileCache.get(base);
|
|
@@ -56,6 +69,12 @@ class DependencyGraph {
|
|
|
56
69
|
base + ".tsx",
|
|
57
70
|
base + ".js",
|
|
58
71
|
base + ".jsx",
|
|
72
|
+
base + ".mjs",
|
|
73
|
+
base + ".cjs",
|
|
74
|
+
base + ".d.ts",
|
|
75
|
+
base + ".vue",
|
|
76
|
+
base + ".svelte",
|
|
77
|
+
base + ".astro",
|
|
59
78
|
path_1.default.join(base, "index.ts"),
|
|
60
79
|
path_1.default.join(base, "index.tsx"),
|
|
61
80
|
path_1.default.join(base, "index.js"),
|
|
@@ -70,12 +89,22 @@ class DependencyGraph {
|
|
|
70
89
|
this.fileCache.set(base, null);
|
|
71
90
|
return null;
|
|
72
91
|
}
|
|
92
|
+
// UPDATE GRAPH
|
|
73
93
|
update(file, source) {
|
|
74
94
|
const normalizedFile = this.normalize(file);
|
|
95
|
+
for (const deps of this.reverse.values()) {
|
|
96
|
+
deps.delete(normalizedFile);
|
|
97
|
+
}
|
|
75
98
|
this.exports.set(normalizedFile, new Set());
|
|
76
99
|
for (const stmt of source.statements) {
|
|
100
|
+
// IMPORT
|
|
77
101
|
if (typescript_1.default.isImportDeclaration(stmt)) {
|
|
78
|
-
|
|
102
|
+
// ignore import type
|
|
103
|
+
if (stmt.importClause?.importKind === "type")
|
|
104
|
+
continue;
|
|
105
|
+
const module = stmt.moduleSpecifier
|
|
106
|
+
.getText()
|
|
107
|
+
.replace(/['"]/g, "");
|
|
79
108
|
const resolved = this.resolveModule(module, normalizedFile);
|
|
80
109
|
if (!resolved)
|
|
81
110
|
continue;
|
|
@@ -84,8 +113,13 @@ class DependencyGraph {
|
|
|
84
113
|
}
|
|
85
114
|
this.reverse.get(resolved).add(normalizedFile);
|
|
86
115
|
}
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
// EXPORT (re-export)
|
|
117
|
+
if (typescript_1.default.isExportDeclaration(stmt)) {
|
|
118
|
+
if (!stmt.moduleSpecifier)
|
|
119
|
+
continue;
|
|
120
|
+
const module = stmt.moduleSpecifier
|
|
121
|
+
.getText()
|
|
122
|
+
.replace(/['"]/g, "");
|
|
89
123
|
const resolved = this.resolveModule(module, normalizedFile);
|
|
90
124
|
if (!resolved)
|
|
91
125
|
continue;
|
|
@@ -97,6 +131,7 @@ class DependencyGraph {
|
|
|
97
131
|
}
|
|
98
132
|
}
|
|
99
133
|
}
|
|
134
|
+
// DEPENDENTS
|
|
100
135
|
getAllDependentsDeep(file) {
|
|
101
136
|
const start = this.normalize(file);
|
|
102
137
|
const visited = new Set();
|
|
@@ -116,6 +151,7 @@ class DependencyGraph {
|
|
|
116
151
|
walk(start);
|
|
117
152
|
return [...result];
|
|
118
153
|
}
|
|
154
|
+
// REMOVE
|
|
119
155
|
remove(file) {
|
|
120
156
|
const normalized = this.normalize(file);
|
|
121
157
|
this.reverse.delete(normalized);
|