@vesk/compiler 0.1.14 → 0.2.9

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.
Files changed (50) hide show
  1. package/dist/api-routes.d.ts +6 -1
  2. package/dist/api-routes.d.ts.map +1 -1
  3. package/dist/api-routes.js +52 -7
  4. package/dist/client-codegen.d.ts +15 -0
  5. package/dist/client-codegen.d.ts.map +1 -1
  6. package/dist/client-codegen.js +84 -11
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +25 -0
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +33 -4
  12. package/dist/ir-generator.d.ts.map +1 -1
  13. package/dist/ir-generator.js +208 -9
  14. package/dist/md-inline.d.ts +17 -0
  15. package/dist/md-inline.d.ts.map +1 -0
  16. package/dist/md-inline.js +149 -0
  17. package/dist/middleware.d.ts.map +1 -1
  18. package/dist/middleware.js +42 -7
  19. package/dist/module-imports.d.ts +51 -0
  20. package/dist/module-imports.d.ts.map +1 -0
  21. package/dist/module-imports.js +715 -0
  22. package/dist/router.d.ts +2 -0
  23. package/dist/router.d.ts.map +1 -1
  24. package/dist/router.js +48 -3
  25. package/dist/server-codegen.d.ts +1 -1
  26. package/dist/server-codegen.d.ts.map +1 -1
  27. package/dist/server-codegen.js +1 -1
  28. package/dist/server-jsgen.d.ts.map +1 -1
  29. package/dist/server-jsgen.js +9 -4
  30. package/dist/server-render.d.ts.map +1 -1
  31. package/dist/server-render.js +30 -10
  32. package/dist/server-utils.d.ts +31 -0
  33. package/dist/server-utils.d.ts.map +1 -1
  34. package/dist/server-utils.js +478 -52
  35. package/dist/tokens.d.ts.map +1 -1
  36. package/dist/tokens.js +24 -1
  37. package/dist/typecheck.d.ts +2 -2
  38. package/dist/typecheck.d.ts.map +1 -1
  39. package/dist/typecheck.js +71 -27
  40. package/dist/types.d.ts +4 -129
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +1 -0
  43. package/dist/vesk-plugin.d.ts +3 -0
  44. package/dist/vesk-plugin.d.ts.map +1 -1
  45. package/dist/vesk-plugin.js +518 -1
  46. package/dist/vsk-imports.d.ts.map +1 -1
  47. package/dist/vsk-imports.js +3 -1
  48. package/dist/vsk-tsx.d.ts.map +1 -1
  49. package/dist/vsk-tsx.js +27 -5
  50. package/package.json +2 -1
@@ -1,7 +1,136 @@
1
1
  import * as acorn from 'acorn';
