@eventra_dev/eventra-cli 0.1.22 → 0.1.23
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/engine/functionAnalyzer.js +18 -10
- package/dist/engine/resolver.js +97 -21
- package/dist/utils/universal.js +47 -27
- package/package.json +1 -1
- package/src/engine/functionAnalyzer.ts +22 -12
- package/src/engine/resolver.ts +126 -31
- package/src/utils/universal.ts +59 -31
|
@@ -15,8 +15,20 @@ function findTrackParamIndex(fn) {
|
|
|
15
15
|
let result = null;
|
|
16
16
|
function visit(node) {
|
|
17
17
|
if (typescript_1.default.isCallExpression(node)) {
|
|
18
|
-
|
|
19
|
-
if (
|
|
18
|
+
let isTrackCall = false;
|
|
19
|
+
if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
|
|
20
|
+
const method = node.expression.name.text;
|
|
21
|
+
if (method === "track") {
|
|
22
|
+
isTrackCall = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// fallback
|
|
26
|
+
if (typescript_1.default.isIdentifier(node.expression)) {
|
|
27
|
+
if (node.expression.text === "track") {
|
|
28
|
+
isTrackCall = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (isTrackCall) {
|
|
20
32
|
for (const arg of node.arguments) {
|
|
21
33
|
if (typescript_1.default.isIdentifier(arg)) {
|
|
22
34
|
const idx = paramIndex.get(arg.text);
|
|
@@ -25,6 +37,10 @@ function findTrackParamIndex(fn) {
|
|
|
25
37
|
return;
|
|
26
38
|
}
|
|
27
39
|
}
|
|
40
|
+
if (typescript_1.default.isStringLiteral(arg)) {
|
|
41
|
+
result = 0;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
28
44
|
}
|
|
29
45
|
}
|
|
30
46
|
}
|
|
@@ -35,11 +51,3 @@ function findTrackParamIndex(fn) {
|
|
|
35
51
|
}
|
|
36
52
|
return result;
|
|
37
53
|
}
|
|
38
|
-
function getCallName(expr) {
|
|
39
|
-
if (typescript_1.default.isIdentifier(expr))
|
|
40
|
-
return expr.text;
|
|
41
|
-
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
42
|
-
return expr.name.text;
|
|
43
|
-
}
|
|
44
|
-
return "";
|
|
45
|
-
}
|
package/dist/engine/resolver.js
CHANGED
|
@@ -11,42 +11,60 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
11
11
|
return { values: [], dynamic: true };
|
|
12
12
|
}
|
|
13
13
|
seen.add(node);
|
|
14
|
+
// param mapping
|
|
14
15
|
if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
15
16
|
return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
|
|
16
17
|
}
|
|
18
|
+
// STRING
|
|
17
19
|
if (typescript_1.default.isStringLiteral(node)) {
|
|
18
20
|
return { values: [node.text], dynamic: false };
|
|
19
21
|
}
|
|
22
|
+
// ARRAY
|
|
23
|
+
if (typescript_1.default.isArrayLiteralExpression(node)) {
|
|
24
|
+
const values = [];
|
|
25
|
+
for (const el of node.elements) {
|
|
26
|
+
const res = resolveNodeValue(el, checker, paramMap, seen);
|
|
27
|
+
if (res.values.length) {
|
|
28
|
+
values.push(...res.values);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { values, dynamic: true };
|
|
32
|
+
}
|
|
33
|
+
// TEMPLATE
|
|
20
34
|
if (typescript_1.default.isTemplateExpression(node)) {
|
|
21
|
-
let
|
|
35
|
+
let results = [node.head.text];
|
|
22
36
|
for (const span of node.templateSpans) {
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
const res = resolveNodeValue(span.expression, checker, paramMap, seen);
|
|
38
|
+
if (res.values.length) {
|
|
39
|
+
results = results.flatMap(r => res.values.map(v => r + v));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
results = results.map(r => r + "*");
|
|
43
|
+
}
|
|
44
|
+
results = results.map(r => r + span.literal.text);
|
|
25
45
|
}
|
|
26
|
-
return { values:
|
|
46
|
+
return { values: results, dynamic: true };
|
|
27
47
|
}
|
|
48
|
+
// CONDITIONAL
|
|
28
49
|
if (typescript_1.default.isConditionalExpression(node)) {
|
|
29
50
|
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
30
51
|
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
31
52
|
return {
|
|
32
|
-
values: [...new Set([...
|
|
53
|
+
values: [...new Set([...a.values, ...b.values])],
|
|
33
54
|
dynamic: true,
|
|
34
55
|
};
|
|
35
56
|
}
|
|
57
|
+
// IDENTIFIER
|
|
36
58
|
if (typescript_1.default.isIdentifier(node)) {
|
|
37
59
|
let symbol = checker.getSymbolAtLocation(node);
|
|
38
60
|
if (!symbol)
|
|
39
61
|
return { values: [], dynamic: true };
|
|
40
|
-
|
|
41
|
-
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
42
|
-
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const original = symbol;
|
|
62
|
+
// unwrap export
|
|
46
63
|
symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
|
|
47
|
-
if (symbol
|
|
64
|
+
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
48
65
|
symbol = checker.getAliasedSymbol(symbol);
|
|
49
66
|
}
|
|
67
|
+
// inline variable
|
|
50
68
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
51
69
|
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
52
70
|
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
@@ -60,12 +78,30 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
60
78
|
}
|
|
61
79
|
}
|
|
62
80
|
}
|
|
81
|
+
// UNION TYPES
|
|
82
|
+
const type = checker.getTypeAtLocation(node);
|
|
83
|
+
if (type.isUnion()) {
|
|
84
|
+
const values = [];
|
|
85
|
+
for (const t of type.types) {
|
|
86
|
+
if (t.isStringLiteral?.()) {
|
|
87
|
+
values.push(t.value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (values.length) {
|
|
91
|
+
return { values, dynamic: false };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// TYPE FALLBACK (as const)
|
|
95
|
+
if (type.isStringLiteral?.()) {
|
|
96
|
+
return { values: [type.value], dynamic: false };
|
|
97
|
+
}
|
|
63
98
|
return { values: [], dynamic: true };
|
|
64
99
|
}
|
|
100
|
+
// PROPERTY ACCESS (obj.a.b)
|
|
65
101
|
if (typescript_1.default.isPropertyAccessExpression(node)) {
|
|
66
102
|
const symbol = checker.getSymbolAtLocation(node);
|
|
67
103
|
if (symbol) {
|
|
68
|
-
let s = symbol;
|
|
104
|
+
let s = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
|
|
69
105
|
if (s.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
70
106
|
s = checker.getAliasedSymbol(s);
|
|
71
107
|
}
|
|
@@ -80,6 +116,20 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
80
116
|
}
|
|
81
117
|
}
|
|
82
118
|
}
|
|
119
|
+
// constant enum / readonly
|
|
120
|
+
const constantValue = checker.getConstantValue(node);
|
|
121
|
+
if (typeof constantValue === "string") {
|
|
122
|
+
return { values: [constantValue], dynamic: false };
|
|
123
|
+
}
|
|
124
|
+
// type fallback
|
|
125
|
+
const type = checker.getTypeAtLocation(node);
|
|
126
|
+
if (type.isStringLiteral?.()) {
|
|
127
|
+
return {
|
|
128
|
+
values: [type.value],
|
|
129
|
+
dynamic: false,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// object literal resolve
|
|
83
133
|
if (typescript_1.default.isIdentifier(node.expression)) {
|
|
84
134
|
const objSymbol = checker.getSymbolAtLocation(node.expression);
|
|
85
135
|
if (objSymbol) {
|
|
@@ -101,33 +151,59 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
|
101
151
|
}
|
|
102
152
|
return { values: [], dynamic: true };
|
|
103
153
|
}
|
|
154
|
+
// ARRAY ACCESS (arr[0])
|
|
155
|
+
if (typescript_1.default.isElementAccessExpression(node)) {
|
|
156
|
+
const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
|
|
157
|
+
if (!node.argumentExpression) {
|
|
158
|
+
return { values: obj.values, dynamic: true };
|
|
159
|
+
}
|
|
160
|
+
const index = resolveNodeValue(node.argumentExpression, checker, paramMap, seen);
|
|
161
|
+
// if index known
|
|
162
|
+
if (index.values.length === 1) {
|
|
163
|
+
const i = Number(index.values[0]);
|
|
164
|
+
if (!Number.isNaN(i) && obj.values[i] !== undefined) {
|
|
165
|
+
return {
|
|
166
|
+
values: [obj.values[i]],
|
|
167
|
+
dynamic: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// fallback → dynamic
|
|
172
|
+
return obj.values.length
|
|
173
|
+
? { values: obj.values, dynamic: true }
|
|
174
|
+
: { values: [], dynamic: true };
|
|
175
|
+
}
|
|
176
|
+
// OBJECT
|
|
104
177
|
if (typescript_1.default.isObjectLiteralExpression(node)) {
|
|
105
178
|
const values = [];
|
|
106
179
|
for (const prop of node.properties) {
|
|
107
180
|
if (!typescript_1.default.isPropertyAssignment(prop))
|
|
108
181
|
continue;
|
|
109
182
|
const name = prop.name.getText();
|
|
110
|
-
if (name
|
|
183
|
+
if (["event", "name", "type"].includes(name)) {
|
|
111
184
|
const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
|
|
112
185
|
values.push(...res.values);
|
|
113
186
|
}
|
|
114
187
|
}
|
|
115
188
|
return { values, dynamic: true };
|
|
116
189
|
}
|
|
190
|
+
// BINARY ("btn_" + name)
|
|
117
191
|
if (typescript_1.default.isBinaryExpression(node)) {
|
|
118
192
|
if (node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
|
|
119
193
|
const left = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
120
194
|
const right = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
121
|
-
if (left
|
|
122
|
-
return {
|
|
123
|
-
values: left.values.flatMap(l => right.values.length
|
|
124
|
-
? right.values.map(r => l + r)
|
|
125
|
-
: [l + "*"]),
|
|
126
|
-
dynamic: true,
|
|
127
|
-
};
|
|
195
|
+
if (!left.values.length) {
|
|
196
|
+
return { values: [], dynamic: true };
|
|
128
197
|
}
|
|
198
|
+
return {
|
|
199
|
+
values: left.values.flatMap(l => right.values.length
|
|
200
|
+
? right.values.map(r => l + r)
|
|
201
|
+
: [l + "*"]),
|
|
202
|
+
dynamic: true,
|
|
203
|
+
};
|
|
129
204
|
}
|
|
130
205
|
}
|
|
206
|
+
// CALL → dynamic
|
|
131
207
|
if (typescript_1.default.isCallExpression(node)) {
|
|
132
208
|
return { values: [], dynamic: true };
|
|
133
209
|
}
|
package/dist/utils/universal.js
CHANGED
|
@@ -6,10 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.parseUniversal = parseUniversal;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
* Strip comments (HTML + JS)
|
|
10
|
+
*/
|
|
11
|
+
function stripComments(input) {
|
|
12
|
+
return input
|
|
13
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
14
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
15
|
+
.replace(/\/\/.*$/gm, "");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Extract JS expressions safely
|
|
13
19
|
*/
|
|
14
20
|
function extractJS(input) {
|
|
15
21
|
const result = [];
|
|
@@ -66,8 +72,13 @@ function extractJS(input) {
|
|
|
66
72
|
}
|
|
67
73
|
if (depth === 0) {
|
|
68
74
|
const expr = input.slice(i + 1, j - 1).trim();
|
|
69
|
-
if (expr
|
|
75
|
+
if (expr &&
|
|
76
|
+
(expr.includes("(") ||
|
|
77
|
+
expr.includes("=") ||
|
|
78
|
+
expr.includes(".") ||
|
|
79
|
+
expr.includes("track"))) {
|
|
70
80
|
result.push(expr);
|
|
81
|
+
}
|
|
71
82
|
i = j;
|
|
72
83
|
continue;
|
|
73
84
|
}
|
|
@@ -89,8 +100,12 @@ function extractJS(input) {
|
|
|
89
100
|
val += c;
|
|
90
101
|
j++;
|
|
91
102
|
}
|
|
92
|
-
if (val.trim()
|
|
103
|
+
if (val.trim() &&
|
|
104
|
+
(val.includes("(") ||
|
|
105
|
+
val.includes("=>") ||
|
|
106
|
+
val.includes("track"))) {
|
|
93
107
|
result.push(val.trim());
|
|
108
|
+
}
|
|
94
109
|
i = j + 1;
|
|
95
110
|
continue;
|
|
96
111
|
}
|
|
@@ -113,6 +128,9 @@ function extractJS(input) {
|
|
|
113
128
|
}
|
|
114
129
|
return result;
|
|
115
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Extract loose JS but less noisy
|
|
133
|
+
*/
|
|
116
134
|
function extractLooseJS(input) {
|
|
117
135
|
const result = [];
|
|
118
136
|
const lines = input.split("\n");
|
|
@@ -122,33 +140,30 @@ function extractLooseJS(input) {
|
|
|
122
140
|
continue;
|
|
123
141
|
if (l.startsWith("<"))
|
|
124
142
|
continue;
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
// strict filter
|
|
144
|
+
if (l.includes("track") ||
|
|
145
|
+
l.includes(".track(") ||
|
|
127
146
|
l.includes("=>") ||
|
|
128
|
-
l.
|
|
129
|
-
l.includes("
|
|
147
|
+
l.startsWith("function") ||
|
|
148
|
+
l.includes("(")) {
|
|
130
149
|
result.push(l);
|
|
131
150
|
}
|
|
132
151
|
}
|
|
133
152
|
return result;
|
|
134
153
|
}
|
|
135
154
|
/**
|
|
136
|
-
* Universal
|
|
137
|
-
* - Astro
|
|
138
|
-
* - Vue
|
|
139
|
-
* - Svelte
|
|
140
|
-
* - React / JSX
|
|
141
|
-
* - Node / backend
|
|
155
|
+
* Universal parser
|
|
142
156
|
*/
|
|
143
157
|
function parseUniversal(content, file) {
|
|
144
158
|
const parts = [];
|
|
145
159
|
const deps = [];
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
const clean = stripComments(content);
|
|
161
|
+
// Astro frontmatter
|
|
162
|
+
const fm = clean.match(/^---([\s\S]*?)---/);
|
|
148
163
|
if (fm?.[1])
|
|
149
164
|
parts.push(fm[1]);
|
|
150
165
|
// <script src="">
|
|
151
|
-
const scriptSrc =
|
|
166
|
+
const scriptSrc = clean.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
|
|
152
167
|
for (const s of scriptSrc) {
|
|
153
168
|
const src = s[1];
|
|
154
169
|
const resolved = src.startsWith("/")
|
|
@@ -157,18 +172,23 @@ function parseUniversal(content, file) {
|
|
|
157
172
|
deps.push(resolved);
|
|
158
173
|
}
|
|
159
174
|
// inline <script>
|
|
160
|
-
const scripts =
|
|
175
|
+
const scripts = clean.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/g);
|
|
161
176
|
for (const m of scripts) {
|
|
162
|
-
|
|
163
|
-
|
|
177
|
+
const attrs = m[1];
|
|
178
|
+
// skip JSON / data
|
|
179
|
+
if (/type=["']application\/json/.test(attrs))
|
|
180
|
+
continue;
|
|
181
|
+
if (m[2].trim()) {
|
|
182
|
+
parts.push(m[2]);
|
|
183
|
+
}
|
|
164
184
|
}
|
|
165
|
-
|
|
185
|
+
// remove scripts
|
|
186
|
+
const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
|
|
187
|
+
// loose JS
|
|
166
188
|
const loose = extractLooseJS(noScripts);
|
|
167
|
-
|
|
168
|
-
parts.push(l);
|
|
169
|
-
}
|
|
189
|
+
parts.push(...loose);
|
|
170
190
|
// expressions
|
|
171
|
-
const extracted = extractJS(
|
|
191
|
+
const extracted = extractJS(clean);
|
|
172
192
|
for (const e of extracted) {
|
|
173
193
|
parts.push(e + ";");
|
|
174
194
|
}
|
package/package.json
CHANGED
|
@@ -15,9 +15,24 @@ export function findTrackParamIndex(
|
|
|
15
15
|
|
|
16
16
|
function visit(node: ts.Node) {
|
|
17
17
|
if (ts.isCallExpression(node)) {
|
|
18
|
-
|
|
18
|
+
let isTrackCall = false;
|
|
19
19
|
|
|
20
|
-
if (
|
|
20
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
21
|
+
const method = node.expression.name.text;
|
|
22
|
+
|
|
23
|
+
if (method === "track") {
|
|
24
|
+
isTrackCall = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// fallback
|
|
29
|
+
if (ts.isIdentifier(node.expression)) {
|
|
30
|
+
if (node.expression.text === "track") {
|
|
31
|
+
isTrackCall = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isTrackCall) {
|
|
21
36
|
for (const arg of node.arguments) {
|
|
22
37
|
if (ts.isIdentifier(arg)) {
|
|
23
38
|
const idx = paramIndex.get(arg.text);
|
|
@@ -26,6 +41,11 @@ export function findTrackParamIndex(
|
|
|
26
41
|
return;
|
|
27
42
|
}
|
|
28
43
|
}
|
|
44
|
+
|
|
45
|
+
if (ts.isStringLiteral(arg)) {
|
|
46
|
+
result = 0;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
29
49
|
}
|
|
30
50
|
}
|
|
31
51
|
}
|
|
@@ -39,13 +59,3 @@ export function findTrackParamIndex(
|
|
|
39
59
|
|
|
40
60
|
return result;
|
|
41
61
|
}
|
|
42
|
-
|
|
43
|
-
function getCallName(expr: ts.Expression): string {
|
|
44
|
-
if (ts.isIdentifier(expr)) return expr.text;
|
|
45
|
-
|
|
46
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
47
|
-
return expr.name.text;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return "";
|
|
51
|
-
}
|
package/src/engine/resolver.ts
CHANGED
|
@@ -12,12 +12,12 @@ export function resolveNodeValue(
|
|
|
12
12
|
paramMap?: Map<string, ts.Expression>,
|
|
13
13
|
seen = new Set<ts.Node>()
|
|
14
14
|
): ResolveResult {
|
|
15
|
-
|
|
16
15
|
if (seen.has(node)) {
|
|
17
16
|
return { values: [], dynamic: true };
|
|
18
17
|
}
|
|
19
18
|
seen.add(node);
|
|
20
19
|
|
|
20
|
+
// param mapping
|
|
21
21
|
if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
22
22
|
return resolveNodeValue(
|
|
23
23
|
paramMap.get(node.text)!,
|
|
@@ -27,54 +27,70 @@ export function resolveNodeValue(
|
|
|
27
27
|
);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// STRING
|
|
30
31
|
if (ts.isStringLiteral(node)) {
|
|
31
32
|
return { values: [node.text], dynamic: false };
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// ARRAY
|
|
36
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
37
|
+
const values: string[] = [];
|
|
38
|
+
|
|
39
|
+
for (const el of node.elements) {
|
|
40
|
+
const res = resolveNodeValue(el, checker, paramMap, seen);
|
|
41
|
+
if (res.values.length) {
|
|
42
|
+
values.push(...res.values);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { values, dynamic: true };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// TEMPLATE
|
|
34
50
|
if (ts.isTemplateExpression(node)) {
|
|
35
|
-
let
|
|
51
|
+
let results = [node.head.text];
|
|
36
52
|
|
|
37
53
|
for (const span of node.templateSpans) {
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
const res = resolveNodeValue(span.expression, checker, paramMap, seen);
|
|
55
|
+
|
|
56
|
+
if (res.values.length) {
|
|
57
|
+
results = results.flatMap(r =>
|
|
58
|
+
res.values.map(v => r + v)
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
results = results.map(r => r + "*");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
results = results.map(r => r + span.literal.text);
|
|
40
65
|
}
|
|
41
66
|
|
|
42
|
-
return { values:
|
|
67
|
+
return { values: results, dynamic: true };
|
|
43
68
|
}
|
|
44
69
|
|
|
70
|
+
// CONDITIONAL
|
|
45
71
|
if (ts.isConditionalExpression(node)) {
|
|
46
72
|
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
47
73
|
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
48
74
|
|
|
49
75
|
return {
|
|
50
|
-
values: [...new Set([...
|
|
76
|
+
values: [...new Set([...a.values, ...b.values])],
|
|
51
77
|
dynamic: true,
|
|
52
78
|
};
|
|
53
79
|
}
|
|
54
80
|
|
|
81
|
+
// IDENTIFIER
|
|
55
82
|
if (ts.isIdentifier(node)) {
|
|
56
83
|
let symbol = checker.getSymbolAtLocation(node);
|
|
57
84
|
if (!symbol) return { values: [], dynamic: true };
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
61
|
-
return resolveNodeValue(
|
|
62
|
-
decl.initializer,
|
|
63
|
-
checker,
|
|
64
|
-
paramMap,
|
|
65
|
-
seen
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const original = symbol;
|
|
71
|
-
|
|
86
|
+
// unwrap export
|
|
72
87
|
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
73
88
|
|
|
74
|
-
if (symbol
|
|
89
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
75
90
|
symbol = checker.getAliasedSymbol(symbol);
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
// inline variable
|
|
78
94
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
79
95
|
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
80
96
|
return resolveNodeValue(
|
|
@@ -95,14 +111,38 @@ export function resolveNodeValue(
|
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
113
|
|
|
114
|
+
// UNION TYPES
|
|
115
|
+
const type = checker.getTypeAtLocation(node);
|
|
116
|
+
|
|
117
|
+
if (type.isUnion()) {
|
|
118
|
+
const values: string[] = [];
|
|
119
|
+
|
|
120
|
+
for (const t of type.types) {
|
|
121
|
+
if ((t as any).isStringLiteral?.()) {
|
|
122
|
+
values.push((t as any).value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (values.length) {
|
|
127
|
+
return { values, dynamic: false };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// TYPE FALLBACK (as const)
|
|
132
|
+
if ((type as any).isStringLiteral?.()) {
|
|
133
|
+
return { values: [(type as any).value], dynamic: false };
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
return { values: [], dynamic: true };
|
|
99
137
|
}
|
|
100
138
|
|
|
139
|
+
// PROPERTY ACCESS (obj.a.b)
|
|
101
140
|
if (ts.isPropertyAccessExpression(node)) {
|
|
141
|
+
|
|
102
142
|
const symbol = checker.getSymbolAtLocation(node);
|
|
103
143
|
|
|
104
144
|
if (symbol) {
|
|
105
|
-
let s = symbol;
|
|
145
|
+
let s = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
106
146
|
|
|
107
147
|
if (s.flags & ts.SymbolFlags.Alias) {
|
|
108
148
|
s = checker.getAliasedSymbol(s);
|
|
@@ -120,6 +160,23 @@ export function resolveNodeValue(
|
|
|
120
160
|
}
|
|
121
161
|
}
|
|
122
162
|
|
|
163
|
+
// constant enum / readonly
|
|
164
|
+
const constantValue = checker.getConstantValue(node as any);
|
|
165
|
+
if (typeof constantValue === "string") {
|
|
166
|
+
return { values: [constantValue], dynamic: false };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// type fallback
|
|
170
|
+
const type = checker.getTypeAtLocation(node);
|
|
171
|
+
|
|
172
|
+
if ((type as any).isStringLiteral?.()) {
|
|
173
|
+
return {
|
|
174
|
+
values: [(type as any).value],
|
|
175
|
+
dynamic: false,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// object literal resolve
|
|
123
180
|
if (ts.isIdentifier(node.expression)) {
|
|
124
181
|
const objSymbol = checker.getSymbolAtLocation(node.expression);
|
|
125
182
|
|
|
@@ -152,6 +209,40 @@ export function resolveNodeValue(
|
|
|
152
209
|
return { values: [], dynamic: true };
|
|
153
210
|
}
|
|
154
211
|
|
|
212
|
+
// ARRAY ACCESS (arr[0])
|
|
213
|
+
if (ts.isElementAccessExpression(node)) {
|
|
214
|
+
const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
|
|
215
|
+
|
|
216
|
+
if (!node.argumentExpression) {
|
|
217
|
+
return { values: obj.values, dynamic: true };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const index = resolveNodeValue(
|
|
221
|
+
node.argumentExpression,
|
|
222
|
+
checker,
|
|
223
|
+
paramMap,
|
|
224
|
+
seen
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// if index known
|
|
228
|
+
if (index.values.length === 1) {
|
|
229
|
+
const i = Number(index.values[0]);
|
|
230
|
+
|
|
231
|
+
if (!Number.isNaN(i) && obj.values[i] !== undefined) {
|
|
232
|
+
return {
|
|
233
|
+
values: [obj.values[i]],
|
|
234
|
+
dynamic: false,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// fallback → dynamic
|
|
240
|
+
return obj.values.length
|
|
241
|
+
? { values: obj.values, dynamic: true }
|
|
242
|
+
: { values: [], dynamic: true };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// OBJECT
|
|
155
246
|
if (ts.isObjectLiteralExpression(node)) {
|
|
156
247
|
const values: string[] = [];
|
|
157
248
|
|
|
@@ -160,7 +251,7 @@ export function resolveNodeValue(
|
|
|
160
251
|
|
|
161
252
|
const name = prop.name.getText();
|
|
162
253
|
|
|
163
|
-
if (name
|
|
254
|
+
if (["event", "name", "type"].includes(name)) {
|
|
164
255
|
const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
|
|
165
256
|
values.push(...res.values);
|
|
166
257
|
}
|
|
@@ -169,24 +260,28 @@ export function resolveNodeValue(
|
|
|
169
260
|
return { values, dynamic: true };
|
|
170
261
|
}
|
|
171
262
|
|
|
263
|
+
// BINARY ("btn_" + name)
|
|
172
264
|
if (ts.isBinaryExpression(node)) {
|
|
173
265
|
if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
174
266
|
const left = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
175
267
|
const right = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
176
268
|
|
|
177
|
-
if (left
|
|
178
|
-
return {
|
|
179
|
-
values: left.values.flatMap(l =>
|
|
180
|
-
right.values.length
|
|
181
|
-
? right.values.map(r => l + r)
|
|
182
|
-
: [l + "*"]
|
|
183
|
-
),
|
|
184
|
-
dynamic: true,
|
|
185
|
-
};
|
|
269
|
+
if (!left.values.length) {
|
|
270
|
+
return { values: [], dynamic: true };
|
|
186
271
|
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
values: left.values.flatMap(l =>
|
|
275
|
+
right.values.length
|
|
276
|
+
? right.values.map(r => l + r)
|
|
277
|
+
: [l + "*"]
|
|
278
|
+
),
|
|
279
|
+
dynamic: true,
|
|
280
|
+
};
|
|
187
281
|
}
|
|
188
282
|
}
|
|
189
283
|
|
|
284
|
+
// CALL → dynamic
|
|
190
285
|
if (ts.isCallExpression(node)) {
|
|
191
286
|
return { values: [], dynamic: true };
|
|
192
287
|
}
|
package/src/utils/universal.ts
CHANGED
|
@@ -2,10 +2,17 @@ import path from "path";
|
|
|
2
2
|
import { ParseResult } from "../types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
* Strip comments (HTML + JS)
|
|
6
|
+
*/
|
|
7
|
+
function stripComments(input: string): string {
|
|
8
|
+
return input
|
|
9
|
+
.replace(/<!--[\s\S]*?-->/g, "")
|
|
10
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
11
|
+
.replace(/\/\/.*$/gm, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract JS expressions safely
|
|
9
16
|
*/
|
|
10
17
|
function extractJS(input: string): string[] {
|
|
11
18
|
const result: string[] = [];
|
|
@@ -24,7 +31,6 @@ function extractJS(input: string): string[] {
|
|
|
24
31
|
if (escape) escape = false;
|
|
25
32
|
else if (ch === "\\") escape = true;
|
|
26
33
|
else if (ch === inString) inString = null;
|
|
27
|
-
|
|
28
34
|
i++;
|
|
29
35
|
continue;
|
|
30
36
|
}
|
|
@@ -68,7 +74,17 @@ function extractJS(input: string): string[] {
|
|
|
68
74
|
|
|
69
75
|
if (depth === 0) {
|
|
70
76
|
const expr = input.slice(i + 1, j - 1).trim();
|
|
71
|
-
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
expr &&
|
|
80
|
+
(expr.includes("(") ||
|
|
81
|
+
expr.includes("=") ||
|
|
82
|
+
expr.includes(".") ||
|
|
83
|
+
expr.includes("track"))
|
|
84
|
+
) {
|
|
85
|
+
result.push(expr);
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
i = j;
|
|
73
89
|
continue;
|
|
74
90
|
}
|
|
@@ -95,7 +111,14 @@ function extractJS(input: string): string[] {
|
|
|
95
111
|
j++;
|
|
96
112
|
}
|
|
97
113
|
|
|
98
|
-
if (
|
|
114
|
+
if (
|
|
115
|
+
val.trim() &&
|
|
116
|
+
(val.includes("(") ||
|
|
117
|
+
val.includes("=>") ||
|
|
118
|
+
val.includes("track"))
|
|
119
|
+
) {
|
|
120
|
+
result.push(val.trim());
|
|
121
|
+
}
|
|
99
122
|
|
|
100
123
|
i = j + 1;
|
|
101
124
|
continue;
|
|
@@ -123,6 +146,9 @@ function extractJS(input: string): string[] {
|
|
|
123
146
|
return result;
|
|
124
147
|
}
|
|
125
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Extract loose JS but less noisy
|
|
151
|
+
*/
|
|
126
152
|
function extractLooseJS(input: string): string[] {
|
|
127
153
|
const result: string[] = [];
|
|
128
154
|
|
|
@@ -134,12 +160,13 @@ function extractLooseJS(input: string): string[] {
|
|
|
134
160
|
if (!l) continue;
|
|
135
161
|
if (l.startsWith("<")) continue;
|
|
136
162
|
|
|
163
|
+
// strict filter
|
|
137
164
|
if (
|
|
138
|
-
l.includes("
|
|
139
|
-
l.includes("
|
|
165
|
+
l.includes("track") ||
|
|
166
|
+
l.includes(".track(") ||
|
|
140
167
|
l.includes("=>") ||
|
|
141
|
-
l.
|
|
142
|
-
l.includes("
|
|
168
|
+
l.startsWith("function") ||
|
|
169
|
+
l.includes("(")
|
|
143
170
|
) {
|
|
144
171
|
result.push(l);
|
|
145
172
|
}
|
|
@@ -149,14 +176,8 @@ function extractLooseJS(input: string): string[] {
|
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
/**
|
|
152
|
-
* Universal
|
|
153
|
-
* - Astro
|
|
154
|
-
* - Vue
|
|
155
|
-
* - Svelte
|
|
156
|
-
* - React / JSX
|
|
157
|
-
* - Node / backend
|
|
179
|
+
* Universal parser
|
|
158
180
|
*/
|
|
159
|
-
|
|
160
181
|
export function parseUniversal(
|
|
161
182
|
content: string,
|
|
162
183
|
file: string
|
|
@@ -164,12 +185,14 @@ export function parseUniversal(
|
|
|
164
185
|
const parts: string[] = [];
|
|
165
186
|
const deps: string[] = [];
|
|
166
187
|
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
const clean = stripComments(content);
|
|
189
|
+
|
|
190
|
+
// Astro frontmatter
|
|
191
|
+
const fm = clean.match(/^---([\s\S]*?)---/);
|
|
169
192
|
if (fm?.[1]) parts.push(fm[1]);
|
|
170
193
|
|
|
171
194
|
// <script src="">
|
|
172
|
-
const scriptSrc =
|
|
195
|
+
const scriptSrc = clean.matchAll(
|
|
173
196
|
/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g
|
|
174
197
|
);
|
|
175
198
|
|
|
@@ -184,28 +207,33 @@ export function parseUniversal(
|
|
|
184
207
|
}
|
|
185
208
|
|
|
186
209
|
// inline <script>
|
|
187
|
-
const scripts =
|
|
188
|
-
/<script\b[^>]
|
|
210
|
+
const scripts = clean.matchAll(
|
|
211
|
+
/<script\b([^>]*)>([\s\S]*?)<\/script>/g
|
|
189
212
|
);
|
|
190
213
|
|
|
191
214
|
for (const m of scripts) {
|
|
192
|
-
|
|
215
|
+
const attrs = m[1];
|
|
216
|
+
|
|
217
|
+
// skip JSON / data
|
|
218
|
+
if (/type=["']application\/json/.test(attrs)) continue;
|
|
219
|
+
|
|
220
|
+
if (m[2].trim()) {
|
|
221
|
+
parts.push(m[2]);
|
|
222
|
+
}
|
|
193
223
|
}
|
|
194
224
|
|
|
195
|
-
|
|
225
|
+
// remove scripts
|
|
226
|
+
const noScripts = clean.replace(
|
|
196
227
|
/<script[\s\S]*?<\/script>/g,
|
|
197
228
|
""
|
|
198
229
|
);
|
|
199
230
|
|
|
231
|
+
// loose JS
|
|
200
232
|
const loose = extractLooseJS(noScripts);
|
|
201
|
-
|
|
202
|
-
for (const l of loose) {
|
|
203
|
-
parts.push(l);
|
|
204
|
-
}
|
|
233
|
+
parts.push(...loose);
|
|
205
234
|
|
|
206
235
|
// expressions
|
|
207
|
-
const extracted = extractJS(
|
|
208
|
-
|
|
236
|
+
const extracted = extractJS(clean);
|
|
209
237
|
for (const e of extracted) {
|
|
210
238
|
parts.push(e + ";");
|
|
211
239
|
}
|