@eventra_dev/eventra-cli 0.1.25 → 0.1.27
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 +62 -50
- 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
|
@@ -29,18 +29,48 @@ function resolveFromSymbol(
|
|
|
29
29
|
return resolveFromSymbol(aliased, checker, visited, depth + 1);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// re-export support
|
|
33
|
+
if ((symbol as any).exports) {
|
|
34
|
+
for (const exp of (symbol as any).exports.values()) {
|
|
35
|
+
const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
|
|
36
|
+
if (resolved) return resolved;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
const decls = symbol.getDeclarations() ?? [];
|
|
33
41
|
|
|
34
42
|
for (const decl of decls) {
|
|
43
|
+
// direct function
|
|
35
44
|
if (isFunctionLike(decl)) {
|
|
36
45
|
return decl;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
|
-
//
|
|
48
|
+
// VARIABLE → FUNCTION
|
|
49
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
50
|
+
if (isFunctionLike(decl.initializer)) {
|
|
51
|
+
return decl.initializer;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
|
|
55
|
+
if (innerSymbol && innerSymbol !== symbol) {
|
|
56
|
+
const resolved = resolveFromSymbol(
|
|
57
|
+
innerSymbol,
|
|
58
|
+
checker,
|
|
59
|
+
visited,
|
|
60
|
+
depth + 1
|
|
61
|
+
);
|
|
62
|
+
if (resolved) return resolved;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MethodSignature (interface / type)
|
|
40
67
|
if (ts.isMethodSignature(decl)) {
|
|
41
68
|
const parent = decl.parent;
|
|
42
69
|
|
|
43
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
ts.isInterfaceDeclaration(parent) ||
|
|
72
|
+
ts.isTypeLiteralNode(parent)
|
|
73
|
+
) {
|
|
44
74
|
const name = decl.name.getText();
|
|
45
75
|
|
|
46
76
|
const type = checker.getTypeAtLocation(parent);
|
|
@@ -49,16 +79,26 @@ function resolveFromSymbol(
|
|
|
49
79
|
for (const prop of props) {
|
|
50
80
|
if (prop.name !== name) continue;
|
|
51
81
|
|
|
52
|
-
const resolved = resolveFromSymbol(
|
|
82
|
+
const resolved = resolveFromSymbol(
|
|
83
|
+
prop,
|
|
84
|
+
checker,
|
|
85
|
+
visited,
|
|
86
|
+
depth + 1
|
|
87
|
+
);
|
|
53
88
|
if (resolved) return resolved;
|
|
54
89
|
}
|
|
55
90
|
}
|
|
56
91
|
}
|
|
57
92
|
|
|
58
|
-
//
|
|
93
|
+
// fallback recursion
|
|
59
94
|
const s = checker.getSymbolAtLocation(decl);
|
|
60
95
|
if (s && s !== symbol) {
|
|
61
|
-
const resolved = resolveFromSymbol(
|
|
96
|
+
const resolved = resolveFromSymbol(
|
|
97
|
+
s,
|
|
98
|
+
checker,
|
|
99
|
+
visited,
|
|
100
|
+
depth + 1
|
|
101
|
+
);
|
|
62
102
|
if (resolved) return resolved;
|
|
63
103
|
}
|
|
64
104
|
}
|
|
@@ -70,6 +110,32 @@ export function resolveFunctionFromCall(
|
|
|
70
110
|
expr: ts.Expression,
|
|
71
111
|
checker: ts.TypeChecker
|
|
72
112
|
): ts.FunctionLikeDeclaration | null {
|
|
113
|
+
|
|
114
|
+
// tracker.track
|
|
115
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
116
|
+
let symbol = checker.getSymbolAtLocation(expr.name);
|
|
117
|
+
|
|
118
|
+
if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
|
|
119
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (symbol) {
|
|
123
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
124
|
+
if (resolved) return resolved;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// tracker["track"]
|
|
129
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
130
|
+
const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
|
|
131
|
+
|
|
132
|
+
if (symbol) {
|
|
133
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
134
|
+
if (resolved) return resolved;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// normal call signatures
|
|
73
139
|
const type = checker.getTypeAtLocation(expr);
|
|
74
140
|
const signatures = type.getCallSignatures();
|
|
75
141
|
|
|
@@ -81,7 +147,6 @@ export function resolveFunctionFromCall(
|
|
|
81
147
|
return decl;
|
|
82
148
|
}
|
|
83
149
|
|
|
84
|
-
// interface method
|
|
85
150
|
if (ts.isMethodSignature(decl)) {
|
|
86
151
|
const symbol = checker.getSymbolAtLocation(decl.name);
|
|
87
152
|
|
|
@@ -92,5 +157,11 @@ export function resolveFunctionFromCall(
|
|
|
92
157
|
}
|
|
93
158
|
}
|
|
94
159
|
|
|
160
|
+
// fallback
|
|
161
|
+
const symbol = checker.getSymbolAtLocation(expr);
|
|
162
|
+
if (symbol) {
|
|
163
|
+
return resolveFromSymbol(symbol, checker);
|
|
164
|
+
}
|
|
165
|
+
|
|
95
166
|
return null;
|
|
96
167
|
}
|
package/src/engine/engine.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
1
4
|
import { TSService } from "./languageService";
|
|
2
5
|
import { ScanResult, scanSource } from "./scanner";
|
|
3
6
|
import { DependencyGraph } from "./graph";
|
|
@@ -18,8 +21,29 @@ export class EventraEngine {
|
|
|
18
21
|
const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
|
|
19
22
|
this.graph = new DependencyGraph(baseUrl, paths);
|
|
20
23
|
}
|
|
24
|
+
|
|
25
|
+
private normalize(file: string) {
|
|
26
|
+
return path.resolve(file).replace(/\\/g, "/");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private empty(): ScanResult {
|
|
30
|
+
return {
|
|
31
|
+
events: new Set(),
|
|
32
|
+
detectedFunctionWrappers: new Set(),
|
|
33
|
+
detectedComponentWrappers: new Map(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// PRELOAD
|
|
21
38
|
preloadFile(file: string, content: string) {
|
|
39
|
+
file = this.normalize(file);
|
|
40
|
+
|
|
22
41
|
this.ts.updateFile(file, content);
|
|
42
|
+
|
|
43
|
+
const source = this.ts.getSourceFile(file);
|
|
44
|
+
if (source) {
|
|
45
|
+
this.graph.update(file, source);
|
|
46
|
+
}
|
|
23
47
|
}
|
|
24
48
|
|
|
25
49
|
// SCAN FILE
|
|
@@ -28,33 +52,26 @@ export class EventraEngine {
|
|
|
28
52
|
content: string,
|
|
29
53
|
config: EventraConfig
|
|
30
54
|
): ScanResult {
|
|
55
|
+
file = this.normalize(file);
|
|
56
|
+
|
|
31
57
|
const h = hash(content);
|
|
32
58
|
|
|
33
|
-
// skip unchanged
|
|
34
59
|
if (this.fileHash.get(file) === h) {
|
|
35
|
-
return (
|
|
36
|
-
this.fileResults.get(file) ?? {
|
|
37
|
-
events: new Set(),
|
|
38
|
-
detectedFunctionWrappers: new Set(),
|
|
39
|
-
detectedComponentWrappers: new Map(),
|
|
40
|
-
}
|
|
41
|
-
);
|
|
60
|
+
return this.fileResults.get(file) ?? this.empty();
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
this.fileHash.set(file, h);
|
|
64
|
+
|
|
45
65
|
this.ts.updateFile(file, content);
|
|
46
66
|
|
|
47
67
|
const source = this.ts.getSourceFile(file);
|
|
48
68
|
const checker = this.ts.getChecker();
|
|
49
69
|
|
|
50
70
|
if (!source || !checker) {
|
|
51
|
-
return
|
|
52
|
-
events: new Set(),
|
|
53
|
-
detectedFunctionWrappers: new Set(),
|
|
54
|
-
detectedComponentWrappers: new Map(),
|
|
55
|
-
};
|
|
71
|
+
return this.empty();
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
// обновляем graph
|
|
58
75
|
this.graph.update(file, source);
|
|
59
76
|
|
|
60
77
|
const res = scanSource(source, checker, config);
|
|
@@ -69,32 +86,46 @@ export class EventraEngine {
|
|
|
69
86
|
content: string,
|
|
70
87
|
config: EventraConfig
|
|
71
88
|
) {
|
|
89
|
+
file = this.normalize(file);
|
|
90
|
+
|
|
72
91
|
const res = this.scanFile(file, content, config);
|
|
73
92
|
|
|
74
93
|
const dependents = this.graph.getAllDependentsDeep(file);
|
|
75
94
|
|
|
76
95
|
for (const dep of dependents) {
|
|
77
|
-
|
|
96
|
+
const abs = this.normalize(dep);
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
if (isExternalFile(abs)) continue;
|
|
99
|
+
|
|
100
|
+
let depContent = this.ts.getFileContent(abs);
|
|
101
|
+
|
|
102
|
+
if (!depContent) {
|
|
103
|
+
try {
|
|
104
|
+
depContent = fs.readFileSync(abs, "utf-8");
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
81
109
|
|
|
82
110
|
const h = hash(depContent);
|
|
83
111
|
|
|
84
|
-
if (this.fileHash.get(
|
|
112
|
+
if (this.fileHash.get(abs) === h) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.fileHash.set(abs, h);
|
|
85
117
|
|
|
86
|
-
this.
|
|
87
|
-
this.ts.updateFile(dep, depContent);
|
|
118
|
+
this.ts.updateFile(abs, depContent);
|
|
88
119
|
|
|
89
|
-
const source = this.ts.getSourceFile(
|
|
120
|
+
const source = this.ts.getSourceFile(abs);
|
|
90
121
|
const checker = this.ts.getChecker();
|
|
91
122
|
|
|
92
123
|
if (!source || !checker) continue;
|
|
93
124
|
|
|
94
|
-
this.graph.update(
|
|
125
|
+
this.graph.update(abs, source);
|
|
95
126
|
|
|
96
127
|
const r = scanSource(source, checker, config);
|
|
97
|
-
this.fileResults.set(
|
|
128
|
+
this.fileResults.set(abs, r);
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
return res;
|
|
@@ -113,31 +144,45 @@ export class EventraEngine {
|
|
|
113
144
|
|
|
114
145
|
// REMOVE FILE
|
|
115
146
|
removeFile(file: string, config?: EventraConfig) {
|
|
147
|
+
file = this.normalize(file);
|
|
148
|
+
|
|
149
|
+
const dependents = this.graph.getAllDependentsDeep(file);
|
|
150
|
+
|
|
116
151
|
this.ts.removeFile(file);
|
|
117
152
|
this.fileResults.delete(file);
|
|
118
153
|
this.fileHash.delete(file);
|
|
119
154
|
this.graph.remove(file);
|
|
120
155
|
|
|
121
|
-
const dependents = this.graph.getAllDependentsDeep(file);
|
|
122
|
-
|
|
123
156
|
for (const dep of dependents) {
|
|
124
|
-
|
|
157
|
+
const abs = this.normalize(dep);
|
|
125
158
|
|
|
126
|
-
|
|
127
|
-
|
|
159
|
+
if (isExternalFile(abs)) continue;
|
|
160
|
+
|
|
161
|
+
let content = this.ts.getFileContent(abs);
|
|
162
|
+
|
|
163
|
+
if (!content) {
|
|
164
|
+
try {
|
|
165
|
+
content = fs.readFileSync(abs, "utf-8");
|
|
166
|
+
} catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
128
170
|
|
|
129
171
|
const h = hash(content);
|
|
130
172
|
|
|
131
|
-
if (this.fileHash.get(
|
|
173
|
+
if (this.fileHash.get(abs) === h) continue;
|
|
174
|
+
|
|
175
|
+
this.fileHash.set(abs, h);
|
|
132
176
|
|
|
133
|
-
this.
|
|
134
|
-
this.ts.updateFile(dep, content);
|
|
177
|
+
this.ts.updateFile(abs, content);
|
|
135
178
|
|
|
136
|
-
const source = this.ts.getSourceFile(
|
|
179
|
+
const source = this.ts.getSourceFile(abs);
|
|
137
180
|
const checker = this.ts.getChecker();
|
|
138
181
|
|
|
139
182
|
if (!source || !checker) continue;
|
|
140
183
|
|
|
184
|
+
this.graph.update(abs, source);
|
|
185
|
+
|
|
141
186
|
const r = scanSource(
|
|
142
187
|
source,
|
|
143
188
|
checker,
|
|
@@ -149,7 +194,7 @@ export class EventraEngine {
|
|
|
149
194
|
}
|
|
150
195
|
);
|
|
151
196
|
|
|
152
|
-
this.fileResults.set(
|
|
197
|
+
this.fileResults.set(abs, r);
|
|
153
198
|
}
|
|
154
199
|
}
|
|
155
200
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
+
function isRealDeclaration(node: ts.Node) {
|
|
4
|
+
return (
|
|
5
|
+
ts.isFunctionDeclaration(node) ||
|
|
6
|
+
ts.isVariableDeclaration(node) ||
|
|
7
|
+
ts.isClassDeclaration(node)
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
3
11
|
export function resolveExportedSymbol(
|
|
4
12
|
symbol: ts.Symbol,
|
|
5
13
|
checker: ts.TypeChecker,
|
|
@@ -8,20 +16,29 @@ export function resolveExportedSymbol(
|
|
|
8
16
|
|
|
9
17
|
if (visited.has(symbol)) return null;
|
|
10
18
|
visited.add(symbol);
|
|
19
|
+
|
|
20
|
+
// unwrap alias
|
|
11
21
|
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
12
22
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
13
23
|
return resolveExportedSymbol(aliased, checker, visited);
|
|
14
24
|
}
|
|
15
25
|
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
// export symbol
|
|
27
|
+
const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
|
|
28
|
+
if (exportSymbol && exportSymbol !== symbol) {
|
|
29
|
+
return resolveExportedSymbol(exportSymbol, checker, visited);
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
const
|
|
32
|
+
const decls = symbol.getDeclarations() ?? [];
|
|
33
|
+
|
|
34
|
+
if (decls.some(isRealDeclaration)) {
|
|
35
|
+
return symbol;
|
|
36
|
+
}
|
|
21
37
|
|
|
22
|
-
|
|
38
|
+
// exports (re-export)
|
|
39
|
+
for (const decl of decls) {
|
|
23
40
|
const source = decl.getSourceFile();
|
|
24
|
-
const moduleSymbol =
|
|
41
|
+
const moduleSymbol = (source as any).symbol;
|
|
25
42
|
|
|
26
43
|
if (!moduleSymbol) continue;
|
|
27
44
|
|
|
@@ -8,24 +8,95 @@ export function findTrackParamIndex(
|
|
|
8
8
|
fn.parameters.forEach((p, i) => {
|
|
9
9
|
if (ts.isIdentifier(p.name)) {
|
|
10
10
|
paramIndex.set(p.name.text, i);
|
|
11
|
+
|
|
12
|
+
// default param: (name = "x")
|
|
13
|
+
if (p.initializer && ts.isStringLiteral(p.initializer)) {
|
|
14
|
+
if (i === 0) return 0;
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
});
|
|
13
18
|
|
|
14
19
|
let result: number | null = null;
|
|
15
20
|
|
|
21
|
+
function unwrap(node: ts.Expression): ts.Expression {
|
|
22
|
+
// as / parentheses
|
|
23
|
+
while (
|
|
24
|
+
ts.isAsExpression(node) ||
|
|
25
|
+
ts.isParenthesizedExpression(node)
|
|
26
|
+
) {
|
|
27
|
+
node = node.expression;
|
|
28
|
+
}
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function checkArg(arg: ts.Expression) {
|
|
33
|
+
arg = unwrap(arg);
|
|
34
|
+
|
|
35
|
+
// identifier → param
|
|
36
|
+
if (ts.isIdentifier(arg)) {
|
|
37
|
+
const idx = paramIndex.get(arg.text);
|
|
38
|
+
if (idx !== undefined) {
|
|
39
|
+
result = idx;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// string literal
|
|
45
|
+
if (ts.isStringLiteral(arg)) {
|
|
46
|
+
result = 0;
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// template `${name}`
|
|
51
|
+
if (ts.isTemplateExpression(arg)) {
|
|
52
|
+
for (const span of arg.templateSpans) {
|
|
53
|
+
if (ts.isIdentifier(span.expression)) {
|
|
54
|
+
const idx = paramIndex.get(span.expression.text);
|
|
55
|
+
if (idx !== undefined) {
|
|
56
|
+
result = idx;
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
result = 0;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// object { event: name }
|
|
66
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
67
|
+
for (const prop of arg.properties) {
|
|
68
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
69
|
+
|
|
70
|
+
const name = prop.name.getText();
|
|
71
|
+
|
|
72
|
+
if (["event", "name", "type"].includes(name)) {
|
|
73
|
+
return checkArg(prop.initializer);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// call: String(name)
|
|
79
|
+
if (ts.isCallExpression(arg)) {
|
|
80
|
+
for (const inner of arg.arguments) {
|
|
81
|
+
if (checkArg(inner)) return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
16
88
|
function visit(node: ts.Node) {
|
|
89
|
+
if (result !== null) return;
|
|
90
|
+
|
|
17
91
|
if (ts.isCallExpression(node)) {
|
|
18
92
|
let isTrackCall = false;
|
|
19
93
|
|
|
20
94
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (method === "track") {
|
|
95
|
+
if (node.expression.name.text === "track") {
|
|
24
96
|
isTrackCall = true;
|
|
25
97
|
}
|
|
26
98
|
}
|
|
27
99
|
|
|
28
|
-
// fallback
|
|
29
100
|
if (ts.isIdentifier(node.expression)) {
|
|
30
101
|
if (node.expression.text === "track") {
|
|
31
102
|
isTrackCall = true;
|
|
@@ -34,18 +105,7 @@ export function findTrackParamIndex(
|
|
|
34
105
|
|
|
35
106
|
if (isTrackCall) {
|
|
36
107
|
for (const arg of node.arguments) {
|
|
37
|
-
if (
|
|
38
|
-
const idx = paramIndex.get(arg.text);
|
|
39
|
-
if (idx !== undefined) {
|
|
40
|
-
result = idx;
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (ts.isStringLiteral(arg)) {
|
|
46
|
-
result = 0;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
108
|
+
if (checkArg(arg)) return;
|
|
49
109
|
}
|
|
50
110
|
}
|
|
51
111
|
}
|
package/src/engine/graph.ts
CHANGED
|
@@ -13,15 +13,18 @@ export class DependencyGraph {
|
|
|
13
13
|
) {}
|
|
14
14
|
|
|
15
15
|
private normalize(file: string) {
|
|
16
|
-
return path.resolve(file);
|
|
16
|
+
return path.resolve(file).replace(/\\/g, "/");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// MODULE RESOLVE
|
|
19
20
|
private resolveModule(module: string, from: string): string | null {
|
|
21
|
+
// relative import
|
|
20
22
|
if (module.startsWith(".")) {
|
|
21
23
|
const abs = path.resolve(path.dirname(from), module);
|
|
22
24
|
return this.resolveFile(abs);
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
// tsconfig paths
|
|
25
28
|
for (const key in this.paths) {
|
|
26
29
|
const pattern = key.replace(/\*/g, "(.*)");
|
|
27
30
|
const regex = new RegExp("^" + pattern + "$");
|
|
@@ -44,10 +47,21 @@ export class DependencyGraph {
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
// node resolution (safe)
|
|
51
|
+
try {
|
|
52
|
+
const resolved = require.resolve(module, {
|
|
53
|
+
paths: [path.dirname(from)],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return this.normalize(resolved);
|
|
57
|
+
} catch {}
|
|
58
|
+
|
|
59
|
+
// fallback
|
|
47
60
|
const abs = path.resolve(this.baseUrl, module);
|
|
48
61
|
return this.resolveFile(abs);
|
|
49
62
|
}
|
|
50
63
|
|
|
64
|
+
// FILE RESOLVE
|
|
51
65
|
private resolveFile(base: string): string | null {
|
|
52
66
|
if (this.fileCache.has(base)) {
|
|
53
67
|
return this.fileCache.get(base)!;
|
|
@@ -59,6 +73,12 @@ export class DependencyGraph {
|
|
|
59
73
|
base + ".tsx",
|
|
60
74
|
base + ".js",
|
|
61
75
|
base + ".jsx",
|
|
76
|
+
base + ".mjs",
|
|
77
|
+
base + ".cjs",
|
|
78
|
+
base + ".d.ts",
|
|
79
|
+
base + ".vue",
|
|
80
|
+
base + ".svelte",
|
|
81
|
+
base + ".astro",
|
|
62
82
|
path.join(base, "index.ts"),
|
|
63
83
|
path.join(base, "index.tsx"),
|
|
64
84
|
path.join(base, "index.js"),
|
|
@@ -76,16 +96,28 @@ export class DependencyGraph {
|
|
|
76
96
|
return null;
|
|
77
97
|
}
|
|
78
98
|
|
|
99
|
+
// UPDATE GRAPH
|
|
79
100
|
update(file: string, source: ts.SourceFile) {
|
|
80
101
|
const normalizedFile = this.normalize(file);
|
|
81
102
|
|
|
103
|
+
for (const deps of this.reverse.values()) {
|
|
104
|
+
deps.delete(normalizedFile);
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
this.exports.set(normalizedFile, new Set());
|
|
83
108
|
|
|
84
109
|
for (const stmt of source.statements) {
|
|
110
|
+
|
|
111
|
+
// IMPORT
|
|
85
112
|
if (ts.isImportDeclaration(stmt)) {
|
|
86
|
-
|
|
87
|
-
|
|
113
|
+
// ignore import type
|
|
114
|
+
if ((stmt.importClause as any)?.importKind === "type") continue;
|
|
115
|
+
|
|
116
|
+
const module = stmt.moduleSpecifier
|
|
117
|
+
.getText()
|
|
118
|
+
.replace(/['"]/g, "");
|
|
88
119
|
|
|
120
|
+
const resolved = this.resolveModule(module, normalizedFile);
|
|
89
121
|
if (!resolved) continue;
|
|
90
122
|
|
|
91
123
|
if (!this.reverse.has(resolved)) {
|
|
@@ -95,10 +127,15 @@ export class DependencyGraph {
|
|
|
95
127
|
this.reverse.get(resolved)!.add(normalizedFile);
|
|
96
128
|
}
|
|
97
129
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
130
|
+
// EXPORT (re-export)
|
|
131
|
+
if (ts.isExportDeclaration(stmt)) {
|
|
132
|
+
if (!stmt.moduleSpecifier) continue;
|
|
133
|
+
|
|
134
|
+
const module = stmt.moduleSpecifier
|
|
135
|
+
.getText()
|
|
136
|
+
.replace(/['"]/g, "");
|
|
101
137
|
|
|
138
|
+
const resolved = this.resolveModule(module, normalizedFile);
|
|
102
139
|
if (!resolved) continue;
|
|
103
140
|
|
|
104
141
|
this.exports.get(normalizedFile)?.add(resolved);
|
|
@@ -112,6 +149,7 @@ export class DependencyGraph {
|
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
|
|
152
|
+
// DEPENDENTS
|
|
115
153
|
getAllDependentsDeep(file: string): string[] {
|
|
116
154
|
const start = this.normalize(file);
|
|
117
155
|
const visited = new Set<string>();
|
|
@@ -136,6 +174,7 @@ export class DependencyGraph {
|
|
|
136
174
|
return [...result];
|
|
137
175
|
}
|
|
138
176
|
|
|
177
|
+
// REMOVE
|
|
139
178
|
remove(file: string) {
|
|
140
179
|
const normalized = this.normalize(file);
|
|
141
180
|
|