@eventra_dev/eventra-cli 0.1.32 → 0.1.34
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/ARCHITECTURE.md +88 -0
- package/dist/engine/callResolver.js +32 -131
- package/dist/engine/languageService.js +7 -38
- package/dist/engine/resolver.js +88 -77
- package/dist/engine/scanner.js +123 -201
- package/package.json +1 -1
- package/src/engine/callResolver.ts +24 -159
- package/src/engine/languageService.ts +22 -52
- package/src/engine/resolver.ts +99 -120
- package/src/engine/scanner.ts +128 -262
|
@@ -7,13 +7,9 @@ export class TSService {
|
|
|
7
7
|
private fileNames = new Set<string>();
|
|
8
8
|
private service: ts.LanguageService;
|
|
9
9
|
|
|
10
|
-
private
|
|
11
|
-
private
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
private usage = new Map<string, number>();
|
|
15
|
-
private tick = 0;
|
|
16
|
-
private MAX_FILES = 2000;
|
|
10
|
+
private compilerOptions: ts.CompilerOptions;
|
|
11
|
+
private baseUrl: string;
|
|
12
|
+
private paths: Record<string, string[]>;
|
|
17
13
|
|
|
18
14
|
constructor(private root: string) {
|
|
19
15
|
const configPath = ts.findConfigFile(
|
|
@@ -61,16 +57,19 @@ export class TSService {
|
|
|
61
57
|
this.baseUrl = baseUrl;
|
|
62
58
|
this.paths = paths;
|
|
63
59
|
|
|
60
|
+
const allFiles = ts.sys.readDirectory(
|
|
61
|
+
root,
|
|
62
|
+
[".ts", ".tsx", ".js", ".jsx"],
|
|
63
|
+
undefined,
|
|
64
|
+
undefined
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
for (const f of allFiles) {
|
|
68
|
+
this.fileNames.add(this.normalize(f));
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
const host: ts.LanguageServiceHost = {
|
|
65
72
|
getScriptFileNames: () => {
|
|
66
|
-
if (this.fileNames.size === 0) {
|
|
67
|
-
return ts.sys.readDirectory(
|
|
68
|
-
this.root,
|
|
69
|
-
[".ts", ".tsx", ".js", ".jsx"],
|
|
70
|
-
undefined,
|
|
71
|
-
undefined
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
73
|
return Array.from(this.fileNames);
|
|
75
74
|
},
|
|
76
75
|
|
|
@@ -81,11 +80,14 @@ export class TSService {
|
|
|
81
80
|
|
|
82
81
|
getScriptSnapshot: (file) => {
|
|
83
82
|
file = this.normalize(file);
|
|
83
|
+
|
|
84
|
+
// из памяти
|
|
84
85
|
const f = this.files.get(file);
|
|
85
86
|
if (f) {
|
|
86
|
-
this.touch(file);
|
|
87
87
|
return ts.ScriptSnapshot.fromString(f.content);
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
// virtual TSX (vue/svelte/astro)
|
|
89
91
|
if (file.endsWith(".tsx")) {
|
|
90
92
|
const original = file.replace(/\.tsx$/, "");
|
|
91
93
|
|
|
@@ -98,12 +100,11 @@ export class TSService {
|
|
|
98
100
|
});
|
|
99
101
|
|
|
100
102
|
this.fileNames.add(file);
|
|
101
|
-
this.touch(file);
|
|
102
|
-
this.evictIfNeeded();
|
|
103
103
|
|
|
104
104
|
return ts.ScriptSnapshot.fromString(content);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
|
|
107
108
|
try {
|
|
108
109
|
if (fs.existsSync(file)) {
|
|
109
110
|
const content = fs.readFileSync(file, "utf-8");
|
|
@@ -114,8 +115,6 @@ export class TSService {
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
this.fileNames.add(file);
|
|
117
|
-
this.touch(file);
|
|
118
|
-
this.evictIfNeeded();
|
|
119
118
|
|
|
120
119
|
return ts.ScriptSnapshot.fromString(content);
|
|
121
120
|
}
|
|
@@ -123,10 +122,12 @@ export class TSService {
|
|
|
123
122
|
|
|
124
123
|
return ts.ScriptSnapshot.fromString("");
|
|
125
124
|
},
|
|
125
|
+
|
|
126
126
|
getCurrentDirectory: () => this.root,
|
|
127
127
|
getCompilationSettings: () => this.compilerOptions,
|
|
128
128
|
getDefaultLibFileName: (opts) =>
|
|
129
129
|
ts.getDefaultLibFilePath(opts),
|
|
130
|
+
|
|
130
131
|
fileExists: (file) => {
|
|
131
132
|
file = this.normalize(file);
|
|
132
133
|
return this.files.has(file) || ts.sys.fileExists(file);
|
|
@@ -143,41 +144,13 @@ export class TSService {
|
|
|
143
144
|
this.service = ts.createLanguageService(host);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
// INTERNAL
|
|
147
147
|
private normalize(file: string) {
|
|
148
148
|
return path.resolve(file).replace(/\\/g, "/");
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
private touch(file: string) {
|
|
152
|
-
this.tick++;
|
|
153
|
-
this.usage.set(file, this.tick);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private evictIfNeeded() {
|
|
157
|
-
if (this.fileNames.size <= this.MAX_FILES) return;
|
|
158
|
-
|
|
159
|
-
let oldest: string | null = null;
|
|
160
|
-
let oldestTick = Infinity;
|
|
161
|
-
|
|
162
|
-
for (const f of this.fileNames) {
|
|
163
|
-
const t = this.usage.get(f) ?? 0;
|
|
164
|
-
|
|
165
|
-
if (t < oldestTick) {
|
|
166
|
-
oldestTick = t;
|
|
167
|
-
oldest = f;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (oldest) {
|
|
172
|
-
this.files.delete(oldest);
|
|
173
|
-
this.fileNames.delete(oldest);
|
|
174
|
-
this.usage.delete(oldest);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
151
|
private ensureProgram() {
|
|
179
152
|
try {
|
|
180
|
-
this.service.getProgram()
|
|
153
|
+
this.service.getProgram();
|
|
181
154
|
} catch {}
|
|
182
155
|
}
|
|
183
156
|
|
|
@@ -193,8 +166,6 @@ export class TSService {
|
|
|
193
166
|
});
|
|
194
167
|
|
|
195
168
|
this.fileNames.add(file);
|
|
196
|
-
this.touch(file);
|
|
197
|
-
this.evictIfNeeded();
|
|
198
169
|
|
|
199
170
|
this.ensureProgram();
|
|
200
171
|
}
|
|
@@ -204,7 +175,6 @@ export class TSService {
|
|
|
204
175
|
|
|
205
176
|
this.files.delete(file);
|
|
206
177
|
this.fileNames.delete(file);
|
|
207
|
-
this.usage.delete(file);
|
|
208
178
|
|
|
209
179
|
this.ensureProgram();
|
|
210
180
|
}
|
package/src/engine/resolver.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveExportedSymbol } from "./exportResolver";
|
|
3
|
+
import { resolveFunctionFromCall } from "./callResolver";
|
|
3
4
|
|
|
4
5
|
export type ResolveResult = {
|
|
5
6
|
values: string[];
|
|
@@ -12,167 +13,139 @@ export function resolveNodeValue(
|
|
|
12
13
|
paramMap?: Map<string, ts.Expression>,
|
|
13
14
|
seen = new Set<ts.Node>()
|
|
14
15
|
): ResolveResult {
|
|
15
|
-
if (seen.has(node)) {
|
|
16
|
-
return { values: [], dynamic: true };
|
|
17
|
-
}
|
|
16
|
+
if (seen.has(node)) return { values: [], dynamic: true };
|
|
18
17
|
seen.add(node);
|
|
19
18
|
|
|
20
|
-
//
|
|
19
|
+
// param passthrough
|
|
21
20
|
if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
22
|
-
return resolveNodeValue(
|
|
23
|
-
paramMap.get(node.text)!,
|
|
24
|
-
checker,
|
|
25
|
-
paramMap,
|
|
26
|
-
seen
|
|
27
|
-
);
|
|
21
|
+
return resolveNodeValue(paramMap.get(node.text)!, checker, paramMap, seen);
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
//
|
|
24
|
+
// string
|
|
31
25
|
if (ts.isStringLiteral(node)) {
|
|
32
26
|
return { values: [node.text], dynamic: false };
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
//
|
|
29
|
+
// template
|
|
36
30
|
if (ts.isTemplateExpression(node)) {
|
|
37
31
|
let results = node.head.text ? [node.head.text] : [""];
|
|
38
|
-
|
|
39
32
|
for (const span of node.templateSpans) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
paramMap,
|
|
44
|
-
seen
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
results = results.flatMap(r =>
|
|
48
|
-
res.values.length
|
|
49
|
-
? res.values.map(v => r + v)
|
|
50
|
-
: [r + "*"]
|
|
33
|
+
const r = resolveNodeValue(span.expression, checker, paramMap, seen);
|
|
34
|
+
results = results.flatMap(s =>
|
|
35
|
+
r.values.length ? r.values.map(v => s + v) : [s + "*"]
|
|
51
36
|
);
|
|
52
|
-
|
|
53
|
-
results = results.map(r => r + span.literal.text);
|
|
37
|
+
results = results.map(s => s + span.literal.text);
|
|
54
38
|
}
|
|
55
|
-
|
|
56
39
|
return { values: results, dynamic: true };
|
|
57
40
|
}
|
|
58
41
|
|
|
59
|
-
//
|
|
42
|
+
// ternary
|
|
60
43
|
if (ts.isConditionalExpression(node)) {
|
|
61
44
|
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
62
45
|
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
63
|
-
|
|
64
46
|
return {
|
|
65
47
|
values: [...new Set([...a.values, ...b.values])],
|
|
66
48
|
dynamic: true,
|
|
67
49
|
};
|
|
68
50
|
}
|
|
69
51
|
|
|
70
|
-
//
|
|
52
|
+
// object (event/name priority)
|
|
71
53
|
if (ts.isObjectLiteralExpression(node)) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
checker,
|
|
80
|
-
paramMap,
|
|
81
|
-
seen
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
values.push(...res.values);
|
|
54
|
+
for (const p of node.properties) {
|
|
55
|
+
if (ts.isPropertyAssignment(p)) {
|
|
56
|
+
const key = p.name.getText();
|
|
57
|
+
if (key === "event" || key === "name") {
|
|
58
|
+
return resolveNodeValue(p.initializer, checker, paramMap, seen);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
85
61
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
62
|
+
const vals: string[] = [];
|
|
63
|
+
for (const p of node.properties) {
|
|
64
|
+
if (!ts.isPropertyAssignment(p)) continue;
|
|
65
|
+
const r = resolveNodeValue(p.initializer, checker, paramMap, seen);
|
|
66
|
+
vals.push(...r.values);
|
|
67
|
+
}
|
|
68
|
+
return { values: [...new Set(vals)], dynamic: true };
|
|
91
69
|
}
|
|
92
70
|
|
|
93
|
-
//
|
|
71
|
+
// array
|
|
94
72
|
if (ts.isArrayLiteralExpression(node)) {
|
|
95
|
-
const
|
|
96
|
-
|
|
73
|
+
const vals: string[] = [];
|
|
97
74
|
for (const el of node.elements) {
|
|
98
|
-
const
|
|
99
|
-
|
|
75
|
+
const r = resolveNodeValue(el, checker, paramMap, seen);
|
|
76
|
+
vals.push(...r.values);
|
|
100
77
|
}
|
|
78
|
+
return { values: [...new Set(vals)], dynamic: true };
|
|
79
|
+
}
|
|
101
80
|
|
|
81
|
+
// concat
|
|
82
|
+
if (ts.isBinaryExpression(node) &&
|
|
83
|
+
node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
84
|
+
const l = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
85
|
+
const r = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
102
86
|
return {
|
|
103
|
-
values:
|
|
87
|
+
values: l.values.flatMap(a =>
|
|
88
|
+
r.values.length ? r.values.map(b => a + b) : [a + "*"]
|
|
89
|
+
),
|
|
104
90
|
dynamic: true,
|
|
105
91
|
};
|
|
106
92
|
}
|
|
107
93
|
|
|
108
|
-
//
|
|
109
|
-
if (ts.isBinaryExpression(node)) {
|
|
110
|
-
if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
111
|
-
const left = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
112
|
-
const right = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
values: left.values.flatMap(l =>
|
|
116
|
-
right.values.length
|
|
117
|
-
? right.values.map(r => l + r)
|
|
118
|
-
: [l + "*"]
|
|
119
|
-
),
|
|
120
|
-
dynamic: true,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// IDENTIFIER
|
|
94
|
+
// identifier (const / enum)
|
|
126
95
|
if (ts.isIdentifier(node)) {
|
|
127
|
-
let
|
|
128
|
-
if (!
|
|
129
|
-
|
|
130
|
-
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
131
|
-
|
|
132
|
-
// const VAR = "click"
|
|
133
|
-
for (const decl of symbol.getDeclarations() ?? []) {
|
|
134
|
-
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
135
|
-
return resolveNodeValue(
|
|
136
|
-
decl.initializer,
|
|
137
|
-
checker,
|
|
138
|
-
paramMap,
|
|
139
|
-
seen
|
|
140
|
-
);
|
|
141
|
-
}
|
|
96
|
+
let s = checker.getSymbolAtLocation(node);
|
|
97
|
+
if (!s) return { values: [], dynamic: true };
|
|
142
98
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
99
|
+
s = resolveExportedSymbol(s, checker) ?? s;
|
|
100
|
+
|
|
101
|
+
for (const d of s.getDeclarations() ?? []) {
|
|
102
|
+
if (ts.isVariableDeclaration(d) && d.initializer) {
|
|
103
|
+
return resolveNodeValue(d.initializer, checker, paramMap, seen);
|
|
104
|
+
}
|
|
105
|
+
if (ts.isEnumMember(d) && d.initializer && ts.isStringLiteral(d.initializer)) {
|
|
106
|
+
return { values: [d.initializer.text], dynamic: false };
|
|
151
107
|
}
|
|
152
108
|
}
|
|
153
|
-
|
|
154
109
|
return { values: [], dynamic: true };
|
|
155
110
|
}
|
|
156
111
|
|
|
157
|
-
// PROPERTY ACCESS (
|
|
112
|
+
// PROPERTY ACCESS (OBJECT + ENUM)
|
|
158
113
|
if (ts.isPropertyAccessExpression(node)) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
114
|
+
const obj = node.expression;
|
|
115
|
+
const prop = node.name.text;
|
|
116
|
+
|
|
117
|
+
// object literal lookup
|
|
118
|
+
if (ts.isIdentifier(obj)) {
|
|
119
|
+
const sym = checker.getSymbolAtLocation(obj);
|
|
120
|
+
if (sym) {
|
|
121
|
+
const s = resolveExportedSymbol(sym, checker) ?? sym;
|
|
122
|
+
for (const d of s.getDeclarations() ?? []) {
|
|
123
|
+
if (
|
|
124
|
+
ts.isVariableDeclaration(d) &&
|
|
125
|
+
d.initializer &&
|
|
126
|
+
ts.isObjectLiteralExpression(d.initializer)
|
|
127
|
+
) {
|
|
128
|
+
for (const p of d.initializer.properties) {
|
|
129
|
+
if (
|
|
130
|
+
ts.isPropertyAssignment(p) &&
|
|
131
|
+
p.name.getText() === prop
|
|
132
|
+
) {
|
|
133
|
+
return resolveNodeValue(p.initializer, checker, paramMap, seen);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
166
138
|
}
|
|
139
|
+
}
|
|
167
140
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
141
|
+
// enum fallback
|
|
142
|
+
const sym = checker.getSymbolAtLocation(node);
|
|
143
|
+
if (sym) {
|
|
144
|
+
let s = resolveExportedSymbol(sym, checker) ?? sym;
|
|
145
|
+
if (s.flags & ts.SymbolFlags.Alias) s = checker.getAliasedSymbol(s);
|
|
146
|
+
for (const d of s.getDeclarations() ?? []) {
|
|
147
|
+
if (ts.isEnumMember(d) && d.initializer && ts.isStringLiteral(d.initializer)) {
|
|
148
|
+
return { values: [d.initializer.text], dynamic: false };
|
|
176
149
|
}
|
|
177
150
|
}
|
|
178
151
|
}
|
|
@@ -180,18 +153,24 @@ export function resolveNodeValue(
|
|
|
180
153
|
return { values: [], dynamic: true };
|
|
181
154
|
}
|
|
182
155
|
|
|
183
|
-
// CALL
|
|
156
|
+
// CALL (unwrap + dive into wrapper body)
|
|
184
157
|
if (ts.isCallExpression(node)) {
|
|
185
|
-
const
|
|
158
|
+
const vals: string[] = [];
|
|
159
|
+
|
|
160
|
+
// args
|
|
161
|
+
for (const a of node.arguments) {
|
|
162
|
+
const r = resolveNodeValue(a, checker, paramMap, seen);
|
|
163
|
+
vals.push(...r.values);
|
|
164
|
+
}
|
|
186
165
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
166
|
+
// dive into called function (wrapper support)
|
|
167
|
+
const fn = resolveFunctionFromCall(node.expression, checker);
|
|
168
|
+
if (fn?.body) {
|
|
169
|
+
const r = resolveNodeValue(fn.body, checker, paramMap, seen);
|
|
170
|
+
vals.push(...r.values);
|
|
190
171
|
}
|
|
191
172
|
|
|
192
|
-
return values
|
|
193
|
-
? { values, dynamic: true }
|
|
194
|
-
: { values: [], dynamic: true };
|
|
173
|
+
return { values: [...new Set(vals)], dynamic: true };
|
|
195
174
|
}
|
|
196
175
|
|
|
197
176
|
return { values: [], dynamic: true };
|