@eventra_dev/eventra-cli 0.2.4 → 0.3.1
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 +154 -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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CompilerContext = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
10
|
+
const documentRegistry_1 = require("./documentRegistry");
|
|
11
|
+
const processFile_1 = require("../filesystem/processFile");
|
|
12
|
+
class CompilerContext {
|
|
13
|
+
rootDir;
|
|
14
|
+
registry = new documentRegistry_1.DocumentRegistry();
|
|
15
|
+
compilerOptions;
|
|
16
|
+
host;
|
|
17
|
+
builder;
|
|
18
|
+
rootNames = new Set();
|
|
19
|
+
constructor(rootDir) {
|
|
20
|
+
this.rootDir = rootDir;
|
|
21
|
+
const configPath = typescript_1.default.findConfigFile(rootDir, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
22
|
+
let compilerOptions = {
|
|
23
|
+
target: typescript_1.default.ScriptTarget.ESNext,
|
|
24
|
+
module: typescript_1.default.ModuleKind.ESNext,
|
|
25
|
+
moduleResolution: typescript_1.default.ModuleResolutionKind.Bundler,
|
|
26
|
+
jsx: typescript_1.default.JsxEmit.Preserve,
|
|
27
|
+
allowJs: true,
|
|
28
|
+
checkJs: false,
|
|
29
|
+
strict: false,
|
|
30
|
+
skipLibCheck: true,
|
|
31
|
+
noEmit: true,
|
|
32
|
+
allowSyntheticDefaultImports: true,
|
|
33
|
+
esModuleInterop: true,
|
|
34
|
+
resolveJsonModule: true,
|
|
35
|
+
allowNonTsExtensions: true,
|
|
36
|
+
incremental: true,
|
|
37
|
+
};
|
|
38
|
+
if (configPath) {
|
|
39
|
+
const config = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
40
|
+
if (config.error) {
|
|
41
|
+
throw new Error(typescript_1.default.flattenDiagnosticMessageText(config.error.messageText, "\n"));
|
|
42
|
+
}
|
|
43
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(config.config, typescript_1.default.sys, path_1.default.dirname(configPath));
|
|
44
|
+
compilerOptions = {
|
|
45
|
+
...compilerOptions,
|
|
46
|
+
...parsed.options,
|
|
47
|
+
};
|
|
48
|
+
for (const file of parsed.fileNames) {
|
|
49
|
+
this.rootNames.add(this.normalize(file));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.compilerOptions =
|
|
53
|
+
compilerOptions;
|
|
54
|
+
this.host =
|
|
55
|
+
typescript_1.default.createCompilerHost(this.compilerOptions, true);
|
|
56
|
+
this.patchCompilerHost();
|
|
57
|
+
this.builder =
|
|
58
|
+
typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram([...this.rootNames], this.compilerOptions, this.host);
|
|
59
|
+
}
|
|
60
|
+
normalize(fileName) {
|
|
61
|
+
return this.registry
|
|
62
|
+
.normalize(fileName);
|
|
63
|
+
}
|
|
64
|
+
patchCompilerHost() {
|
|
65
|
+
const originalGetSourceFile = this.host.getSourceFile;
|
|
66
|
+
this.host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
67
|
+
const normalized = this.normalize(fileName);
|
|
68
|
+
this.registry.ensure(normalized);
|
|
69
|
+
const snapshot = this.registry.getSnapshot(normalized);
|
|
70
|
+
if (!snapshot) {
|
|
71
|
+
return originalGetSourceFile(normalized, languageVersion, onError, shouldCreateNewSourceFile);
|
|
72
|
+
}
|
|
73
|
+
return typescript_1.default.createSourceFile(normalized, snapshot.getText(0, snapshot.getLength()), languageVersion, true, this.getScriptKind(normalized));
|
|
74
|
+
};
|
|
75
|
+
this.host.readFile = (fileName) => {
|
|
76
|
+
const normalized = this.normalize(fileName);
|
|
77
|
+
this.registry.ensure(normalized);
|
|
78
|
+
return (this.registry.getContent(normalized)
|
|
79
|
+
?? fs_1.default.readFileSync(normalized, "utf8"));
|
|
80
|
+
};
|
|
81
|
+
this.host.fileExists = (fileName) => {
|
|
82
|
+
const normalized = this.normalize(fileName);
|
|
83
|
+
return (this.registry.has(normalized)
|
|
84
|
+
|| fs_1.default.existsSync(normalized));
|
|
85
|
+
};
|
|
86
|
+
this.host.getCanonicalFileName =
|
|
87
|
+
(f) => this.normalize(f);
|
|
88
|
+
this.host.useCaseSensitiveFileNames =
|
|
89
|
+
() => true;
|
|
90
|
+
this.host.getCurrentDirectory =
|
|
91
|
+
() => this.rootDir;
|
|
92
|
+
this.host.getNewLine =
|
|
93
|
+
() => "\n";
|
|
94
|
+
}
|
|
95
|
+
getScriptKind(fileName) {
|
|
96
|
+
if (fileName.endsWith(".tsx")) {
|
|
97
|
+
return typescript_1.default.ScriptKind.TSX;
|
|
98
|
+
}
|
|
99
|
+
if (fileName.endsWith(".ts")) {
|
|
100
|
+
return typescript_1.default.ScriptKind.TS;
|
|
101
|
+
}
|
|
102
|
+
if (fileName.endsWith(".jsx")) {
|
|
103
|
+
return typescript_1.default.ScriptKind.JSX;
|
|
104
|
+
}
|
|
105
|
+
if (fileName.endsWith(".js")) {
|
|
106
|
+
return typescript_1.default.ScriptKind.JS;
|
|
107
|
+
}
|
|
108
|
+
if (fileName.endsWith(".json")) {
|
|
109
|
+
return typescript_1.default.ScriptKind.JSON;
|
|
110
|
+
}
|
|
111
|
+
return typescript_1.default.ScriptKind.Unknown;
|
|
112
|
+
}
|
|
113
|
+
rebuild() {
|
|
114
|
+
this.builder =
|
|
115
|
+
typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram([...this.rootNames], this.compilerOptions, this.host, this.builder);
|
|
116
|
+
}
|
|
117
|
+
async updateFile(fileName, content) {
|
|
118
|
+
const normalized = this.normalize(fileName);
|
|
119
|
+
const processed = await (0, processFile_1.processFile)(normalized, content);
|
|
120
|
+
this.registry.update(processed.fileName, processed.content);
|
|
121
|
+
this.rootNames.add(processed.fileName);
|
|
122
|
+
this.rebuild();
|
|
123
|
+
}
|
|
124
|
+
removeFile(fileName) {
|
|
125
|
+
const normalized = this.normalize(fileName);
|
|
126
|
+
this.registry.remove(normalized);
|
|
127
|
+
this.rootNames.delete(normalized);
|
|
128
|
+
this.rebuild();
|
|
129
|
+
}
|
|
130
|
+
invalidate() {
|
|
131
|
+
this.registry.invalidate();
|
|
132
|
+
this.rebuild();
|
|
133
|
+
}
|
|
134
|
+
getProgram() {
|
|
135
|
+
return this.builder
|
|
136
|
+
.getProgram();
|
|
137
|
+
}
|
|
138
|
+
getChecker() {
|
|
139
|
+
return this.builder
|
|
140
|
+
.getProgram()
|
|
141
|
+
.getTypeChecker();
|
|
142
|
+
}
|
|
143
|
+
getSourceFile(fileName) {
|
|
144
|
+
return this.builder
|
|
145
|
+
.getProgram()
|
|
146
|
+
.getSourceFile(this.normalize(fileName));
|
|
147
|
+
}
|
|
148
|
+
getSemanticDiagnostics(fileName) {
|
|
149
|
+
const program = this.builder.getProgram();
|
|
150
|
+
if (!fileName) {
|
|
151
|
+
return typescript_1.default.getPreEmitDiagnostics(program);
|
|
152
|
+
}
|
|
153
|
+
const sourceFile = this.getSourceFile(fileName);
|
|
154
|
+
if (!sourceFile) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
return [
|
|
158
|
+
...program.getSemanticDiagnostics(sourceFile),
|
|
159
|
+
...program.getSyntacticDiagnostics(sourceFile),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
resolveModule(moduleName, fromFile) {
|
|
163
|
+
const resolved = typescript_1.default.resolveModuleName(moduleName, this.normalize(fromFile), this.compilerOptions, this.host);
|
|
164
|
+
return resolved
|
|
165
|
+
.resolvedModule
|
|
166
|
+
?.resolvedFileName;
|
|
167
|
+
}
|
|
168
|
+
getResolvedModules(fileName) {
|
|
169
|
+
const source = this.getSourceFile(fileName);
|
|
170
|
+
if (!source) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const modules = new Set();
|
|
174
|
+
for (const statement of source.statements) {
|
|
175
|
+
if (!typescript_1.default.isImportDeclaration(statement)
|
|
176
|
+
&& !typescript_1.default.isExportDeclaration(statement)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const specifier = statement.moduleSpecifier;
|
|
180
|
+
if (!specifier
|
|
181
|
+
|| !typescript_1.default.isStringLiteral(specifier)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const resolved = this.resolveModule(specifier.text, source.fileName);
|
|
185
|
+
if (!resolved) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
modules.add(this.normalize(resolved));
|
|
189
|
+
}
|
|
190
|
+
return [
|
|
191
|
+
...modules,
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
getAllSourceFiles() {
|
|
195
|
+
return this.builder
|
|
196
|
+
.getProgram()
|
|
197
|
+
.getSourceFiles()
|
|
198
|
+
.filter(sf => !sf.isDeclarationFile);
|
|
199
|
+
}
|
|
200
|
+
getRealFileName(fileName) {
|
|
201
|
+
return fileName;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.CompilerContext = CompilerContext;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DocumentRegistry = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
10
|
+
class DocumentRegistry {
|
|
11
|
+
documents = new Map();
|
|
12
|
+
normalize(fileName) {
|
|
13
|
+
return path_1.default.resolve(fileName)
|
|
14
|
+
.replace(/\\/g, "/");
|
|
15
|
+
}
|
|
16
|
+
has(fileName) {
|
|
17
|
+
return this.documents.has(this.normalize(fileName));
|
|
18
|
+
}
|
|
19
|
+
get(fileName) {
|
|
20
|
+
return this.documents.get(this.normalize(fileName));
|
|
21
|
+
}
|
|
22
|
+
getContent(fileName) {
|
|
23
|
+
return this.get(fileName)?.content;
|
|
24
|
+
}
|
|
25
|
+
getSnapshot(fileName) {
|
|
26
|
+
return this.get(fileName)?.snapshot;
|
|
27
|
+
}
|
|
28
|
+
getVersion(fileName) {
|
|
29
|
+
return (this.get(fileName)
|
|
30
|
+
?.version
|
|
31
|
+
.toString()
|
|
32
|
+
?? "0");
|
|
33
|
+
}
|
|
34
|
+
update(fileName, content) {
|
|
35
|
+
const normalized = this.normalize(fileName);
|
|
36
|
+
const existing = this.documents.get(normalized);
|
|
37
|
+
// critical:
|
|
38
|
+
// avoid snapshot churn
|
|
39
|
+
if (existing &&
|
|
40
|
+
existing.content === content) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.documents.set(normalized, {
|
|
44
|
+
fileName: normalized,
|
|
45
|
+
content,
|
|
46
|
+
version: existing
|
|
47
|
+
? existing.version + 1
|
|
48
|
+
: 1,
|
|
49
|
+
snapshot: typescript_1.default.ScriptSnapshot
|
|
50
|
+
.fromString(content),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
ensure(fileName) {
|
|
54
|
+
const normalized = this.normalize(fileName);
|
|
55
|
+
if (this.documents.has(normalized)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const content = fs_1.default.readFileSync(normalized, "utf8");
|
|
60
|
+
this.update(normalized, content);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore missing files
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
remove(fileName) {
|
|
67
|
+
this.documents.delete(this.normalize(fileName));
|
|
68
|
+
}
|
|
69
|
+
getFileNames() {
|
|
70
|
+
return [
|
|
71
|
+
...this.documents.keys()
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
invalidate() {
|
|
75
|
+
this.documents.clear();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.DocumentRegistry = DocumentRegistry;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/compiler/importGraph.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ImportGraph = void 0;
|
|
5
|
+
class ImportGraph {
|
|
6
|
+
// file -> imports
|
|
7
|
+
imports = new Map();
|
|
8
|
+
// import -> importers
|
|
9
|
+
reverse = new Map();
|
|
10
|
+
updateFile(file, nextImports) {
|
|
11
|
+
const previous = this.imports.get(file)
|
|
12
|
+
?? new Set();
|
|
13
|
+
const next = new Set(nextImports);
|
|
14
|
+
// remove old edges
|
|
15
|
+
for (const imported of previous) {
|
|
16
|
+
if (next.has(imported)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const reverseImporters = this.reverse.get(imported);
|
|
20
|
+
if (reverseImporters) {
|
|
21
|
+
reverseImporters.delete(file);
|
|
22
|
+
if (reverseImporters.size
|
|
23
|
+
=== 0) {
|
|
24
|
+
this.reverse.delete(imported);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// add new edges
|
|
29
|
+
for (const imported of next) {
|
|
30
|
+
if (previous.has(imported)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
let importers = this.reverse.get(imported);
|
|
34
|
+
if (!importers) {
|
|
35
|
+
importers =
|
|
36
|
+
new Set();
|
|
37
|
+
this.reverse.set(imported, importers);
|
|
38
|
+
}
|
|
39
|
+
importers.add(file);
|
|
40
|
+
}
|
|
41
|
+
this.imports.set(file, next);
|
|
42
|
+
}
|
|
43
|
+
removeFile(file) {
|
|
44
|
+
const imports = this.imports.get(file);
|
|
45
|
+
if (imports) {
|
|
46
|
+
for (const imported of imports) {
|
|
47
|
+
const importers = this.reverse.get(imported);
|
|
48
|
+
if (importers) {
|
|
49
|
+
importers.delete(file);
|
|
50
|
+
if (importers.size
|
|
51
|
+
=== 0) {
|
|
52
|
+
this.reverse.delete(imported);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.imports.delete(file);
|
|
58
|
+
this.reverse.delete(file);
|
|
59
|
+
for (const importers of this.reverse.values()) {
|
|
60
|
+
importers.delete(file);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
collectDependents(file) {
|
|
64
|
+
const result = new Set();
|
|
65
|
+
const queue = [file];
|
|
66
|
+
while (queue.length) {
|
|
67
|
+
const current = queue.pop();
|
|
68
|
+
if (result.has(current)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
result.add(current);
|
|
72
|
+
const importers = this.reverse.get(current);
|
|
73
|
+
if (!importers) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const importer of importers) {
|
|
77
|
+
queue.push(importer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.ImportGraph = ImportGraph;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scheduler = void 0;
|
|
4
|
+
class Scheduler {
|
|
5
|
+
flush;
|
|
6
|
+
pending = new Map();
|
|
7
|
+
scheduled = false;
|
|
8
|
+
currentFlush = null;
|
|
9
|
+
constructor(flush) {
|
|
10
|
+
this.flush = flush;
|
|
11
|
+
}
|
|
12
|
+
async enqueue(fileName, content) {
|
|
13
|
+
this.pending.set(fileName, content);
|
|
14
|
+
if (!this.scheduled) {
|
|
15
|
+
this.scheduled = true;
|
|
16
|
+
this.currentFlush =
|
|
17
|
+
Promise.resolve()
|
|
18
|
+
.then(() => this.run());
|
|
19
|
+
}
|
|
20
|
+
await this.currentFlush;
|
|
21
|
+
}
|
|
22
|
+
async run() {
|
|
23
|
+
this.scheduled = false;
|
|
24
|
+
const batch = new Map(this.pending);
|
|
25
|
+
this.pending.clear();
|
|
26
|
+
await this.flush(batch);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.Scheduler = Scheduler;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/engine/engine.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.EventraEngine = void 0;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
10
|
+
const compilerContext_1 = require("../compiler/compilerContext");
|
|
11
|
+
const scheduler_1 = require("../compiler/scheduler");
|
|
12
|
+
const importGraph_1 = require("../compiler/importGraph");
|
|
13
|
+
const evaluationCache_1 = require("../analysis/cache/evaluationCache");
|
|
14
|
+
const resolvedCallCache_1 = require("../analysis/cache/resolvedCallCache");
|
|
15
|
+
const resolvedExportCache_1 = require("../analysis/cache/resolvedExportCache");
|
|
16
|
+
const symbolInvalidation_1 = require("../analysis/cache/symbolInvalidation");
|
|
17
|
+
const scanner_1 = require("../analysis/scanner/scanner");
|
|
18
|
+
const extractor_1 = require("../analysis/extractor/extractor");
|
|
19
|
+
const wrapperRegistry_1 = require("../analysis/symbols/wrapperRegistry");
|
|
20
|
+
const EMPTY_RESULT = () => ({
|
|
21
|
+
events: new Set(),
|
|
22
|
+
detectedFunctionWrappers: new Set(),
|
|
23
|
+
detectedComponentWrappers: new Map(),
|
|
24
|
+
});
|
|
25
|
+
class EventraEngine {
|
|
26
|
+
compiler;
|
|
27
|
+
scheduler;
|
|
28
|
+
importGraph = new importGraph_1.ImportGraph();
|
|
29
|
+
evaluationCache = new evaluationCache_1.EvaluationCache();
|
|
30
|
+
resolvedCallCache = new resolvedCallCache_1.ResolvedCallCache();
|
|
31
|
+
resolvedExportCache = new resolvedExportCache_1.ResolvedExportCache();
|
|
32
|
+
wrapperRegistry;
|
|
33
|
+
fileResults = new Map();
|
|
34
|
+
normalizedPathCache = new Map();
|
|
35
|
+
isPreloading = false;
|
|
36
|
+
lastConfig = {
|
|
37
|
+
events: [],
|
|
38
|
+
wrappers: [],
|
|
39
|
+
functionWrappers: [],
|
|
40
|
+
sync: {
|
|
41
|
+
include: [],
|
|
42
|
+
exclude: [],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
constructor(rootDir) {
|
|
46
|
+
this.compiler =
|
|
47
|
+
new compilerContext_1.CompilerContext(rootDir);
|
|
48
|
+
this.wrapperRegistry = new wrapperRegistry_1.WrapperRegistry();
|
|
49
|
+
this.scheduler =
|
|
50
|
+
new scheduler_1.Scheduler(async (updates) => {
|
|
51
|
+
//
|
|
52
|
+
// APPLY UPDATES
|
|
53
|
+
//
|
|
54
|
+
for (const [file, content] of updates) {
|
|
55
|
+
//
|
|
56
|
+
// SYMBOL INVALIDATION
|
|
57
|
+
//
|
|
58
|
+
const existing = this.compiler.getSourceFile(file);
|
|
59
|
+
if (existing) {
|
|
60
|
+
(0, symbolInvalidation_1.invalidateSourceFileSymbols)(existing, this.compiler.getChecker(), this.evaluationCache, this.resolvedCallCache, this.resolvedExportCache);
|
|
61
|
+
}
|
|
62
|
+
//
|
|
63
|
+
// UPDATE FILE
|
|
64
|
+
//
|
|
65
|
+
await this.compiler.updateFile(file, content);
|
|
66
|
+
//
|
|
67
|
+
// UPDATE IMPORT GRAPH
|
|
68
|
+
//
|
|
69
|
+
this.updateImportGraph(file);
|
|
70
|
+
}
|
|
71
|
+
//
|
|
72
|
+
// COLLECT AFFECTED FILES
|
|
73
|
+
//
|
|
74
|
+
const affected = new Set();
|
|
75
|
+
for (const file of updates.keys()) {
|
|
76
|
+
const dependents = this.importGraph
|
|
77
|
+
.collectDependents(file);
|
|
78
|
+
for (const dependent of dependents) {
|
|
79
|
+
affected.add(dependent);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//
|
|
83
|
+
// RE-ANALYZE
|
|
84
|
+
//
|
|
85
|
+
for (const file of affected) {
|
|
86
|
+
this.analyzeFile(file, this.lastConfig);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
invalidateCaches() {
|
|
91
|
+
this.evaluationCache
|
|
92
|
+
.clear();
|
|
93
|
+
this.resolvedCallCache
|
|
94
|
+
.clear();
|
|
95
|
+
this.resolvedExportCache
|
|
96
|
+
.clear();
|
|
97
|
+
this.wrapperRegistry
|
|
98
|
+
.clear();
|
|
99
|
+
}
|
|
100
|
+
beginPreload() {
|
|
101
|
+
this.isPreloading =
|
|
102
|
+
true;
|
|
103
|
+
}
|
|
104
|
+
endPreload() {
|
|
105
|
+
this.isPreloading =
|
|
106
|
+
false;
|
|
107
|
+
}
|
|
108
|
+
normalize(fileName) {
|
|
109
|
+
const cached = this.normalizedPathCache.get(fileName);
|
|
110
|
+
if (cached) {
|
|
111
|
+
return cached;
|
|
112
|
+
}
|
|
113
|
+
const normalized = path_1.default.resolve(fileName)
|
|
114
|
+
.replace(/\\/g, "/");
|
|
115
|
+
this.normalizedPathCache.set(fileName, normalized);
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
updateImportGraph(fileName) {
|
|
119
|
+
const normalized = this.normalize(fileName);
|
|
120
|
+
const source = this.compiler.getSourceFile(normalized);
|
|
121
|
+
if (!source) {
|
|
122
|
+
this.importGraph.removeFile(normalized);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const imports = [];
|
|
126
|
+
for (const statement of source.statements) {
|
|
127
|
+
if (typescript_1.default.isExportAssignment(statement)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!typescript_1.default.isImportDeclaration(statement)
|
|
131
|
+
&& !typescript_1.default.isExportDeclaration(statement)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const specifier = statement.moduleSpecifier;
|
|
135
|
+
if (!specifier
|
|
136
|
+
|| !typescript_1.default.isStringLiteral(specifier)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const resolved = this.compiler.resolveModule(specifier.text, this.compiler.getRealFileName(source.fileName));
|
|
140
|
+
if (!resolved) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
imports.push(this.normalize(resolved));
|
|
144
|
+
}
|
|
145
|
+
this.importGraph.updateFile(normalized, imports);
|
|
146
|
+
}
|
|
147
|
+
analyzeFile(fileName, config) {
|
|
148
|
+
const normalized = this.normalize(fileName);
|
|
149
|
+
const source = this.compiler.getSourceFile(normalized);
|
|
150
|
+
if (!source) {
|
|
151
|
+
this.fileResults.delete(normalized);
|
|
152
|
+
return EMPTY_RESULT();
|
|
153
|
+
}
|
|
154
|
+
const checker = this.compiler.getChecker();
|
|
155
|
+
const index = (0, scanner_1.scanSource)(source, checker, config, this.resolvedCallCache, this.resolvedExportCache, this.wrapperRegistry);
|
|
156
|
+
const result = (0, extractor_1.extractEvents)(index, checker, config, this.evaluationCache, this.resolvedExportCache, this.resolvedCallCache, this.wrapperRegistry);
|
|
157
|
+
this.fileResults.set(normalized, result);
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
async preloadFile(fileName, content) {
|
|
161
|
+
if (!this.isPreloading) {
|
|
162
|
+
throw new Error("preload phase not active");
|
|
163
|
+
}
|
|
164
|
+
const normalized = this.normalize(fileName);
|
|
165
|
+
await this.compiler.updateFile(normalized, content);
|
|
166
|
+
this.updateImportGraph(normalized);
|
|
167
|
+
}
|
|
168
|
+
async updateFile(fileName, content, config) {
|
|
169
|
+
this.lastConfig =
|
|
170
|
+
config;
|
|
171
|
+
const normalized = this.normalize(fileName);
|
|
172
|
+
await this.scheduler.enqueue(normalized, content);
|
|
173
|
+
}
|
|
174
|
+
scanFile(fileName, config) {
|
|
175
|
+
this.lastConfig =
|
|
176
|
+
config;
|
|
177
|
+
return this.analyzeFile(fileName, config);
|
|
178
|
+
}
|
|
179
|
+
async syncFile(fileName, content, config) {
|
|
180
|
+
this.lastConfig =
|
|
181
|
+
config;
|
|
182
|
+
const normalized = this.normalize(fileName);
|
|
183
|
+
const existing = this.compiler.getSourceFile(normalized);
|
|
184
|
+
if (existing) {
|
|
185
|
+
(0, symbolInvalidation_1.invalidateSourceFileSymbols)(existing, this.compiler.getChecker(), this.evaluationCache, this.resolvedCallCache, this.resolvedExportCache);
|
|
186
|
+
}
|
|
187
|
+
await this.compiler.updateFile(normalized, content);
|
|
188
|
+
this.updateImportGraph(normalized);
|
|
189
|
+
return this.analyzeFile(normalized, config);
|
|
190
|
+
}
|
|
191
|
+
async removeFile(fileName, config) {
|
|
192
|
+
const normalized = this.normalize(fileName);
|
|
193
|
+
this.invalidateCaches();
|
|
194
|
+
const affected = this.importGraph
|
|
195
|
+
.collectDependents(normalized);
|
|
196
|
+
this.importGraph.removeFile(normalized);
|
|
197
|
+
this.compiler.removeFile(normalized);
|
|
198
|
+
this.fileResults.delete(normalized);
|
|
199
|
+
this.normalizedPathCache.delete(fileName);
|
|
200
|
+
this.normalizedPathCache.delete(normalized);
|
|
201
|
+
const nextConfig = config
|
|
202
|
+
?? this.lastConfig;
|
|
203
|
+
for (const file of affected) {
|
|
204
|
+
if (file === normalized) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
this.analyzeFile(file, nextConfig);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
getResult(fileName) {
|
|
211
|
+
return (this.fileResults.get(this.normalize(fileName))
|
|
212
|
+
?? EMPTY_RESULT());
|
|
213
|
+
}
|
|
214
|
+
getAllEvents() {
|
|
215
|
+
const events = new Set();
|
|
216
|
+
for (const result of this.fileResults.values()) {
|
|
217
|
+
for (const event of result.events) {
|
|
218
|
+
events.add(event);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return [
|
|
222
|
+
...events,
|
|
223
|
+
].sort();
|
|
224
|
+
}
|
|
225
|
+
getDiagnostics(fileName) {
|
|
226
|
+
return this.compiler
|
|
227
|
+
.getSemanticDiagnostics(fileName);
|
|
228
|
+
}
|
|
229
|
+
invalidate() {
|
|
230
|
+
this.invalidateCaches();
|
|
231
|
+
this.fileResults.clear();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.EventraEngine = EventraEngine;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/projectScanner.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.scanProject = scanProject;
|
|
8
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const EventraEngine_1 = require("./EventraEngine");
|
|
12
|
+
const processFile_1 = require("../filesystem/processFile");
|
|
13
|
+
const getVirtualFile_1 = require("../filesystem/getVirtualFile");
|
|
14
|
+
async function getParsedFile(file, cache) {
|
|
15
|
+
const existing = cache.get(file);
|
|
16
|
+
if (existing) {
|
|
17
|
+
return existing;
|
|
18
|
+
}
|
|
19
|
+
const raw = await promises_1.default.readFile(file, "utf8");
|
|
20
|
+
const parsed = await (0, processFile_1.processFile)(file, raw);
|
|
21
|
+
cache.set(file, parsed);
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
async function scanProject(config) {
|
|
25
|
+
const files = await (0, fast_glob_1.default)(config.sync.include, {
|
|
26
|
+
ignore: config.sync.exclude,
|
|
27
|
+
absolute: true,
|
|
28
|
+
});
|
|
29
|
+
const engine = new EventraEngine_1.EventraEngine(process.cwd());
|
|
30
|
+
const cache = new Map();
|
|
31
|
+
const toScan = new Set();
|
|
32
|
+
// COLLECT
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = await promises_1.default.readFile(file, "utf8");
|
|
36
|
+
if (raw.length
|
|
37
|
+
> 2_000_000) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const parsed = await (0, processFile_1.processFile)(file, raw);
|
|
41
|
+
cache.set(file, parsed);
|
|
42
|
+
toScan.add(file);
|
|
43
|
+
for (const dep of parsed.dependencies) {
|
|
44
|
+
toScan.add(path_1.default.resolve(path_1.default.dirname(file), dep));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
}
|
|
49
|
+
// PRELOAD
|
|
50
|
+
engine.beginPreload();
|
|
51
|
+
for (const file of toScan) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = await getParsedFile(file, cache);
|
|
54
|
+
await engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
}
|
|
58
|
+
engine.endPreload();
|
|
59
|
+
// SCAN
|
|
60
|
+
const results = new Map();
|
|
61
|
+
for (const file of toScan) {
|
|
62
|
+
try {
|
|
63
|
+
const result = engine.scanFile((0, getVirtualFile_1.getVirtualFile)(file), config);
|
|
64
|
+
results.set(file, result);
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
engine,
|
|
70
|
+
results,
|
|
71
|
+
files,
|
|
72
|
+
};
|
|
73
|
+
}
|