@eventra_dev/eventra-cli 0.2.4 → 0.3.2
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 +143 -32
- package/dist/analysis/cache/evaluationCache.js +21 -0
- package/dist/analysis/cache/resolvedCallCache.js +21 -0
- package/dist/analysis/cache/resolvedExportCache.js +21 -0
- package/dist/analysis/cache/semanticDependencyGraph.js +25 -0
- package/dist/analysis/cache/symbolInvalidation.js +20 -0
- package/dist/analysis/extractor/extractor.js +47 -0
- package/dist/analysis/extractor/propagationExtractor.js +52 -0
- package/dist/analysis/pipeline/semanticPass.js +2 -0
- package/dist/analysis/resolver/callResolver.js +125 -0
- package/dist/analysis/resolver/exportResolver.js +75 -0
- package/dist/analysis/resolver/resolver.js +133 -0
- package/dist/analysis/scanner/analyzer/propagationAnalyzer.js +193 -0
- package/dist/analysis/scanner/analyzer/returnPropagationAnalyzer.js +105 -0
- package/dist/analysis/scanner/scanner.js +117 -0
- package/dist/analysis/scanner/sinkDetector.js +91 -0
- package/dist/analysis/shared/evaluationContext.js +9 -0
- package/dist/analysis/shared/propagation.js +3 -0
- package/dist/analysis/shared/returnPropagation.js +2 -0
- package/dist/analysis/shared/types.js +2 -0
- package/dist/analysis/shared/utils.js +62 -0
- package/dist/analysis/symbols/symbolUtils.js +68 -0
- package/dist/analysis/symbols/wrapperRegistry.js +55 -0
- package/dist/cli/check.js +73 -0
- package/dist/{commands → cli}/init.js +1 -1
- package/dist/{commands → cli}/send.js +1 -1
- package/dist/cli/sync.js +56 -0
- package/dist/{commands → cli}/watch.js +41 -25
- package/dist/compiler/compilerContext.js +204 -0
- package/dist/compiler/documentRegistry.js +78 -0
- package/dist/compiler/importGraph.js +83 -0
- package/dist/compiler/scheduler.js +29 -0
- package/dist/core/EventraEngine.js +234 -0
- package/dist/core/projectScanner.js +73 -0
- package/dist/{utils → filesystem}/getVirtualFile.js +1 -3
- package/dist/filesystem/processFile.js +38 -0
- package/dist/index.js +5 -5
- package/dist/shared/buildSyntheticTsx.js +20 -0
- package/dist/shared/templateExpressionExtractor.js +22 -0
- package/package.json +32 -9
- package/.github/workflows/release.yml +0 -43
- package/ARCHITECTURE.md +0 -88
- package/assets/eventra-icon-animated.svg +0 -19
- package/dist/commands/check.js +0 -78
- package/dist/commands/sync.js +0 -79
- package/dist/engine/boundary.js +0 -14
- package/dist/engine/callResolver.js +0 -58
- package/dist/engine/engine.js +0 -154
- package/dist/engine/exportResolver.js +0 -46
- package/dist/engine/functionAnalyzer.js +0 -71
- package/dist/engine/graph.js +0 -168
- package/dist/engine/languageService.js +0 -152
- package/dist/engine/resolver.js +0 -149
- package/dist/engine/scanner.js +0 -230
- package/dist/utils/processFile.js +0 -28
- package/dist/utils/universal.js +0 -190
- package/src/commands/check.ts +0 -91
- package/src/commands/init.ts +0 -174
- package/src/commands/send.ts +0 -213
- package/src/commands/sync.ts +0 -106
- package/src/commands/watch.ts +0 -144
- package/src/engine/boundary.ts +0 -12
- package/src/engine/callResolver.ts +0 -65
- package/src/engine/engine.ts +0 -227
- package/src/engine/exportResolver.ts +0 -56
- package/src/engine/functionAnalyzer.ts +0 -81
- package/src/engine/graph.ts +0 -193
- package/src/engine/hash.ts +0 -5
- package/src/engine/languageService.ts +0 -197
- package/src/engine/resolver.ts +0 -177
- package/src/engine/scanner.ts +0 -298
- package/src/index.ts +0 -49
- package/src/types.ts +0 -35
- package/src/utils/config.ts +0 -53
- package/src/utils/getVirtualFile.ts +0 -5
- package/src/utils/processFile.ts +0 -29
- package/src/utils/universal.ts +0 -249
- package/tests/fixtures/backend/express/app.ts +0 -233
- package/tests/fixtures/backend/nest/service.ts +0 -70
- package/tests/fixtures/backend/node/index.ts +0 -63
- package/tests/fixtures/frontend/next/page.tsx +0 -101
- package/tests/fixtures/frontend/react/App.tsx +0 -104
- package/tests/fixtures/frontend/vue/App.vue +0 -83
- package/tests/fixtures/wrappers/component/test.tsx +0 -62
- package/tests/fixtures/wrappers/function/test.ts +0 -60
- package/tests/run.ts +0 -106
- package/tsconfig.json +0 -14
- /package/dist/{utils → config}/config.js +0 -0
- /package/dist/{engine → shared}/hash.js +0 -0
package/src/engine/engine.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
|
|
4
|
-
import { TSService } from "./languageService";
|
|
5
|
-
import { scanSource } from "./scanner";
|
|
6
|
-
import { DependencyGraph } from "./graph";
|
|
7
|
-
import { hash } from "./hash";
|
|
8
|
-
import { EventraConfig, ScanResult } from "../types";
|
|
9
|
-
import { isExternalFile } from "./boundary";
|
|
10
|
-
|
|
11
|
-
export class EventraEngine {
|
|
12
|
-
private ts: TSService;
|
|
13
|
-
private graph: DependencyGraph;
|
|
14
|
-
private wrapperCache = new Map<string, string | null>();
|
|
15
|
-
private fileResults = new Map<string, ScanResult>();
|
|
16
|
-
private fileHash = new Map<string, string>();
|
|
17
|
-
|
|
18
|
-
constructor(root: string) {
|
|
19
|
-
this.ts = new TSService(root);
|
|
20
|
-
|
|
21
|
-
const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
|
|
22
|
-
this.graph = new DependencyGraph(baseUrl, paths);
|
|
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
|
|
38
|
-
preloadFile(file: string, content: string) {
|
|
39
|
-
file = this.normalize(file);
|
|
40
|
-
|
|
41
|
-
this.ts.updateFile(file, content);
|
|
42
|
-
|
|
43
|
-
const source = this.ts.getSourceFile(file);
|
|
44
|
-
if (source) {
|
|
45
|
-
this.graph.update(file, source);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// SCAN FILE (cached)
|
|
50
|
-
scanFile(
|
|
51
|
-
file: string,
|
|
52
|
-
content: string,
|
|
53
|
-
config: EventraConfig
|
|
54
|
-
): ScanResult {
|
|
55
|
-
file = this.normalize(file);
|
|
56
|
-
|
|
57
|
-
const h = hash(content);
|
|
58
|
-
|
|
59
|
-
if (this.fileHash.get(file) === h) {
|
|
60
|
-
return this.fileResults.get(file) ?? this.empty();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.fileHash.set(file, h);
|
|
64
|
-
|
|
65
|
-
this.ts.updateFile(file, content);
|
|
66
|
-
|
|
67
|
-
const source = this.ts.getSourceFile(file);
|
|
68
|
-
const checker = this.ts.getChecker();
|
|
69
|
-
|
|
70
|
-
if (!source || !checker) {
|
|
71
|
-
return this.empty();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
this.graph.update(file, source);
|
|
75
|
-
|
|
76
|
-
const res = scanSource(
|
|
77
|
-
source,
|
|
78
|
-
checker,
|
|
79
|
-
config,
|
|
80
|
-
new Set(),
|
|
81
|
-
undefined,
|
|
82
|
-
this.wrapperCache
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
this.fileResults.set(file, res);
|
|
86
|
-
|
|
87
|
-
return res;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// UPDATE FILE (incremental)
|
|
91
|
-
updateFile(
|
|
92
|
-
file: string,
|
|
93
|
-
content: string,
|
|
94
|
-
config: EventraConfig
|
|
95
|
-
): ScanResult {
|
|
96
|
-
file = this.normalize(file);
|
|
97
|
-
|
|
98
|
-
// EARLY EXIT
|
|
99
|
-
if (this.fileHash.get(file) === hash(content)) {
|
|
100
|
-
return this.fileResults.get(file) ?? this.empty();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// invalidate wrapper cache
|
|
104
|
-
this.wrapperCache.clear();
|
|
105
|
-
|
|
106
|
-
const res = this.scanFile(file, content, config);
|
|
107
|
-
|
|
108
|
-
const dependents = this.graph.getAllDependentsDeep(file);
|
|
109
|
-
|
|
110
|
-
for (const dep of dependents) {
|
|
111
|
-
const abs = this.normalize(dep);
|
|
112
|
-
|
|
113
|
-
if (abs === file) continue; // self-skip
|
|
114
|
-
if (isExternalFile(abs)) continue;
|
|
115
|
-
|
|
116
|
-
let depContent = this.ts.getFileContent(abs);
|
|
117
|
-
|
|
118
|
-
if (!depContent) {
|
|
119
|
-
try {
|
|
120
|
-
depContent = fs.readFileSync(abs, "utf-8");
|
|
121
|
-
} catch {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const h = hash(depContent);
|
|
127
|
-
|
|
128
|
-
if (this.fileHash.get(abs) === h) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
this.fileHash.set(abs, h);
|
|
133
|
-
|
|
134
|
-
this.ts.updateFile(abs, depContent);
|
|
135
|
-
|
|
136
|
-
const source = this.ts.getSourceFile(abs);
|
|
137
|
-
const checker = this.ts.getChecker();
|
|
138
|
-
|
|
139
|
-
if (!source || !checker) continue;
|
|
140
|
-
|
|
141
|
-
this.graph.update(abs, source);
|
|
142
|
-
|
|
143
|
-
const r = scanSource(
|
|
144
|
-
source,
|
|
145
|
-
checker,
|
|
146
|
-
config,
|
|
147
|
-
new Set(),
|
|
148
|
-
undefined,
|
|
149
|
-
this.wrapperCache
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
this.fileResults.set(abs, r);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return res;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// GET ALL EVENTS
|
|
159
|
-
getAllEvents() {
|
|
160
|
-
const all = new Set<string>();
|
|
161
|
-
|
|
162
|
-
for (const r of this.fileResults.values()) {
|
|
163
|
-
r.events.forEach(e => all.add(e));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return [...all];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// REMOVE FILE
|
|
170
|
-
removeFile(file: string, config?: EventraConfig) {
|
|
171
|
-
file = this.normalize(file);
|
|
172
|
-
|
|
173
|
-
this.wrapperCache.clear();
|
|
174
|
-
|
|
175
|
-
const dependents = this.graph.getAllDependentsDeep(file);
|
|
176
|
-
|
|
177
|
-
this.graph.remove(file);
|
|
178
|
-
|
|
179
|
-
this.ts.removeFile(file);
|
|
180
|
-
this.fileResults.delete(file);
|
|
181
|
-
this.fileHash.delete(file);
|
|
182
|
-
|
|
183
|
-
for (const dep of dependents) {
|
|
184
|
-
const abs = this.normalize(dep);
|
|
185
|
-
|
|
186
|
-
if (abs === file) continue;
|
|
187
|
-
if (isExternalFile(abs)) continue;
|
|
188
|
-
|
|
189
|
-
let content = this.ts.getFileContent(abs);
|
|
190
|
-
|
|
191
|
-
if (!content) {
|
|
192
|
-
try {
|
|
193
|
-
content = fs.readFileSync(abs, "utf-8");
|
|
194
|
-
} catch {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
this.fileHash.delete(abs);
|
|
200
|
-
|
|
201
|
-
this.ts.updateFile(abs, content);
|
|
202
|
-
|
|
203
|
-
const source = this.ts.getSourceFile(abs);
|
|
204
|
-
const checker = this.ts.getChecker();
|
|
205
|
-
|
|
206
|
-
if (!source || !checker) continue;
|
|
207
|
-
|
|
208
|
-
this.graph.update(abs, source);
|
|
209
|
-
|
|
210
|
-
const r = scanSource(
|
|
211
|
-
source,
|
|
212
|
-
checker,
|
|
213
|
-
config ?? {
|
|
214
|
-
events: [],
|
|
215
|
-
wrappers: [],
|
|
216
|
-
functionWrappers: [],
|
|
217
|
-
sync: { include: [], exclude: [] },
|
|
218
|
-
},
|
|
219
|
-
new Set(),
|
|
220
|
-
undefined,
|
|
221
|
-
this.wrapperCache
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
this.fileResults.set(abs, r);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
function isRealDeclaration(node: ts.Node) {
|
|
4
|
-
return (
|
|
5
|
-
ts.isFunctionDeclaration(node) ||
|
|
6
|
-
ts.isVariableDeclaration(node) ||
|
|
7
|
-
ts.isClassDeclaration(node)
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function resolveExportedSymbol(
|
|
12
|
-
symbol: ts.Symbol,
|
|
13
|
-
checker: ts.TypeChecker,
|
|
14
|
-
visited = new Set<ts.Symbol>()
|
|
15
|
-
): ts.Symbol | null {
|
|
16
|
-
|
|
17
|
-
if (visited.has(symbol)) return null;
|
|
18
|
-
visited.add(symbol);
|
|
19
|
-
|
|
20
|
-
// unwrap alias
|
|
21
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
22
|
-
const aliased = checker.getAliasedSymbol(symbol);
|
|
23
|
-
return resolveExportedSymbol(aliased, checker, visited);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// export symbol
|
|
27
|
-
const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
|
|
28
|
-
if (exportSymbol && exportSymbol !== symbol) {
|
|
29
|
-
return resolveExportedSymbol(exportSymbol, checker, visited);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const decls = symbol.getDeclarations() ?? [];
|
|
33
|
-
|
|
34
|
-
if (decls.some(isRealDeclaration)) {
|
|
35
|
-
return symbol;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// exports (re-export)
|
|
39
|
-
for (const decl of decls) {
|
|
40
|
-
const source = decl.getSourceFile();
|
|
41
|
-
const moduleSymbol = (source as any).symbol;
|
|
42
|
-
|
|
43
|
-
if (!moduleSymbol) continue;
|
|
44
|
-
|
|
45
|
-
const exports = checker.getExportsOfModule(moduleSymbol);
|
|
46
|
-
|
|
47
|
-
for (const exp of exports) {
|
|
48
|
-
const resolved = resolveExportedSymbol(exp, checker, visited);
|
|
49
|
-
if (resolved?.getDeclarations()?.length) {
|
|
50
|
-
return resolved;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return symbol;
|
|
56
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
export function findTrackParamIndex(
|
|
4
|
-
fn: ts.FunctionLikeDeclaration
|
|
5
|
-
): number | null {
|
|
6
|
-
const paramIndex = new Map<string, number>();
|
|
7
|
-
|
|
8
|
-
fn.parameters.forEach((p, i) => {
|
|
9
|
-
if (ts.isIdentifier(p.name)) {
|
|
10
|
-
paramIndex.set(p.name.text, i);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
let result: number | null = null;
|
|
15
|
-
|
|
16
|
-
function check(arg: ts.Expression): boolean {
|
|
17
|
-
// identifier
|
|
18
|
-
if (ts.isIdentifier(arg)) {
|
|
19
|
-
const idx = paramIndex.get(arg.text);
|
|
20
|
-
if (idx !== undefined) {
|
|
21
|
-
result = idx;
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// template
|
|
27
|
-
if (ts.isTemplateExpression(arg)) {
|
|
28
|
-
for (const span of arg.templateSpans) {
|
|
29
|
-
if (ts.isIdentifier(span.expression)) {
|
|
30
|
-
const idx = paramIndex.get(span.expression.text);
|
|
31
|
-
if (idx !== undefined) {
|
|
32
|
-
result = idx;
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// object { event: name }
|
|
40
|
-
if (ts.isObjectLiteralExpression(arg)) {
|
|
41
|
-
for (const prop of arg.properties) {
|
|
42
|
-
if (!ts.isPropertyAssignment(prop)) continue;
|
|
43
|
-
|
|
44
|
-
const key = prop.name.getText();
|
|
45
|
-
|
|
46
|
-
if (["event", "name", "type"].includes(key)) {
|
|
47
|
-
return check(prop.initializer);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function visit(node: ts.Node) {
|
|
56
|
-
if (result !== null) return;
|
|
57
|
-
|
|
58
|
-
if (ts.isCallExpression(node)) {
|
|
59
|
-
const expr = node.expression;
|
|
60
|
-
|
|
61
|
-
const isTrack =
|
|
62
|
-
(ts.isIdentifier(expr) && expr.text === "track") ||
|
|
63
|
-
(ts.isPropertyAccessExpression(expr) &&
|
|
64
|
-
expr.name.text === "track");
|
|
65
|
-
|
|
66
|
-
if (!isTrack) return;
|
|
67
|
-
|
|
68
|
-
for (const arg of node.arguments) {
|
|
69
|
-
if (check(arg)) return;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
ts.forEachChild(node, visit);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (fn.body) {
|
|
77
|
-
visit(fn.body);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return result;
|
|
81
|
-
}
|
package/src/engine/graph.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
|
|
5
|
-
export class DependencyGraph {
|
|
6
|
-
private reverse = new Map<string, Set<string>>();
|
|
7
|
-
private exports = new Map<string, Set<string>>();
|
|
8
|
-
private fileCache = new Map<string, string | null>();
|
|
9
|
-
|
|
10
|
-
constructor(
|
|
11
|
-
private baseUrl: string,
|
|
12
|
-
private paths: Record<string, string[]>
|
|
13
|
-
) {}
|
|
14
|
-
|
|
15
|
-
private normalize(file: string) {
|
|
16
|
-
return path.resolve(file).replace(/\\/g, "/");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// MODULE RESOLVE
|
|
20
|
-
private resolveModule(module: string, from: string): string | null {
|
|
21
|
-
// relative import
|
|
22
|
-
if (module.startsWith(".")) {
|
|
23
|
-
const abs = path.resolve(path.dirname(from), module);
|
|
24
|
-
return this.resolveFile(abs);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// tsconfig paths
|
|
28
|
-
for (const key in this.paths) {
|
|
29
|
-
const pattern = key.replace(/\*/g, "(.*)");
|
|
30
|
-
const regex = new RegExp("^" + pattern + "$");
|
|
31
|
-
const match = module.match(regex);
|
|
32
|
-
|
|
33
|
-
if (!match) continue;
|
|
34
|
-
|
|
35
|
-
const wildcard = match.slice(1);
|
|
36
|
-
|
|
37
|
-
for (const target of this.paths[key]) {
|
|
38
|
-
let replaced = target;
|
|
39
|
-
|
|
40
|
-
wildcard.forEach(w => {
|
|
41
|
-
replaced = replaced.replace("*", w);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const abs = path.resolve(this.baseUrl, replaced);
|
|
45
|
-
const resolved = this.resolveFile(abs);
|
|
46
|
-
if (resolved) return resolved;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
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
|
|
60
|
-
const abs = path.resolve(this.baseUrl, module);
|
|
61
|
-
return this.resolveFile(abs);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// FILE RESOLVE
|
|
65
|
-
private resolveFile(base: string): string | null {
|
|
66
|
-
if (this.fileCache.has(base)) {
|
|
67
|
-
return this.fileCache.get(base)!;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const tryFiles = [
|
|
71
|
-
base,
|
|
72
|
-
base + ".ts",
|
|
73
|
-
base + ".tsx",
|
|
74
|
-
base + ".js",
|
|
75
|
-
base + ".jsx",
|
|
76
|
-
base + ".mjs",
|
|
77
|
-
base + ".cjs",
|
|
78
|
-
base + ".d.ts",
|
|
79
|
-
base + ".vue",
|
|
80
|
-
base + ".svelte",
|
|
81
|
-
base + ".astro",
|
|
82
|
-
path.join(base, "index.ts"),
|
|
83
|
-
path.join(base, "index.tsx"),
|
|
84
|
-
path.join(base, "index.js"),
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
for (const f of tryFiles) {
|
|
88
|
-
if (fs.existsSync(f)) {
|
|
89
|
-
const normalized = this.normalize(f);
|
|
90
|
-
this.fileCache.set(base, normalized);
|
|
91
|
-
return normalized;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.fileCache.set(base, null);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// UPDATE GRAPH
|
|
100
|
-
update(file: string, source: ts.SourceFile) {
|
|
101
|
-
const normalizedFile = this.normalize(file);
|
|
102
|
-
|
|
103
|
-
for (const deps of this.reverse.values()) {
|
|
104
|
-
deps.delete(normalizedFile);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.exports.set(normalizedFile, new Set());
|
|
108
|
-
|
|
109
|
-
for (const stmt of source.statements) {
|
|
110
|
-
|
|
111
|
-
// IMPORT
|
|
112
|
-
if (ts.isImportDeclaration(stmt)) {
|
|
113
|
-
// ignore import type
|
|
114
|
-
if ((stmt.importClause as any)?.importKind === "type") continue;
|
|
115
|
-
|
|
116
|
-
const module = stmt.moduleSpecifier
|
|
117
|
-
.getText()
|
|
118
|
-
.replace(/['"]/g, "");
|
|
119
|
-
|
|
120
|
-
const resolved = this.resolveModule(module, normalizedFile);
|
|
121
|
-
if (!resolved) continue;
|
|
122
|
-
|
|
123
|
-
if (!this.reverse.has(resolved)) {
|
|
124
|
-
this.reverse.set(resolved, new Set());
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.reverse.get(resolved)!.add(normalizedFile);
|
|
128
|
-
}
|
|
129
|
-
|
|
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, "");
|
|
137
|
-
|
|
138
|
-
const resolved = this.resolveModule(module, normalizedFile);
|
|
139
|
-
if (!resolved) continue;
|
|
140
|
-
|
|
141
|
-
this.exports.get(normalizedFile)?.add(resolved);
|
|
142
|
-
|
|
143
|
-
if (!this.reverse.has(resolved)) {
|
|
144
|
-
this.reverse.set(resolved, new Set());
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
this.reverse.get(resolved)!.add(normalizedFile);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// DEPENDENTS
|
|
153
|
-
getAllDependentsDeep(file: string): string[] {
|
|
154
|
-
const start = this.normalize(file);
|
|
155
|
-
const visited = new Set<string>();
|
|
156
|
-
const result = new Set<string>();
|
|
157
|
-
|
|
158
|
-
const walk = (f: string) => {
|
|
159
|
-
if (visited.has(f)) return;
|
|
160
|
-
visited.add(f);
|
|
161
|
-
|
|
162
|
-
const deps = this.reverse.get(f);
|
|
163
|
-
deps?.forEach(d => {
|
|
164
|
-
result.add(d);
|
|
165
|
-
walk(d);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const ex = this.exports.get(f);
|
|
169
|
-
ex?.forEach(e => walk(e));
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
walk(start);
|
|
173
|
-
|
|
174
|
-
return [...result];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// REMOVE
|
|
178
|
-
remove(file: string) {
|
|
179
|
-
const normalized = this.normalize(file);
|
|
180
|
-
|
|
181
|
-
this.reverse.delete(normalized);
|
|
182
|
-
this.exports.delete(normalized);
|
|
183
|
-
this.fileCache.delete(normalized);
|
|
184
|
-
|
|
185
|
-
for (const set of this.reverse.values()) {
|
|
186
|
-
set.delete(normalized);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
for (const set of this.exports.values()) {
|
|
190
|
-
set.delete(normalized);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|