@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.
@@ -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
- const name = getCallName(node.expression);
19
- if (/(track|event|capture|send)/i.test(name)) {
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
- }
@@ -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 result = node.head.text;
35
+ let results = [node.head.text];
22
36
  for (const span of node.templateSpans) {
23
- result += "*";
24
- result += span.literal.text;
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: [result], dynamic: true };
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([...(a.values ?? []), ...(b.values ?? [])])],
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
- for (const decl of symbol.getDeclarations() ?? []) {
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 !== original && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
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 === "event") {
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 && right) {
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
  }
@@ -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
- * exact JS:
10
- * - {...}
11
- * - attr="..."
12
- * - Vue / Svelte handlers
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
- if (l.includes("(") ||
126
- l.includes("=") ||
143
+ // strict filter
144
+ if (l.includes("track") ||
145
+ l.includes(".track(") ||
127
146
  l.includes("=>") ||
128
- l.includes("function") ||
129
- l.includes("track")) {
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
- // Astro frontmatter ---
147
- const fm = content.match(/^---([\s\S]*?)---/);
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 = content.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
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 = content.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g);
175
+ const scripts = clean.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/g);
161
176
  for (const m of scripts) {
162
- if (m[1].trim())
163
- parts.push(m[1]);
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
- const noScripts = content.replace(/<script[\s\S]*?<\/script>/g, "");
185
+ // remove scripts
186
+ const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
187
+ // loose JS
166
188
  const loose = extractLooseJS(noScripts);
167
- for (const l of loose) {
168
- parts.push(l);
169
- }
189
+ parts.push(...loose);
170
190
  // expressions
171
- const extracted = extractJS(content);
191
+ const extracted = extractJS(clean);
172
192
  for (const e of extracted) {
173
193
  parts.push(e + ";");
174
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -15,9 +15,24 @@ export function findTrackParamIndex(
15
15
 
16
16
  function visit(node: ts.Node) {
17
17
  if (ts.isCallExpression(node)) {
18
- const name = getCallName(node.expression);
18
+ let isTrackCall = false;
19
19
 
20
- if (/(track|event|capture|send)/i.test(name)) {
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
- }
@@ -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 result = node.head.text;
51
+ let results = [node.head.text];
36
52
 
37
53
  for (const span of node.templateSpans) {
38
- result += "*";
39
- result += span.literal.text;
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: [result], dynamic: true };
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([...(a.values ?? []), ...(b.values ?? [])])],
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
- for (const decl of symbol.getDeclarations() ?? []) {
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 !== original && (symbol.flags & ts.SymbolFlags.Alias)) {
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 === "event") {
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 && right) {
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
  }
@@ -2,10 +2,17 @@ import path from "path";
2
2
  import { ParseResult } from "../types";
3
3
 
4
4
  /**
5
- * exact JS:
6
- * - {...}
7
- * - attr="..."
8
- * - Vue / Svelte handlers
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
- if (expr) result.push(expr);
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 (val.trim()) result.push(val.trim());
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.includes("function") ||
142
- l.includes("track")
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
- // Astro frontmatter ---
168
- const fm = content.match(/^---([\s\S]*?)---/);
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 = content.matchAll(
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 = content.matchAll(
188
- /<script\b[^>]*>([\s\S]*?)<\/script>/g
210
+ const scripts = clean.matchAll(
211
+ /<script\b([^>]*)>([\s\S]*?)<\/script>/g
189
212
  );
190
213
 
191
214
  for (const m of scripts) {
192
- if (m[1].trim()) parts.push(m[1]);
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
- const noScripts = content.replace(
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(content);
208
-
236
+ const extracted = extractJS(clean);
209
237
  for (const e of extracted) {
210
238
  parts.push(e + ";");
211
239
  }