2
+ const VOID_ELEMENTS = new Set([
3
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
4
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
5
+ ]);
6
+ function getJSXElementName(node) {
7
+ if (!node)
8
+ return null;
9
+ if (node.type === 'JSXIdentifier')
10
+ return node.name;
11
+ if (node.type === 'JSXNamespacedName' && node.namespace && node.name) {
12
+ return `${node.namespace.name}:${node.name.name}`;
13
+ }
14
+ if (node.type === 'JSXMemberExpression') {
15
+ const parts = [];
16
+ let cur = node;
17
+ while (cur && cur.type === 'JSXMemberExpression') {
18
+ if (cur.property && cur.property.name)
19
+ parts.unshift(cur.property.name);
20
+ cur = cur.object;
21
+ }
22
+ if (cur && cur.type === 'JSXIdentifier' && cur.name)
23
+ parts.unshift(cur.name);
24
+ return parts.join('.') || null;
25
+ }
26
+ return null;
27
+ }
28
+ function isIdentStartCode(code) {
29
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95 || code === 36;
30
+ }
31
+ function isIdentCharCode(code) {
32
+ return isIdentStartCode(code) || (code >= 48 && code <= 57);
33
+ }
34
+ function getQualifiedJSXName(object) {
35
+ if (!object)
36
+ return object;
37
+ if (object.type === 'JSXIdentifier')
38
+ return object.name;
39
+ if (object.type === 'JSXNamespacedName')
40
+ return object.namespace.name + ':' + object.name.name;
41
+ if (object.type === 'JSXMemberExpression') {
42
+ return getQualifiedJSXName(object.object) + '.' + getQualifiedJSXName(object.property);
43
+ }
44
+ return null;
45
+ }
46
+ function looksLikeGenericArrowAt(input, pos) {
47
+ let i = pos + 1;
48
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
49
+ i++;
50
+ if (i >= input.length || !isIdentStartCode(input.charCodeAt(i)))
51
+ return false;
52
+ while (i < input.length && isIdentCharCode(input.charCodeAt(i)))
53
+ i++;
54
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
55
+ i++;
56
+ if (input.charCodeAt(i) !== 44)
57
+ return false; // ','
58
+ i++;
59
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
60
+ i++;
61
+ if (input.charCodeAt(i) !== 62)
62
+ return false; // '>'
63
+ i++;
64
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
65
+ i++;
66
+ if (input.charCodeAt(i) !== 40)
67
+ return false; // '('
68
+ return true;
69
+ }
70
+ const TS_PRIMITIVE_TYPES = new Set(['number', 'string', 'boolean', 'any', 'unknown', 'never', 'void', 'object', 'symbol', 'bigint', 'undefined', 'null']);
71
+ const HTML_TAGS = new Set(['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr']);
72
+ function looksLikeTypeAssertionAt(input, pos) {
73
+ let i = pos + 1;
74
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
75
+ i++;
76
+ const nameStart = i;
77
+ if (i >= input.length || !isIdentStartCode(input.charCodeAt(i)))
78
+ return false;
79
+ while (i < input.length && isIdentCharCode(input.charCodeAt(i)))
80
+ i++;
81
+ const name = input.slice(nameStart, i);
82
+ // If it's a known HTML tag (lowercase), it's JSX, not a type assertion
83
+ if (HTML_TAGS.has(name) || VOID_ELEMENTS.has(name))
84
+ return false;
85
+ // Only treat as type assertion if name is a TS primitive or capitalized type
86
+ const isPrimitive = TS_PRIMITIVE_TYPES.has(name);
87
+ const isCapitalized = name[0] >= 'A' && name[0] <= 'Z';
88
+ if (!isPrimitive && !isCapitalized)
89
+ return false;
90
+ // allow qualified type like `ns.Type` or `Array<string>`? For now only single identifier, skip generics check
91
+ // If next is '<', skip balanced <...>
92
+ if (input.charCodeAt(i) === 60) {
93
+ let depth = 0;
94
+ let j = i;
95
+ while (j < input.length) {
96
+ const c = input.charCodeAt(j);
97
+ if (c === 60)
98
+ depth++;
99
+ else if (c === 62) {
100
+ depth--;
101
+ if (depth === 0) {
102
+ i = j + 1;
103
+ break;
104
+ }
105
+ }
106
+ j++;
107
+ if (j - i > 100)
108
+ break;
109
+ }
110
+ }
111
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
112
+ i++;
113
+ if (input.charCodeAt(i) !== 62)
114
+ return false; // '>'
115
+ i++;
116
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
117
+ i++;
118
+ if (i >= input.length)
119
+ return false;
120
+ const c = input.charCodeAt(i);
121
+ // expression start: identifier, (, [, {, ", ', `, !, ~, +, -, number, this/super etc.
122
+ if (isIdentStartCode(c) || c === 40 || c === 91 || c === 123 || c === 34 || c === 39 || c === 96 || c === 33 || c === 126 || c === 43 || c === 45 || (c >= 48 && c <= 57))
123
+ return true;
124
+ return false;
125
+ }
2
126
  function isWsChar(code) {
3
127
  return code === 32 || code === 9 || code === 10 || code === 13 || code === 12;
4
128
  }
129
+ function skipWs(input, i) {
130
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
131
+ i++;
132
+ return i;
133
+ }
5
134
  function isStyleBoundary(code) {
6
135
  return isWsChar(code) || code === 62 || code === 47 || code === 123;
7
136
  }
