@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
|
@@ -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
|
|
|
@@ -7,9 +7,12 @@ export class TSService {
|
|
|
7
7
|
private fileNames = new Set<string>();
|
|
8
8
|
private service: ts.LanguageService;
|
|
9
9
|
|
|
10
|
-
private
|
|
11
|
-
private
|
|
12
|
-
private
|
|
10
|
+
private compilerOptions: ts.CompilerOptions;
|
|
11
|
+
private baseUrl: string;
|
|
12
|
+
private paths: Record<string, string[]>;
|
|
13
|
+
private usage = new Map<string, number>();
|
|
14
|
+
private tick = 0;
|
|
15
|
+
private MAX_FILES = 2000;
|
|
13
16
|
|
|
14
17
|
constructor(private root: string) {
|
|
15
18
|
const configPath = ts.findConfigFile(
|
|
@@ -20,13 +23,18 @@ export class TSService {
|
|
|
20
23
|
|
|
21
24
|
let compilerOptions: ts.CompilerOptions = {
|
|
22
25
|
allowJs: true,
|
|
23
|
-
jsx: ts.JsxEmit.
|
|
26
|
+
jsx: ts.JsxEmit.React,
|
|
27
|
+
jsxImportSource: "react",
|
|
24
28
|
moduleResolution: ts.ModuleResolutionKind.Node16,
|
|
25
29
|
target: ts.ScriptTarget.ESNext,
|
|
26
30
|
esModuleInterop: true,
|
|
27
31
|
allowSyntheticDefaultImports: true,
|
|
32
|
+
skipLibCheck: true,
|
|
28
33
|
};
|
|
29
34
|
|
|
35
|
+
let baseUrl = root;
|
|
36
|
+
let paths: Record<string, string[]> = {};
|
|
37
|
+
|
|
30
38
|
if (configPath) {
|
|
31
39
|
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
32
40
|
|
|
@@ -41,56 +49,108 @@ export class TSService {
|
|
|
41
49
|
...parsed.options,
|
|
42
50
|
};
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
paths = parsed.options.paths || {};
|
|
53
|
+
baseUrl = parsed.options.baseUrl
|
|
46
54
|
? path.resolve(root, parsed.options.baseUrl)
|
|
47
55
|
: root;
|
|
48
|
-
} else {
|
|
49
|
-
this.baseUrl = root;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
this.compilerOptions = compilerOptions;
|
|
59
|
+
this.baseUrl = baseUrl;
|
|
60
|
+
this.paths = paths;
|
|
53
61
|
|
|
54
62
|
const host: ts.LanguageServiceHost = {
|
|
55
|
-
getScriptFileNames: () =>
|
|
56
|
-
return [...new Set(this.fileNames)].sort((a, b) =>
|
|
57
|
-
a.localeCompare(b)
|
|
58
|
-
);
|
|
59
|
-
},
|
|
63
|
+
getScriptFileNames: () => Array.from(this.fileNames),
|
|
60
64
|
|
|
61
|
-
getScriptVersion: (file) =>
|
|
62
|
-
this.
|
|
65
|
+
getScriptVersion: (file) => {
|
|
66
|
+
file = this.normalize(file);
|
|
67
|
+
return this.files.get(file)?.version.toString() ?? "0";
|
|
68
|
+
},
|
|
63
69
|
|
|
64
70
|
getScriptSnapshot: (file) => {
|
|
65
|
-
|
|
71
|
+
file = this.normalize(file);
|
|
66
72
|
|
|
73
|
+
const f = this.files.get(file);
|
|
67
74
|
if (f) {
|
|
75
|
+
this.touch(file);
|
|
68
76
|
return ts.ScriptSnapshot.fromString(f.content);
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fs.readFileSync(file, "utf-8")
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(file)) {
|
|
81
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
82
|
+
|
|
83
|
+
this.files.set(file, {
|
|
84
|
+
content,
|
|
85
|
+
version: 1,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.fileNames.add(file);
|
|
89
|
+
this.touch(file);
|
|
90
|
+
this.evictIfNeeded();
|
|
76
91
|
|
|
77
|
-
|
|
92
|
+
return ts.ScriptSnapshot.fromString(content);
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
|
|
96
|
+
// fallback
|
|
97
|
+
return ts.ScriptSnapshot.fromString("");
|
|
78
98
|
},
|
|
79
99
|
|
|
80
100
|
getCurrentDirectory: () => this.root,
|
|
81
101
|
getCompilationSettings: () => this.compilerOptions,
|
|
82
102
|
getDefaultLibFileName: (opts) =>
|
|
83
103
|
ts.getDefaultLibFilePath(opts),
|
|
84
|
-
|
|
85
|
-
readFile: ts.sys.readFile,
|
|
86
104
|
fileExists: ts.sys.fileExists,
|
|
105
|
+
readFile: ts.sys.readFile,
|
|
87
106
|
readDirectory: ts.sys.readDirectory,
|
|
88
107
|
};
|
|
89
108
|
|
|
90
109
|
this.service = ts.createLanguageService(host);
|
|
91
110
|
}
|
|
92
111
|
|
|
112
|
+
// INTERNAL
|
|
113
|
+
private normalize(file: string) {
|
|
114
|
+
return path.resolve(file).replace(/\\/g, "/");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private touch(file: string) {
|
|
118
|
+
this.tick++;
|
|
119
|
+
this.usage.set(file, this.tick);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private evictIfNeeded() {
|
|
123
|
+
if (this.fileNames.size <= this.MAX_FILES) return;
|
|
124
|
+
|
|
125
|
+
let oldestFile: string | null = null;
|
|
126
|
+
let oldestTick = Infinity;
|
|
127
|
+
|
|
128
|
+
for (const f of this.fileNames) {
|
|
129
|
+
const t = this.usage.get(f) ?? 0;
|
|
130
|
+
|
|
131
|
+
if (t < oldestTick) {
|
|
132
|
+
oldestTick = t;
|
|
133
|
+
oldestFile = f;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (oldestFile) {
|
|
138
|
+
this.files.delete(oldestFile);
|
|
139
|
+
this.fileNames.delete(oldestFile);
|
|
140
|
+
this.usage.delete(oldestFile);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private ensureProgram() {
|
|
145
|
+
try {
|
|
146
|
+
this.service.getProgram();
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// PUBLIC API
|
|
93
151
|
updateFile(file: string, content: string) {
|
|
152
|
+
file = this.normalize(file);
|
|
153
|
+
|
|
94
154
|
const prev = this.files.get(file);
|
|
95
155
|
|
|
96
156
|
this.files.set(file, {
|
|
@@ -99,6 +159,20 @@ export class TSService {
|
|
|
99
159
|
});
|
|
100
160
|
|
|
101
161
|
this.fileNames.add(file);
|
|
162
|
+
this.touch(file);
|
|
163
|
+
this.evictIfNeeded();
|
|
164
|
+
|
|
165
|
+
this.ensureProgram();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
removeFile(file: string) {
|
|
169
|
+
file = this.normalize(file);
|
|
170
|
+
|
|
171
|
+
this.files.delete(file);
|
|
172
|
+
this.fileNames.delete(file);
|
|
173
|
+
this.usage.delete(file);
|
|
174
|
+
|
|
175
|
+
this.ensureProgram();
|
|
102
176
|
}
|
|
103
177
|
|
|
104
178
|
getProgram() {
|
|
@@ -110,18 +184,15 @@ export class TSService {
|
|
|
110
184
|
}
|
|
111
185
|
|
|
112
186
|
getSourceFile(file: string) {
|
|
187
|
+
file = this.normalize(file);
|
|
113
188
|
return this.getProgram()?.getSourceFile(file);
|
|
114
189
|
}
|
|
115
190
|
|
|
116
191
|
getFileContent(file: string) {
|
|
192
|
+
file = this.normalize(file);
|
|
117
193
|
return this.files.get(file)?.content;
|
|
118
194
|
}
|
|
119
195
|
|
|
120
|
-
removeFile(file: string) {
|
|
121
|
-
this.files.delete(file);
|
|
122
|
-
this.fileNames.delete(file);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
196
|
getModuleResolutionConfig() {
|
|
126
197
|
return {
|
|
127
198
|
baseUrl: this.baseUrl,
|
package/src/engine/resolver.ts
CHANGED
|
@@ -12,12 +12,13 @@ export function resolveNodeValue(
|
|
|
12
12
|
paramMap?: Map<string, ts.Expression>,
|
|
13
13
|
seen = new Set<ts.Node>()
|
|
14
14
|
): ResolveResult {
|
|
15
|
+
|
|
15
16
|
if (seen.has(node)) {
|
|
16
17
|
return { values: [], dynamic: true };
|
|
17
18
|
}
|
|
18
19
|
seen.add(node);
|
|
19
20
|
|
|
20
|
-
//
|
|
21
|
+
// PARAM MAPPING
|
|
21
22
|
if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
22
23
|
return resolveNodeValue(
|
|
23
24
|
paramMap.get(node.text)!,
|
|
@@ -43,12 +44,15 @@ export function resolveNodeValue(
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
values: [...new Set(values)],
|
|
49
|
+
dynamic: true
|
|
50
|
+
};
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
// TEMPLATE
|
|
50
54
|
if (ts.isTemplateExpression(node)) {
|
|
51
|
-
let results = [node.head.text];
|
|
55
|
+
let results = node.head.text ? [node.head.text] : [""];
|
|
52
56
|
|
|
53
57
|
for (const span of node.templateSpans) {
|
|
54
58
|
const res = resolveNodeValue(span.expression, checker, paramMap, seen);
|
|
@@ -83,14 +87,7 @@ export function resolveNodeValue(
|
|
|
83
87
|
let symbol = checker.getSymbolAtLocation(node);
|
|
84
88
|
if (!symbol) return { values: [], dynamic: true };
|
|
85
89
|
|
|
86
|
-
//
|
|
87
|
-
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
88
|
-
|
|
89
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
90
|
-
symbol = checker.getAliasedSymbol(symbol);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// inline variable
|
|
90
|
+
// INLINE FIRST
|
|
94
91
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
95
92
|
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
96
93
|
return resolveNodeValue(
|
|
@@ -100,7 +97,16 @@ export function resolveNodeValue(
|
|
|
100
97
|
seen
|
|
101
98
|
);
|
|
102
99
|
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// unwrap export
|
|
103
|
+
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
104
|
+
|
|
105
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
106
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
107
|
+
}
|
|
103
108
|
|
|
109
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
104
110
|
if (ts.isEnumMember(decl) && decl.initializer) {
|
|
105
111
|
if (ts.isStringLiteral(decl.initializer)) {
|
|
106
112
|
return {
|
|
@@ -111,15 +117,15 @@ export function resolveNodeValue(
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
// UNION TYPES
|
|
115
120
|
const type = checker.getTypeAtLocation(node);
|
|
116
121
|
|
|
122
|
+
// UNION
|
|
117
123
|
if (type.isUnion()) {
|
|
118
124
|
const values: string[] = [];
|
|
119
125
|
|
|
120
126
|
for (const t of type.types) {
|
|
121
|
-
if (
|
|
122
|
-
values.push((t as
|
|
127
|
+
if (t.flags & ts.TypeFlags.StringLiteral) {
|
|
128
|
+
values.push((t as ts.StringLiteralType).value);
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
@@ -128,15 +134,18 @@ export function resolveNodeValue(
|
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
//
|
|
132
|
-
if (
|
|
133
|
-
return {
|
|
137
|
+
// STRING LITERAL TYPE
|
|
138
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
139
|
+
return {
|
|
140
|
+
values: [(type as ts.StringLiteralType).value],
|
|
141
|
+
dynamic: false
|
|
142
|
+
};
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
return { values: [], dynamic: true };
|
|
137
146
|
}
|
|
138
147
|
|
|
139
|
-
// PROPERTY ACCESS
|
|
148
|
+
// PROPERTY ACCESS
|
|
140
149
|
if (ts.isPropertyAccessExpression(node)) {
|
|
141
150
|
|
|
142
151
|
const symbol = checker.getSymbolAtLocation(node);
|
|
@@ -160,18 +169,16 @@ export function resolveNodeValue(
|
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
// constant enum / readonly
|
|
164
172
|
const constantValue = checker.getConstantValue(node as any);
|
|
165
173
|
if (typeof constantValue === "string") {
|
|
166
174
|
return { values: [constantValue], dynamic: false };
|
|
167
175
|
}
|
|
168
176
|
|
|
169
|
-
// type fallback
|
|
170
177
|
const type = checker.getTypeAtLocation(node);
|
|
171
178
|
|
|
172
|
-
if (
|
|
179
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
173
180
|
return {
|
|
174
|
-
values: [(type as
|
|
181
|
+
values: [(type as ts.StringLiteralType).value],
|
|
175
182
|
dynamic: false,
|
|
176
183
|
};
|
|
177
184
|
}
|
|
@@ -206,10 +213,11 @@ export function resolveNodeValue(
|
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
215
|
|
|
209
|
-
|
|
216
|
+
// fallback
|
|
217
|
+
return resolveNodeValue(node.name, checker, paramMap, seen);
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
// ARRAY ACCESS
|
|
220
|
+
// ARRAY ACCESS
|
|
213
221
|
if (ts.isElementAccessExpression(node)) {
|
|
214
222
|
const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
|
|
215
223
|
|
|
@@ -224,7 +232,6 @@ export function resolveNodeValue(
|
|
|
224
232
|
seen
|
|
225
233
|
);
|
|
226
234
|
|
|
227
|
-
// if index known
|
|
228
235
|
if (index.values.length === 1) {
|
|
229
236
|
const i = Number(index.values[0]);
|
|
230
237
|
|
|
@@ -236,7 +243,6 @@ export function resolveNodeValue(
|
|
|
236
243
|
}
|
|
237
244
|
}
|
|
238
245
|
|
|
239
|
-
// fallback → dynamic
|
|
240
246
|
return obj.values.length
|
|
241
247
|
? { values: obj.values, dynamic: true }
|
|
242
248
|
: { values: [], dynamic: true };
|
|
@@ -257,7 +263,10 @@ export function resolveNodeValue(
|
|
|
257
263
|
}
|
|
258
264
|
}
|
|
259
265
|
|
|
260
|
-
return {
|
|
266
|
+
return {
|
|
267
|
+
values: [...new Set(values)],
|
|
268
|
+
dynamic: true
|
|
269
|
+
};
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
// BINARY ("btn_" + name)
|