@@ -36,6 +165,9 @@ export function VeskParserPlugin(config = {}) {
36
165
  #closeTagName = null;
37
166
  #jsxStartsStatement = false;
38
167
  #inTSTypeDecl = false;
168
+ #pendingChildStatement = false;
169
+ /** per-statement context-stack depths (LIFO — child statements may nest) */
170
+ #jsxChildDepthStack = [];
39
171
  constructor(options, input) {
40
172
  super(options, input);
41
173
  }
@@ -43,7 +175,292 @@ export function VeskParserPlugin(config = {}) {
43
175
  const ctx = this.curContext();
44
176
  return ctx && (ctx.token === '{' || ctx.token === 'function');
45
177
  }
178
+ #isJsxChildrenContext() {
179
+ const ctx = this.curContext();
180
+ return !!ctx && ctx.token === '<tag>...</tag>';
181
+ }
182
+ /**
183
+ * Determines whether the text starting at `this.pos` opens a
184
+ * statement-mode control-flow header (`if (`, `for (`, `while (`,
185
+ * `switch (`, `try {`, `do {`) among JSX children. Scans chars only —
186
+ * the paren body must balance and be followed by a `{` block.
187
+ */
188
+ #scansChildStatement() {
189
+ const input = this.input;
190
+ let i = this.pos;
191
+ while (i < input.length && isWsChar(input.charCodeAt(i)))
192
+ i++;
193
+ if (i >= input.length || !isIdentStartCode(input.charCodeAt(i)))
194
+ return false;
195
+ const wordStart = i;
196
+ while (i < input.length && isIdentCharCode(input.charCodeAt(i)))
197
+ i++;
198
+ const word = input.slice(wordStart, i);
199
+ // Declaration statements (`const`/`let`/`var …;`) are also valid
200
+ // statement-mode element children. Recognise them by a `=` of
201
+ // assignment plus either a top-level `;` or a newline at depth 0
202
+ // (ASI-style). Bail out (stay text) when a closing JSX tag `</` is
203
+ // seen before a terminator: a real element-child statement never
204
+ // contains its parent's closing tag, while prose text like
205
+ // `<p>const value = 5 apples</p>` does.
206
+ if (word === 'const' || word === 'let' || word === 'var') {
207
+ let depth = 0;
208
+ let seenAssign = false;
209
+ let q = i;
210
+ while (q < input.length) {
211
+ const c = input.charCodeAt(q);
212
+ if (c === 34 || c === 39 || c === 96) { // " ' `
213
+ const quote = c;
214
+ let tplDepth = 0;
215
+ q++;
216
+ while (q < input.length) {
217
+ const ch = input.charCodeAt(q);
218
+ if (ch === 92) {
219
+ q += 2;
220
+ continue;
221
+ }
222
+ if (quote === 96 && ch === 36 && input.charCodeAt(q + 1) === 123) {
223
+ tplDepth++;
224
+ q += 2;
225
+ continue;
226
+ }
227
+ if (quote === 96 && ch === 125 && tplDepth > 0) {
228
+ tplDepth--;
229
+ q++;
230
+ continue;
231
+ }
232
+ if (ch === quote && tplDepth === 0) {
233
+ q++;
234
+ break;
235
+ }
236
+ q++;
237
+ }
238
+ continue;
239
+ }
240
+ if (c === 60 && input.charCodeAt(q + 1) === 47)
241
+ return false; // '</' -> it's JSX text, not a statement
242
+ if (c === 40 || c === 91 || c === 123)
243
+ depth++;
244
+ else if (c === 41 || c === 93 || c === 125)
245
+ depth--;
246
+ else if (c === 61 && depth === 0) { // '='
247
+ const n2 = input.charCodeAt(q + 1);
248
+ const n1 = q > 0 ? input.charCodeAt(q - 1) : 0;
249
+ if (n2 !== 61 && n2 !== 62 && n1 !== 33 && n1 !== 60 && n1 !== 61)
250
+ seenAssign = true; // not ==, =>, !=, <=, >=
251
+ }
252
+ else if (c === 59 && depth === 0)
253
+ return true; // ';'
254
+ else if ((c === 10 || c === 13) && depth === 0 && seenAssign)
255
+ return true; // ASI at end of line
256
+ q++;
257
+ }
258
+ return false;
259
+ }
260
+ // Bare expression statements (`console.log(x)`, `alert('hi')`,
261
+ // `doSomething(args)`) are also valid statement-mode element children.
262
+ // They are ambiguous with prose, so only treat them as code when they
263
+ // read like a standalone line of code — not a sentence:
264
+ // * an identifier call chain (`console.log`, `foo?.bar`) with NO
265
+ // whitespace before the `(` (prose writes `word (`, code writes
266
+ // `word(`),
267
+ // * a balanced `(...)` call,
268
+ // * then a terminator — a top-level `;` or ASI newline — with no
269
+ // bare word after the `)` before it (`doSomething(x) then more`
270
+ // is prose; `doSomething(x)` on its own line is code).
271
+ // Bail out (stay text) when a closing JSX tag `</` is seen first, as
272
+ // a real child statement never contains its parent's closing tag.
273
+ if (isIdentStartCode(input.charCodeAt(wordStart))) {
274
+ let q = i; // position just after the first identifier
275
+ // member chain: `.foo` / `?.bar`, no surrounding whitespace
276
+ while (q < input.length) {
277
+ const c = input.charCodeAt(q);
278
+ if (c === 46 /* . */) {
279
+ q++;
280
+ }
281
+ else if (c === 63 /* ? */ && input.charCodeAt(q + 1) === 46) {
282
+ q += 2;
283
+ }
284
+ else
285
+ break;
286
+ if (q >= input.length || !isIdentStartCode(input.charCodeAt(q)))
287
+ return false;
288
+ q++;
289
+ while (q < input.length && isIdentCharCode(input.charCodeAt(q)))
290
+ q++;
291
+ }
292
+ // must be a call: `(` with no whitespace before it
293
+ if (input.charCodeAt(q) === 40 /* ( */) {
294
+ let depth = 0;
295
+ let callClosed = false;
296
+ let s = q;
297
+ while (s < input.length) {
298
+ const c = input.charCodeAt(s);
299
+ if (c === 34 || c === 39 || c === 96) { // " ' `
300
+ const quote = c;
301
+ let tplDepth = 0;
302
+ s++;
303
+ while (s < input.length) {
304
+ const ch = input.charCodeAt(s);
305
+ if (ch === 92) {
306
+ s += 2;
307
+ continue;
308
+ }
309
+ if (quote === 96 && ch === 36 && input.charCodeAt(s + 1) === 123) {
310
+ tplDepth++;
311
+ s += 2;
312
+ continue;
313
+ }
314
+ if (quote === 96 && ch === 125 && tplDepth > 0) {
315
+ tplDepth--;
316
+ s++;
317
+ continue;
318
+ }
319
+ if (ch === quote && tplDepth === 0) {
320
+ s++;
321
+ break;
322
+ }
323
+ s++;
324
+ }
325
+ continue;
326
+ }
327
+ if (c === 60 && input.charCodeAt(s + 1) === 47)
328
+ return false; // '</'
329
+ if (c === 40 || c === 91 || c === 123)
330
+ depth++;
331
+ else if (c === 41 || c === 93 || c === 125) {
332
+ depth--;
333
+ if (c === 41 && depth === 0) {
334
+ callClosed = true;
335
+ s++;
336
+ break;
337
+ }
338
+ }
339
+ s++;
340
+ }
341
+ if (!callClosed)
342
+ return false;
343
+ // After the call, walk forward treating each character in turn so
344
+ // we can tell "standalone statement ending in `;` / newline" from
345
+ // "trailing prose on the same line". Chained member calls
346
+ // (`.foo(...)`) and paren-groups are consumed; a bare word on the
347
+ // same line before a newline/`;` means the child is a sentence.
348
+ let d = 0;
349
+ for (;;) {
350
+ if (s >= input.length)
351
+ return false;
352
+ const c = input.charCodeAt(s);
353
+ if (c === 60 && input.charCodeAt(s + 1) === 47)
354
+ return false; // '</' -> prose
355
+ if (c === 34 || c === 39 || c === 96) { // " ' `
356
+ const quote = c;
357
+ let tplDepth = 0;
358
+ s++;
359
+ while (s < input.length) {
360
+ const ch = input.charCodeAt(s);
361
+ if (ch === 92) {
362
+ s += 2;
363
+ continue;
364
+ }
365
+ if (quote === 96 && ch === 36 && input.charCodeAt(s + 1) === 123) {
366
+ tplDepth++;
367
+ s += 2;
368
+ continue;
369
+ }
370
+ if (quote === 96 && ch === 125 && tplDepth > 0) {
371
+ tplDepth--;
372
+ s++;
373
+ continue;
374
+ }
375
+ if (ch === quote && tplDepth === 0) {
376
+ s++;
377
+ break;
378
+ }
379
+ s++;
380
+ }
381
+ continue;
382
+ }
383
+ if (c === 40 || c === 91 || c === 123)
384
+ d++;
385
+ else if (c === 41 || c === 93 || c === 125)
386
+ d--;
387
+ else if (c === 59 && d === 0)
388
+ return true; // ';' -> statement
389
+ else if ((c === 10 || c === 13) && d === 0)
390
+ return true; // ASI newline -> standalone
391
+ else if (c === 46) { // member access `.foo`; may lead to another call
392
+ s++;
393
+ while (s < input.length && isIdentCharCode(input.charCodeAt(s)))
394
+ s++;
395
+ continue;
396
+ }
397
+ else if (isIdentCharCode(c) || isIdentStartCode(c))
398
+ return false; // bare word -> prose
399
+ s++;
400
+ }
401
+ }
402
+ }
403
+ if (word !== 'if' && word !== 'for' && word !== 'while' && word !== 'switch' && word !== 'try' && word !== 'do')
404
+ return false;
405
+ let p = i;
406
+ while (p < input.length && isWsChar(input.charCodeAt(p)))
407
+ p++;
408
+ if (word === 'try' || word === 'do') {
409
+ return input.charCodeAt(p) === 123; // '{'
410
+ }
411
+ if (input.charCodeAt(p) !== 40)
412
+ return false; // '('
413
+ let depth = 1;
414
+ let q = p + 1;
415
+ while (q < input.length) {
416
+ const c = input.charCodeAt(q);
417
+ if (c === 34 || c === 39 || c === 96) {
418
+ const quote = c;
419
+ q++;
420
+ while (q < input.length) {
421
+ const ch = input.charCodeAt(q);
422
+ if (ch === 92) {
423
+ q += 2;
424
+ continue;
425
+ }
426
+ if (ch === quote) {
427
+ q++;
428
+ break;
429
+ }
430
+ q++;
431
+ }
432
+ continue;
433
+ }
434
+ if (c === 40 || c === 91 || c === 123)
435
+ depth++;
436
+ else if (c === 41 || c === 93 || c === 125) {
437
+ depth--;
438
+ if (c === 41 && depth === 0)
439
+ break;
440
+ }
441
+ q++;
442
+ }
443
+ if (q >= input.length)
444
+ return false;
445
+ let r = q + 1;
446
+ while (r < input.length && isWsChar(input.charCodeAt(r)))
447
+ r++;
448
+ return input.charCodeAt(r) === 123; // '{'
449
+ }
46
450
  readToken(code) {
451
+ if (this.#componentDepth > 0 && this.#isJsxChildrenContext() && this.#scansChildStatement()) {
452
+ this.#pendingChildStatement = true;
453
+ this.#jsxChildDepthStack.push(this.context.length);
454
+ this.context.push(acorn.tokContexts.b_stat);
455
+ if (isWsChar(code)) {
456
+ this.skipSpace();
457
+ this.start = this.pos;
458
+ if (this.options.locations && typeof this.curPosition === 'function') {
459
+ this.startLoc = this.curPosition();
460
+ }
461
+ }
462
+ return super.readToken(this.input.codePointAt(this.pos));
463
+ }
47
464
  if (this.#componentDepth > 0 && code === 60 && this.#isBlockContext()) {
48
465
  const next = this.input.charCodeAt(this.pos + 1);
49
466
  if (next === 47 || (next >= 65 && next <= 90) || (next >= 97 && next <= 122)) {
@@ -58,6 +475,10 @@ export function VeskParserPlugin(config = {}) {
58
475
  };
59
476
  if (inType) {
60
477
  if (startsNewStatement) {
478
+ if (looksLikeGenericArrowAt(this.input, this.pos) || looksLikeTypeAssertionAt(this.input, this.pos)) {
479
+ this.#jsxStartsStatement = false;
480
+ return super.readToken(code);
481
+ }
61
482
  this.#jsxStartsStatement = true;
62
483
  ++this.pos;
63
484
  return this.finishToken(tstt.jsxTagStart);
@@ -70,6 +491,10 @@ export function VeskParserPlugin(config = {}) {
70
491
  prev === tt._this || prev === tt._super || prev === tt._true || prev === tt._false ||
71
492
  prev === tt._null || prev === tt.jsxTagEnd;
72
493
  if (!canEndExpr || startsNewStatement) {
494
+ if (looksLikeGenericArrowAt(this.input, this.pos) || looksLikeTypeAssertionAt(this.input, this.pos)) {
495
+ this.#jsxStartsStatement = false;
496
+ return super.readToken(code);
497
+ }
73
498
  this.#jsxStartsStatement = true;
74
499
  return forceJsx();
75
500
  }
@@ -377,7 +802,99 @@ export function VeskParserPlugin(config = {}) {
377
802
  if (prefix.startsWith('<style') && isStyleBoundary(prefix.charCodeAt(6))) {
378
803
  return this.parseStyleElement(startPos, startLoc);
379
804
  }
380
- return super.jsx_parseElementAt(startPos, startLoc);
805
+ return this.#parseElementWithStatements(startPos, startLoc);
806
+ }
807
+ /**
808
+ * Parses an entire JSX element (or fragment) starting after `<`,
809
+ * mirroring the vendored acorn-jsx `jsx_parseElementAt` with an added
810
+ * statement-mode branch: when `#pendingChildStatement` is set (a
811
+ * control-flow header like `if (…) {` appeared where JSX children
812
+ * expect text), a real statement is parsed into the children array.
813
+ */
814
+ #parseElementWithStatements(startPos, startLoc) {
815
+ const node = this.startNodeAt(startPos, startLoc);
816
+ const children = [];
817
+ const openingElement = this.jsx_parseOpeningElementAt(startPos, startLoc);
818
+ let closingElement = null;
819
+ if (!openingElement.selfClosing) {
820
+ contents: for (;;) {
821
+ if (this.#pendingChildStatement) {
822
+ this.#pendingChildStatement = false;
823
+ const stmt = this.parseStatement(null);
824
+ children.push(stmt);
825
+ // Restore the JSX-children tokenizer and re-lex the trailing
826
+ // token (lexed under the temporary b_stat context) as a JSX child.
827
+ this.context.length = this.#jsxChildDepthStack.pop() ?? this.context.length;
828
+ this.exprAllowed = true;
829
+ this.pos = this.start;
830
+ this.next();
831
+ continue;
832
+ }
833
+ switch (this.type) {
834
+ case tstt?.jsxTagStart:
835
+ startPos = this.start;
836
+ startLoc = this.startLoc;
837
+ this.next();
838
+ if (this.eat(tt.slash)) {
839
+ closingElement = this.jsx_parseClosingElementAt(startPos, startLoc);
840
+ break contents;
841
+ }
842
+ children.push(this.jsx_parseElementAt(startPos, startLoc));
843
+ break;
844
+ case tstt?.jsxText:
845
+ children.push(this.parseExprAtom());
846
+ break;
847
+ case tt.braceL:
848
+ children.push(this.jsx_parseExpressionContainer());
849
+ break;
850
+ default:
851
+ this.unexpected();
852
+ }
853
+ }
854
+ if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
855
+ this.raise(closingElement.start, "Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">");
856
+ }
857
+ }
858
+ const fragmentOrElement = openingElement.name ? "Element" : "Fragment";
859
+ node["opening" + fragmentOrElement] = openingElement;
860
+ node["closing" + fragmentOrElement] = closingElement;
861
+ node.children = children;
862
+ if (this.type === tt.relational && this.value === "<") {
863
+ this.raise(this.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
864
+ }
865
+ return this.finishNode(node, "JSX" + fragmentOrElement);
866
+ }
867
+ jsx_parseOpeningElementAt(startPos, startLoc) {
868
+ // Manual implementation that treats HTML void elements as
869
+ // self-closing even without `/>`. Delegating to super would leave
870
+ // a stale tc_expr context (the `>` updateContext only pops the
871
+ // extra level when a `/` was present), so the following `}` is
872
+ // mis-read as JSX text: "Unexpected token `}`".
873
+ const node = this.startNodeAt(startPos, startLoc);
874
+ node.attributes = [];
875
+ const nodeName = this.jsx_parseElementName();
876
+ if (nodeName)
877
+ node.name = nodeName;
878
+ const nameStr = getJSXElementName(nodeName);
879
+ const isVoid = !!(nameStr && VOID_ELEMENTS.has(nameStr));
880
+ while (this.type !== tt.slash && this.type !== tstt.jsxTagEnd) {
881
+ node.attributes.push(this.jsx_parseAttribute());
882
+ }
883
+ const hasSlash = this.eat(tt.slash);
884
+ node.selfClosing = hasSlash || isVoid;
885
+ if (this.type !== tstt.jsxTagEnd)
886
+ this.unexpected();
887
+ if (isVoid && !hasSlash) {
888
+ const stack = this.context;
889
+ if (stack.length > 0 && stack[stack.length - 1].token === '<tag>...</tag>') {
890
+ stack.pop();
891
+ this.exprAllowed = true;
892
+ }
893
+ }
894
+ // Consume `>`. For void `>` we already popped tc_expr above so the
895
+ // following `}` is read as a normal JS token, not JSX text.
896
+ this.next();
897
+ return this.finishNode(node, nodeName ? 'JSXOpeningElement' : 'JSXOpeningFragment');
381
898
  }
382
899
  parseStyleElement(startPos, startLoc) {
383
900
  const el = this.startNodeAt(startPos, startLoc);
@@ -1 +1 @@
1
- {"version":3,"file":"vsk-imports.d.ts","sourceRoot":"","sources":["../src/vsk-imports.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AAEH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAahE;AA0BD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAUrF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBhE"}
1
+ {"version":3,"file":"vsk-imports.d.ts","sourceRoot":"","sources":["../src/vsk-imports.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AAEH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAahE;AA0BD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAUrF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBhE"}
@@ -127,7 +127,9 @@ export function stripTypeImport(importSrc) {
127
127
  return null;
128
128
  const specs = stmt.specifiers || [];
129
129
  const kept = specs.filter((s) => s.importKind !== 'type');
130
- if (kept.length === 0)
130
+ // A side-effect import (`import './x'`) has no specifiers at all — it is NOT
131
+ // type-only and must survive so both bundles run its module-level code.
132
+ if (specs.length > 0 && kept.length === 0)
131
133
  return null;
132
134
  if (kept.length === specs.length)
133
135
  return importSrc;
@@ -1 +1 @@
1
- {"version":3,"file":"vsk-tsx.d.ts","sourceRoot":"","sources":["../src/vsk-tsx.ts"],"names":[],"mappings":"AAiBA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3E,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAC;IACtG,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA09BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA0ChG;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/C;AAWD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmDrD"}
1
+ {"version":3,"file":"vsk-tsx.d.ts","sourceRoot":"","sources":["../src/vsk-tsx.ts"],"names":[],"mappings":"AAiBA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3E,UAAU,CAAC,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;KAAE,CAAC;IACtG,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA++BD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA0ChG;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/C;AAWD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmDrD"}
package/dist/vsk-tsx.js CHANGED
@@ -388,7 +388,17 @@ function emitJSXElement(g, source, el, opts) {
388
388
  emitJSXAttr(g, source, attr);
389
389
  prev = attr.end;
390
390
  }
391
- g.add(source.slice(prev, op.end), prev, op.end);
391
+ const tail = source.slice(prev, op.end);
392
+ if (op.selfClosing && !tail.includes('/')) {
393
+ const gt = tail.lastIndexOf('>');
394
+ const head = gt === -1 ? tail : tail.slice(0, gt);
395
+ if (head)
396
+ g.add(head, prev, prev + head.length);
397
+ g.addRaw('/>');
398
+ }
399
+ else {
400
+ g.add(tail, prev, op.end);
401
+ }
392
402
  if (op.selfClosing)
393
403
  return;
394
404
  emitJSXChildren(g, source, el.children ?? [], opts);
@@ -559,6 +569,18 @@ function emitJSXChildren(g, source, children, opts) {
559
569
  emitJSXFragment(g, source, child, opts);
560
570
  i++;
561
571
  }
572
+ else if (child.type === 'IfStatement' || child.type === 'ForOfStatement' ||
573
+ child.type === 'ForStatement' || child.type === 'ForInStatement' ||
574
+ child.type === 'WhileStatement' || child.type === 'DoWhileStatement' ||
575
+ child.type === 'SwitchStatement' || child.type === 'TryStatement' ||
576
+ child.type === 'VariableDeclaration' || child.type === 'ExpressionStatement') {
577
+ // Statement-mode control flow nested among JSX children: TSX has no
578
+ // statement children, so wrap each in an IIFE expression container.
579
+ g.addRaw('{(() => { ');
580
+ emitBody(g, source, [child], '', opts);
581
+ g.addRaw(' })()}');
582
+ i++;
583
+ }
562
584
  else {
563
585
  i++;
564
586
  }
@@ -1008,7 +1030,7 @@ export function compileVskCodegen(source, opts = {}) {
1008
1030
  g.add(clean.slice(pos), pos, source.length);
1009
1031
  let code = g.code;
1010
1032
  if (isModuleAst(ast) && containsIdentifier(code, 'Head') && !isIdentifierImported(code, 'Head')) {
1011
- g.prepend('declare const Head: (props: { children?: unknown }) => unknown;\n');
1033
+ g.prepend('declare const Head: (props: { children?: Component }) => Component;\n');
1012
1034
  code = g.code;
1013
1035
  }
1014
1036
  return { code, mappings: g.mappings, styleRegions: g.styleRegions, errors: [] };
@@ -1057,7 +1079,7 @@ export function generateVskDts(source) {
1057
1079
  const hasType = propsType !== null && propsType !== undefined;
1058
1080
  const collision = typeDeclNames.has(typeName);
1059
1081
  const signature = hasType
1060
- ? (collision ? propsType : typeName) + ' & { children?: unknown }'
1082
+ ? (collision ? propsType : typeName) + ' & { children?: Component }'
1061
1083
  : 'any';
1062
1084
  if (hasType && !collision) {
1063
1085
  lines.push(`export type ${typeName} = ${propsType};`, '');
@@ -1066,10 +1088,10 @@ export function generateVskDts(source) {
1066
1088
  lines.push(`export type ${typeName} = any;`, '');
1067
1089
  }
1068
1090
  if (defaultExport) {
1069
- lines.push(`export default function ${name}(props: ${signature}): unknown;`, '');
1091
+ lines.push(`export default function ${name}(props: ${signature}): Component;`, '');
1070
1092
  }
1071
1093
  else {
1072
- lines.push(`export declare function ${name}(props: ${signature}): unknown;`, '');
1094
+ lines.push(`export declare function ${name}(props: ${signature}): Component;`, '');
1073
1095
  }
1074
1096
  }
1075
1097
  return lines.join('\n');