@vesk/compiler 0.2.60 → 0.2.62

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EASjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoBD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AA0iED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAmJD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAuED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAUjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAkGxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAiDD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAgJlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAI5E;AAyCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAuB9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAY5F;AAoFD,qDAAqD;AACrD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,SAAS,CAQtE;AA+nED,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AAmJD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAuED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAOpQ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAyC5J;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/E;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -2,7 +2,7 @@ import { walk } from 'zimmerframe';
2
2
  import { print } from 'esrap';
3
3
  import ts from 'esrap/languages/ts';
4
4
  import tsx from 'esrap/languages/tsx';
5
- import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
5
+ import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, BreakSignal, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
6
6
  import { parse } from '@vesk/compiler/src/parser';
7
7
  import { generateIR } from '@vesk/compiler/src/ir-generator';
8
8
  import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
@@ -296,6 +296,20 @@ export function transformTracked(irNode, tracked) {
296
296
  }
297
297
  export function collectTrackedNames(body) {
298
298
  const names = new Map();
299
+ collectTrackedNamesInto(body, names);
300
+ return names;
301
+ }
302
+ /**
303
+ * A `track()` declaration is not always a direct child of the component body.
304
+ * A guard-clause early return (`if (!props.x) return <empty/>;`) lowers to an
305
+ * opaque region whose alternate branch carries the rest of the body — so the
306
+ * track decls (and every assignment that must become `set(cell, …)`) live
307
+ * inside `OpaqueDynamicRegion.alternateNodes`, not at the top level. Other
308
+ * constructs (loops, switch cases, `{#server}` blocks, slot children) nest
309
+ * bodies too. Miss one and the cell is emitted but never `get`/`set`-wrapped,
310
+ * which both breaks reactivity and leaves `x = …` assigning to a `const`.
311
+ */
312
+ function collectTrackedNamesInto(body, names) {
299
313
  for (const node of body) {
300
314
  if (node instanceof TrackDecl) {
301
315
  const cellName = node.rawName || node.name;
@@ -303,8 +317,35 @@ export function collectTrackedNames(body) {
303
317
  if (node.rawName)
304
318
  names.set(node.rawName, { cellName, kind: 'cell' });
305
319
  }
320
+ else if (node instanceof StaticNode || node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
321
+ collectTrackedNamesInto(node.children, names);
322
+ }
323
+ else if (node instanceof ComponentCall) {
324
+ collectTrackedNamesInto(node.children, names);
325
+ }
326
+ else if (node instanceof PropSlot) {
327
+ collectTrackedNamesInto(node.body, names);
328
+ }
329
+ else if (node instanceof MapRegion) {
330
+ collectTrackedNamesInto(node.bodyTemplate, names);
331
+ collectTrackedNamesInto(node.alternateNodes, names);
332
+ }
333
+ else if (node instanceof OpaqueDynamicRegion) {
334
+ collectTrackedNamesInto(node.consequentNodes, names);
335
+ collectTrackedNamesInto(node.alternateNodes, names);
336
+ }
337
+ else if (node instanceof WhileLoop || node instanceof ForLoop) {
338
+ collectTrackedNamesInto(node.bodyTemplate, names);
339
+ }
340
+ else if (node instanceof TryCatch) {
341
+ collectTrackedNamesInto(node.bodyTemplate, names);
342
+ collectTrackedNamesInto(node.catchBody, names);
343
+ }
344
+ else if (node instanceof SwitchBlock) {
345
+ for (const c of node.cases)
346
+ collectTrackedNamesInto(c.body, names);
347
+ }
306
348
  }
307
- return names;
308
349
  }
309
350
  /**
310
351
  * Collect the tracked bindings a prop expression READS, so a string-returning
@@ -365,6 +406,78 @@ export function transformTrackedInit(init, tracked) {
365
406
  return init;
366
407
  return transformTracked({ raw: init, ast: expr }, tracked);
367
408
  }
409
+ /**
410
+ * Names a body-level `RuntimeStatement` binds (`const list = …`, `let x = …`).
411
+ * Only top-level `Identifier` declarators count — destructuring patterns are
412
+ * rare here and a false negative only means we hoist, which is the previous
413
+ * behaviour.
414
+ */
415
+ function runtimeDeclaredNames(node) {
416
+ if (!(node instanceof RuntimeStatement))
417
+ return [];
418
+ const stmt = node.ast;
419
+ if (!stmt || stmt.type !== 'VariableDeclaration' || !stmt.declarations)
420
+ return [];
421
+ const out = [];
422
+ for (const d of stmt.declarations) {
423
+ if (d.id && d.id.type === 'Identifier' && typeof d.id.name === 'string')
424
+ out.push(d.id.name);
425
+ }
426
+ return out;
427
+ }
428
+ /**
429
+ * Whether a `TrackDecl` initializer reads any of `names`. Parsed with the vsk
430
+ * parser (TS generics and `.vsk` syntax included) and walked AST-only — no
431
+ * regex over source. An unparsable init reports `false`, which keeps the
432
+ * previous hoist behaviour rather than silently changing emission order.
433
+ */
434
+ function initReadsAnyOf(init, names) {
435
+ if (!init || names.size === 0)
436
+ return false;
437
+ let expr = null;
438
+ try {
439
+ const prog = parse(`(${init})`);
440
+ expr = prog.body?.[0]?.type === 'ExpressionStatement' ? (prog.body[0].expression ?? null) : null;
441
+ }
442
+ catch {
443
+ return false;
444
+ }
445
+ if (!expr)
446
+ return false;
447
+ let found = false;
448
+ const walk = (n) => {
449
+ if (found || !n || typeof n !== 'object')
450
+ return;
451
+ if (Array.isArray(n)) {
452
+ for (const c of n)
453
+ walk(c);
454
+ return;
455
+ }
456
+ const t = n.type;
457
+ if (typeof t !== 'string')
458
+ return;
459
+ if (t === 'Identifier') {
460
+ if (names.has(n.name))
461
+ found = true;
462
+ return;
463
+ }
464
+ if (t === 'MemberExpression') {
465
+ const m = n;
466
+ walk(m.object);
467
+ // A non-computed property is a field name, not a reference.
468
+ if (m.computed)
469
+ walk(m.property);
470
+ return;
471
+ }
472
+ for (const key of Object.keys(n)) {
473
+ if (key === 'loc' || key === 'range' || key === 'start' || key === 'end')
474
+ continue;
475
+ walk(n[key]);
476
+ }
477
+ };
478
+ walk(expr);
479
+ return found;
480
+ }
368
481
  const NON_BUBBLING_EVENTS = new Set([
369
482
  'focus', 'blur',
370
483
  'mouseenter', 'mouseleave',
@@ -428,6 +541,9 @@ class Ctx {
428
541
  walker = '__hydrate';
429
542
  asyncComps = new Set();
430
543
  isAsyncScope = false;
544
+ // While emitting a loop body, names the boolean flag a `break` sets (see
545
+ // BreakSignal). Null outside a break-capable loop body.
546
+ breakFlag = null;
431
547
  constructor(alloc) {
432
548
  this.alloc = alloc ?? { c: 0 };
433
549
  }
@@ -516,6 +632,11 @@ function emitNode(ctx, node, tracked, effectsVar, parentVar, compPrefix = '__com
516
632
  ctx.push(semicolonizeStatement(transformTracked(node, tracked)));
517
633
  return null;
518
634
  }
635
+ if (node instanceof BreakSignal) {
636
+ if (ctx.breakFlag)
637
+ ctx.push(`${ctx.breakFlag} = true;`);
638
+ return null;
639
+ }
519
640
  if (node instanceof SlotNode) {
520
641
  if (!parentVar) {
521
642
  // A TOP-LEVEL `{props.children}` — the whole body of a layout is one
@@ -1749,6 +1870,8 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1749
1870
  ctx.push(`const ${endAnchor} = document.createComment('while-end');`);
1750
1871
  emitHydrateFenceAnchoring(ctx, anchor, endAnchor, parent === '$root', budget || undefined);
1751
1872
  const renderLoop = ctx.n();
1873
+ const hasBreak = bodyHasBreak(node.bodyTemplate);
1874
+ const breakVar = hasBreak ? ctx.n() : null;
1752
1875
  ctx.push(`const ${renderLoop} = ${ctx.isAsyncScope ? 'async () => {' : '() => {'}`);
1753
1876
  if (!hyd)
1754
1877
  ctx.push(indent(`const __p = ${anchor}.parentNode;`));
@@ -1756,8 +1879,14 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1756
1879
  ctx.push(indent(`const __b = document.createDocumentFragment();`));
1757
1880
  if (hyd)
1758
1881
  ctx.push(indent(`const __cl = [];`));
1882
+ if (breakVar)
1883
+ ctx.push(indent(`let ${breakVar} = false;`));
1884
+ const savedBreakFlag = ctx.breakFlag;
1885
+ ctx.breakFlag = breakVar;
1759
1886
  if (node.isDoWhile) {
1760
1887
  ctx.push(indent(`do {`));
1888
+ if (breakVar)
1889
+ ctx.push(indent(indent(`if (${breakVar}) break;`)));
1761
1890
  for (const n of node.bodyTemplate) {
1762
1891
  const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b', '__components', 0, budget || undefined);
1763
1892
  if (v) {
@@ -1771,6 +1900,8 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1771
1900
  }
1772
1901
  else {
1773
1902
  ctx.push(indent(`while (${condExpr}) {`));
1903
+ if (breakVar)
1904
+ ctx.push(indent(indent(`if (${breakVar}) break;`)));
1774
1905
  for (const n of node.bodyTemplate) {
1775
1906
  const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b', '__components', 0, budget || undefined);
1776
1907
  if (v) {
@@ -1782,6 +1913,7 @@ function emitWhileLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1782
1913
  }
1783
1914
  ctx.push(indent(`}`));
1784
1915
  }
1916
+ ctx.breakFlag = savedBreakFlag;
1785
1917
  if (!hyd)
1786
1918
  ctx.push(indent(`__p.insertBefore(__b, ${endAnchor});`));
1787
1919
  if (hyd)
@@ -1825,6 +1957,8 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1825
1957
  ctx.push(`const ${endAnchor} = document.createComment('for-end');`);
1826
1958
  emitHydrateFenceAnchoring(ctx, anchor, endAnchor, parent === '$root', budget || undefined);
1827
1959
  const renderLoop = ctx.n();
1960
+ const forHasBreak = bodyHasBreak(node.bodyTemplate);
1961
+ const forBreakVar = forHasBreak ? ctx.n() : null;
1828
1962
  ctx.push(`const ${renderLoop} = ${ctx.isAsyncScope ? 'async () => {' : '() => {'}`);
1829
1963
  if (!hyd)
1830
1964
  ctx.push(indent(`const __p = ${anchor}.parentNode;`));
@@ -1832,9 +1966,15 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1832
1966
  ctx.push(indent(`const __b = document.createDocumentFragment();`));
1833
1967
  if (hyd)
1834
1968
  ctx.push(indent(`const __cl = [];`));
1969
+ if (forBreakVar)
1970
+ ctx.push(indent(`let ${forBreakVar} = false;`));
1971
+ const savedForBreakFlag = ctx.breakFlag;
1972
+ ctx.breakFlag = forBreakVar;
1835
1973
  if (node.kind === 'for-in') {
1836
1974
  const srcExpr = transformTracked(node.condition, tracked);
1837
1975
  ctx.push(indent(`for (${node.init} of (Array.isArray(${srcExpr}) ? ${srcExpr} : (${srcExpr} == null ? [] : Object.keys(${srcExpr})))) {`));
1976
+ if (forBreakVar)
1977
+ ctx.push(indent(indent(`if (${forBreakVar}) break;`)));
1838
1978
  for (const n of node.bodyTemplate) {
1839
1979
  const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b', '__components', 0, budget || undefined);
1840
1980
  if (v) {
@@ -1864,6 +2004,8 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1864
2004
  updatePart = updatePart.slice(0, -1).trim();
1865
2005
  const head = initPart ? `${initPart}; ${condExpr}` : `; ${condExpr}`;
1866
2006
  ctx.push(indent(`for (${head}; ${updatePart}) {`));
2007
+ if (forBreakVar)
2008
+ ctx.push(indent(indent(`if (${forBreakVar}) break;`)));
1867
2009
  for (const n of node.bodyTemplate) {
1868
2010
  const v = emitNode(ctx, n, tracked, effectsVar, hyd ? parentVar : '__b', '__components', 0, budget || undefined);
1869
2011
  if (v) {
@@ -1875,6 +2017,7 @@ function emitForLoop(ctx, node, tracked, parentVar, skipK = 0, regionBudget) {
1875
2017
  }
1876
2018
  ctx.push(indent(`}`));
1877
2019
  }
2020
+ ctx.breakFlag = savedForBreakFlag;
1878
2021
  if (!hyd)
1879
2022
  ctx.push(indent(`__p.insertBefore(__b, ${endAnchor});`));
1880
2023
  if (hyd)
@@ -2060,6 +2203,47 @@ function emitSwitchBlock(ctx, node, tracked, parentVar, skipK = 0, regionBudget)
2060
2203
  }`);
2061
2204
  return null;
2062
2205
  }
2206
+ /**
2207
+ * True when a loop body contains a BreakSignal (or `continue`/`break`) that
2208
+ * targets the loop itself. Descent stops at nested loops, whose breaks bind to
2209
+ * themselves and are handled by their own emitter.
2210
+ */
2211
+ function bodyHasBreak(nodes) {
2212
+ for (const n of nodes) {
2213
+ if (n instanceof BreakSignal)
2214
+ return true;
2215
+ if (n instanceof OpaqueDynamicRegion) {
2216
+ if (bodyHasBreak([...n.consequentNodes, ...n.alternateNodes]))
2217
+ return true;
2218
+ }
2219
+ else if (n instanceof SwitchBlock) {
2220
+ for (const c of n.cases)
2221
+ if (bodyHasBreak(c.body))
2222
+ return true;
2223
+ }
2224
+ else if (n instanceof TryCatch) {
2225
+ if (bodyHasBreak([...n.bodyTemplate, ...n.catchBody]))
2226
+ return true;
2227
+ }
2228
+ else if (n instanceof StaticNode) {
2229
+ if (bodyHasBreak(n.children))
2230
+ return true;
2231
+ }
2232
+ else if (n instanceof ComponentCall) {
2233
+ if (bodyHasBreak(n.children))
2234
+ return true;
2235
+ }
2236
+ else if (n instanceof ServerBlock || n instanceof ClientBlock || n instanceof HeadBlock) {
2237
+ if (bodyHasBreak(n.children))
2238
+ return true;
2239
+ }
2240
+ else if (n instanceof PropSlot) {
2241
+ if (bodyHasBreak(n.body))
2242
+ return true;
2243
+ }
2244
+ }
2245
+ return false;
2246
+ }
2063
2247
  function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudget) {
2064
2248
  const arrExpr = transformTracked(node.expression, tracked);
2065
2249
  const itemVar = node.itemVariable;
@@ -2109,9 +2293,15 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2109
2293
  ctx.push(`let ${effectsVar} = [];`);
2110
2294
  ctx.push(`const ${endAnchor} = document.createComment('map-end');`);
2111
2295
  const renderItem = ctx.n();
2296
+ const hasBreak = bodyHasBreak(node.bodyTemplate);
2297
+ const breakVar = hasBreak ? ctx.n() : null;
2298
+ if (breakVar)
2299
+ ctx.push(`let ${breakVar} = false;`);
2112
2300
  const indexParam = node.indexVariable ? ', __i' : '';
2113
2301
  ctx.push(`const ${renderItem} = ${ctx.isAsyncScope ? 'async ' : ''}(${itemVar}${indexParam}, __e, __r${hyd ? ', __cl' : ''}${hydKeyed ? ', __root' : ''}) => {`);
2114
2302
  ctx.push(indent(`__r = __r || ${endAnchor};`));
2303
+ if (breakVar)
2304
+ ctx.push(indent(`if (${breakVar}) return;`));
2115
2305
  if (node.indexVariable)
2116
2306
  ctx.push(indent(`const ${node.indexVariable} = __i;`));
2117
2307
  if (hyd) {
@@ -2128,6 +2318,8 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2128
2318
  }
2129
2319
  if (hydKeyed)
2130
2320
  ctx.rootClaim = true;
2321
+ const savedMapBreak = ctx.breakFlag;
2322
+ ctx.breakFlag = breakVar;
2131
2323
  for (const n of node.bodyTemplate) {
2132
2324
  const v = emitNode(ctx, n, tracked, '__e', hyd ? undefined : '__it', '__components', 0, budget || undefined);
2133
2325
  if (v) {
@@ -2145,6 +2337,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2145
2337
  }
2146
2338
  }
2147
2339
  }
2340
+ ctx.breakFlag = savedMapBreak;
2148
2341
  if (hydKeyed)
2149
2342
  ctx.rootClaim = false;
2150
2343
  if (!hyd)
@@ -2199,16 +2392,17 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2199
2392
  const keyFnSrc = `(${itemVar}${node.indexVariable ? `, ${node.indexVariable}` : ''}) => ${keyExpr}`;
2200
2393
  const reconciler = ctx.n();
2201
2394
  const initList = ctx.n();
2395
+ const breakReset = breakVar ? `${breakVar} = false; ` : '';
2202
2396
  ctx.push(`let ${reconciler} = () => {};`);
2203
2397
  if (hydKeyed) {
2204
2398
  // claim-by-key hydration: `reconcileHydrated` attaches the region anchors
2205
2399
  // inside `parent` (pre-attach, `anchor.parentNode` is null) and peeks all
2206
2400
  // keys before claim-by-key consumes the walker, so server-rendered items
2207
2401
  // are adopted and wired in place instead of being rebuilt.
2208
- ctx.push(`const ${initList} = () => { ${reconciler} = reconcileHydrated(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e, __root) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'}, null, __root), __hydrate, ${parent}, ${skipK}); };`);
2402
+ ctx.push(`const ${initList} = () => { ${breakReset}${reconciler} = reconcileHydrated(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e, __root) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'}, null, __root), __hydrate, ${parent}, ${skipK}); };`);
2209
2403
  }
2210
2404
  else {
2211
- ctx.push(`const ${initList} = () => { ${reconciler} = reconcile(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'})); };`);
2405
+ ctx.push(`const ${initList} = () => { ${breakReset}${reconciler} = reconcile(${anchor}, ${endAnchor}, ${arrExpr}, ${keyFnSrc}, (${itemVar}, __i, __e) => ${renderItem}(${itemVar}${indexParam ? ', __i, __e' : ', __e'})); };`);
2212
2406
  }
2213
2407
  if (emptyRenderName) {
2214
2408
  const isEmptyVar = ctx.n();
@@ -2220,7 +2414,7 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2220
2414
  const __new = ${hasItems}();
2221
2415
  if (__first) { __first = false; __new; return; }
2222
2416
  if (__new !== ${isEmptyVar}) {
2223
- if (__new) ${reconciler}(${arrExpr});
2417
+ if (__new) {${breakVar ? ` ${breakVar} = false;` : ''} ${reconciler}(${arrExpr}); }
2224
2418
  return;
2225
2419
  }
2226
2420
  ${isEmptyVar} = !__new;
@@ -2236,24 +2430,30 @@ function emitMap(ctx, node, tracked, effTarget, parentVar, skipK = 0, regionBudg
2236
2430
  return null;
2237
2431
  pushEffect(`let __first = true;`, `effect(() => {
2238
2432
  const __nv = ${arrExpr};
2239
- if (__first) { __first = false; __nv; ${reconciler}(${arrExpr}); return; }
2240
- ${reconciler}(${arrExpr});
2433
+ if (__first) { __first = false; __nv; ${breakReset}${reconciler}(${arrExpr}); return; }
2434
+ ${breakVar ? `if (${breakVar}) ${breakVar} = false;` : ''}${reconciler}(${arrExpr});
2241
2435
  })`);
2242
2436
  }
2243
2437
  }
2244
2438
  else {
2245
2439
  const renderAllItems = (ind, collect) => {
2246
2440
  const lines = [];
2441
+ if (breakVar)
2442
+ lines.push(`${ind}${breakVar} = false;`);
2247
2443
  if (node.indexVariable) {
2248
2444
  lines.push(`${ind}let __i = 0;`);
2249
2445
  lines.push(`${ind}for (const ${itemVar} of ${arrExpr}) {`);
2250
2446
  lines.push(`${ind}\t${aw}${renderItem}(${itemVar}, __i, ${effectsVar}${collect ? `, null, ${collect}` : ''});`);
2251
2447
  lines.push(`${ind}\t__i++;`);
2448
+ if (breakVar)
2449
+ lines.push(`${ind}\tif (${breakVar}) break;`);
2252
2450
  lines.push(`${ind}}`);
2253
2451
  }
2254
2452
  else {
2255
2453
  lines.push(`${ind}for (const ${itemVar} of ${arrExpr}) {`);
2256
2454
  lines.push(`${ind}\t${aw}${renderItem}(${itemVar}, ${effectsVar}${collect ? `, null, ${collect}` : ''});`);
2455
+ if (breakVar)
2456
+ lines.push(`${ind}\tif (${breakVar}) break;`);
2257
2457
  lines.push(`${ind}}`);
2258
2458
  }
2259
2459
  return lines.join('\n');
@@ -2356,9 +2556,29 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
2356
2556
  const paramInit = buildParamInit(comp.paramNames, comp.propsAlias);
2357
2557
  if (paramInit)
2358
2558
  ctx.push(indent(paramInit));
2359
- // Hoist TrackDecls to top to avoid TDZ when Head/effects reference them before declaration
2559
+ // Hoist TrackDecls to top to avoid TDZ when Head/effects reference them before
2560
+ // declaration — BUT a TrackDecl whose initializer reads a binding declared by
2561
+ // an EARLIER body statement must stay in place. Hoisting it turned
2562
+ // `const list = …; const &[sel] = track(list[0])` into
2563
+ // `const sel = track(list[0]); const list = …`, so the client threw
2564
+ // `ReferenceError: Cannot access 'list' before initialization` and the route
2565
+ // rendered the error page after hydration. Source order already satisfies the
2566
+ // Head/effect case, because both are emitted after the statements they read.
2567
+ const deferredTrackDecls = new Set();
2568
+ {
2569
+ const declaredSoFar = new Set();
2570
+ for (const node of comp.body) {
2571
+ if (node instanceof TrackDecl) {
2572
+ if (initReadsAnyOf(node.init, declaredSoFar))
2573
+ deferredTrackDecls.add(node);
2574
+ continue;
2575
+ }
2576
+ for (const name of runtimeDeclaredNames(node))
2577
+ declaredSoFar.add(name);
2578
+ }
2579
+ }
2360
2580
  for (const node of comp.body) {
2361
- if (node instanceof TrackDecl) {
2581
+ if (node instanceof TrackDecl && !deferredTrackDecls.has(node)) {
2362
2582
  emitNode(ctx, node, tracked, null);
2363
2583
  }
2364
2584
  }
@@ -2411,8 +2631,14 @@ function generateComponent(comp, importedNames = new Set(), hydrate = false, asy
2411
2631
  }
2412
2632
  }
2413
2633
  for (const node of comp.body) {
2414
- if (node instanceof TrackDecl)
2634
+ // A TrackDecl that could not be hoisted (its init reads an earlier body
2635
+ // binding) is emitted here, in source order, so the binding it reads is
2636
+ // already initialised.
2637
+ if (node instanceof TrackDecl) {
2638
+ if (deferredTrackDecls.has(node))
2639
+ emitNode(ctx, node, tracked, null);
2415
2640
  continue;
2641
+ }
2416
2642
  const v = emitNode(ctx, node, tracked, null, undefined, '__components', topPendingSkip, topBudget || undefined);
2417
2643
  if (v) {
2418
2644
  if (ctx.hydrate) {
@@ -1 +1 @@
1
- {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAsBP,MAAM,uBAAuB,CAAC;AA2T/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAgmDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAqV9E"}
1
+ {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAuBP,MAAM,uBAAuB,CAAC;AA2T/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAytDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAqV9E"}
@@ -1,4 +1,4 @@
1
- import { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, RuntimeStatement, ForLoop, TrackDecl, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, Expression, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
1
+ import { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, RuntimeStatement, ForLoop, TrackDecl, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, BreakSignal, Expression, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
2
2
  import { VeskError, codeFrame } from '@vesk/compiler/src/errors';
3
3
  import { createBaseParser } from '@vesk/compiler/src/parser';
4
4
  import { skipWhitespace, findBalancedEnd, splitTopLevel, startsWithIdentifier, stripDeclKeyword, isWhitespaceChar, collapseNewlineWhitespace } from '@vesk/compiler/src/scan';
@@ -1410,6 +1410,13 @@ function processJSXCallbackBody(source, body) {
1410
1410
  nodes.push(...processJSXChildren(source, [c]));
1411
1411
  return nodes;
1412
1412
  }
1413
+ // A block-bodied callback — `.map((x) => { const y = …; return <jsx/>; })` —
1414
+ // is a statement-mode body: local declarations become runtime statements and
1415
+ // the terminal `return <jsx>` renders the item. Previously the block was
1416
+ // printed verbatim as an expression (`const y = …; return <li>`) and esbuild
1417
+ // rejected it with `Unexpected keyword 'const'`.
1418
+ if (body.type === 'BlockStatement')
1419
+ return processStatementModeBody(source, body.body);
1413
1420
  if (body.type === 'ParenthesizedExpression')
1414
1421
  return exprToIR(source, body.expression);
1415
1422
  return exprToIR(source, body);
@@ -1627,6 +1634,54 @@ function getReturnStatement(node) {
1627
1634
  }
1628
1635
  return null;
1629
1636
  }
1637
+ /**
1638
+ * True when `node`'s subtree contains a `continue`/`break` that targets the
1639
+ * loop `node` belongs to. Descent stops at nested loops (their control
1640
+ * statements bind to them, not to the enclosing loop) and at function
1641
+ * boundaries.
1642
+ */
1643
+ function containsLoopControl(node) {
1644
+ if (!node)
1645
+ return false;
1646
+ if (node.type === 'ContinueStatement' || node.type === 'BreakStatement')
1647
+ return true;
1648
+ if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' ||
1649
+ node.type === 'ArrowFunctionExpression')
1650
+ return false;
1651
+ if (node.type === 'ForStatement' || node.type === 'ForInStatement' ||
1652
+ node.type === 'ForOfStatement' || node.type === 'WhileStatement' ||
1653
+ node.type === 'DoWhileStatement')
1654
+ return false;
1655
+ if (node.type === 'BlockStatement')
1656
+ return node.body.some(containsLoopControl);
1657
+ if (node.type === 'IfStatement')
1658
+ return containsLoopControl(node.consequent) || containsLoopControl(node.alternate);
1659
+ if (node.type === 'SwitchStatement')
1660
+ return node.cases.some((c) => (c.consequent || []).some(containsLoopControl));
1661
+ if (node.type === 'TryStatement') {
1662
+ return containsLoopControl(node.block) || containsLoopControl(node.handler) || containsLoopControl(node.finalizer);
1663
+ }
1664
+ if (node.type === 'CatchClause')
1665
+ return containsLoopControl(node.body);
1666
+ if (node.type === 'LabeledStatement')
1667
+ return containsLoopControl(node.body);
1668
+ return false;
1669
+ }
1670
+ /**
1671
+ * Recognizes an `if (c) continue;` / `if (c) break;` guard (no `else`) and
1672
+ * returns the control node plus any statements that precede it inside the
1673
+ * branch block (`if (c) { foo(); continue; }`).
1674
+ */
1675
+ function getLoopControlGuard(consequent, kind) {
1676
+ const stmts = consequent.type === 'BlockStatement' ? consequent.body : [consequent];
1677
+ if (stmts.length === 0)
1678
+ return null;
1679
+ const want = kind === 'continue' ? 'ContinueStatement' : 'BreakStatement';
1680
+ const last = stmts[stmts.length - 1];
1681
+ if (!last || last.type !== want)
1682
+ return null;
1683
+ return { pre: stmts.slice(0, -1), node: last };
1684
+ }
1630
1685
  function isStatementMode(bodyStmts) {
1631
1686
  if (bodyStmts.some((s) => s.type === 'JSXElement' || s.type === 'JSXExpressionContainer' || s.type === 'JSXFragment'))
1632
1687
  return true;
@@ -1680,6 +1735,15 @@ function processIfStatement(source, stmt) {
1680
1735
  return [new OpaqueDynamicRegion(condExpr, consequent, alternate)];
1681
1736
  }
1682
1737
  function processForStatement(source, stmt, alternate = []) {
1738
+ // A loop with no JSX is pure computation: its value is the side effects it
1739
+ // performs on surrounding bindings. Leaving it as a reactive DOM region would
1740
+ // emit anchors and per-item render closures for no output — and a
1741
+ // `continue`/`break` inside it lands inside those closures as invalid JS
1742
+ // (`Unsyntactic continue`). Emit the loop verbatim instead so its control
1743
+ // flow runs in its original scope.
1744
+ if (stmt.body && !hasJSXInSubtree(stmt.body) && containsLoopControl(stmt.body)) {
1745
+ return [new RuntimeStatement(getSource(source, stmt), stmt, source)];
1746
+ }
1683
1747
  if (stmt.type === 'ForOfStatement') {
1684
1748
  const left = stmt.left;
1685
1749
  let itemVar = 'item';
@@ -1715,6 +1779,9 @@ function processForStatement(source, stmt, alternate = []) {
1715
1779
  return [];
1716
1780
  }
1717
1781
  function processWhileStatement(source, stmt) {
1782
+ if (stmt.body && !hasJSXInSubtree(stmt.body) && containsLoopControl(stmt.body)) {
1783
+ return [new RuntimeStatement(getSource(source, stmt), stmt, source)];
1784
+ }
1718
1785
  const condition = toExpression(source, stmt.test);
1719
1786
  const bodyTemplate = processBlockBody(source, stmt.body);
1720
1787
  const isDoWhile = stmt.type === 'DoWhileStatement';
@@ -1797,6 +1864,25 @@ function processStatementModeBody(source, bodyStmts, filename) {
1797
1864
  nodes.push(new OpaqueDynamicRegion(toExpression(source, stmt.test), consequent, alternate));
1798
1865
  break;
1799
1866
  }
1867
+ // `if (c) continue;` / `if (c) break;` — a loop-body guard. The loop body
1868
+ // is re-emitted as a per-item closure (map) or inline loop, so the control
1869
+ // statement cannot cross the branch closure. Rewrite the remainder of the
1870
+ // iteration into the guard's ELSE branch: `if (c) { pre } else { rest }`.
1871
+ // `continue` renders nothing in the consequent; `break` also sets the
1872
+ // region's break flag (a BreakSignal) so later iterations stop.
1873
+ if (!stmt.alternate) {
1874
+ const guardContinue = getLoopControlGuard(stmt.consequent, 'continue');
1875
+ const guardBreak = guardContinue ? null : getLoopControlGuard(stmt.consequent, 'break');
1876
+ const guard = guardContinue || guardBreak;
1877
+ if (guard) {
1878
+ const consequent = guard.pre.length ? processStatementModeBody(source, guard.pre, filename) : [];
1879
+ if (guardBreak)
1880
+ consequent.push(new BreakSignal());
1881
+ const alternate = processStatementModeBody(source, bodyStmts.slice(i + 1), filename);
1882
+ nodes.push(new OpaqueDynamicRegion(toExpression(source, stmt.test), consequent, alternate));
1883
+ break;
1884
+ }
1885
+ }
1800
1886
  nodes.push(...processIfStatement(source, stmt));
1801
1887
  }
1802
1888
  else if (stmt.type === 'ForOfStatement') {
@@ -1847,6 +1933,17 @@ function processStatementModeBody(source, bodyStmts, filename) {
1847
1933
  const { line, column } = offsetToLineCol(source, stmt.start ?? 0);
1848
1934
  throw VeskError.classDecl({ file: filename || '', line, column, code: codeFrame(source, line, column) });
1849
1935
  }
1936
+ else if (stmt.type === 'ContinueStatement') {
1937
+ // An unconditional `continue` at the top level of a loop body: everything
1938
+ // after it is dead code. Drop the remainder of the body.
1939
+ break;
1940
+ }
1941
+ else if (stmt.type === 'BreakStatement') {
1942
+ // An unconditional `break` at the top level of a loop body: stop the loop
1943
+ // and drop the remainder.
1944
+ nodes.push(new BreakSignal());
1945
+ break;
1946
+ }
1850
1947
  else {
1851
1948
  const raw = getSource(source, stmt);
1852
1949
  if (raw)
@@ -2038,6 +2135,13 @@ function warnStaleConstBindings(comp, file) {
2038
2135
  // pushed authors to inline helpers that were already correct.
2039
2136
  if (d.init.type === 'ArrowFunctionExpression' || d.init.type === 'FunctionExpression')
2040
2137
  continue;
2138
+ // `const x = derived(() => trackedValue)` is the DOCUMENTED fix this very
2139
+ // warning recommends, and it is live by construction. The recursion below
2140
+ // would still reach the tracked read inside the closure and flag it, so
2141
+ // a reactive factory call is excluded for the same reason a bare function
2142
+ // initializer is.
2143
+ if (isReactiveFactoryCall(d.init))
2144
+ continue;
2041
2145
  // A bare identifier initializer is an ALIAS, not a read: `const copy =
2042
2146
  // selCell` re-binds the cell and stays live. Only a read snapshots.
2043
2147
  if (d.init.type === 'Identifier' && reactive.has(d.init.name))
@@ -2094,6 +2198,21 @@ function warnStaleConstBindings(comp, file) {
2094
2198
  });
2095
2199
  }
2096
2200
  /** Every identifier name appearing anywhere in an expression (AST walk). */
2201
+ function isReactiveFactoryCall(node) {
2202
+ if (!node || node.type !== 'CallExpression')
2203
+ return false;
2204
+ const callee = node.callee;
2205
+ if (!callee)
2206
+ return false;
2207
+ // `derived(...)` is auto-imported, so the callee is a bare identifier; also
2208
+ // accept a namespace/dotted call (`$state.derived(...)`) defensively.
2209
+ if (callee.type === 'Identifier')
2210
+ return callee.name === 'derived';
2211
+ if (callee.type === 'MemberExpression' && !callee.computed) {
2212
+ return callee.property?.type === 'Identifier' && callee.property.name === 'derived';
2213
+ }
2214
+ return false;
2215
+ }
2097
2216
  function collectIdentifiers(node, into) {
2098
2217
  if (!node || typeof node !== 'object')
2099
2218
  return;
@@ -2104,6 +2223,21 @@ function collectIdentifiers(node, into) {
2104
2223
  }
2105
2224
  if (typeof node.type !== 'string')
2106
2225
  return;
2226
+ // A NON-computed member property (`learner.classId`) and a non-computed
2227
+ // object/class key (`{ classId: 1 }`) are names, not references. Collecting
2228
+ // them made `const klass = base.find(c => c.id === learner.classId)` look
2229
+ // like it read the tracked `classId` binding, when it only reads the loop
2230
+ // item's field — a false positive on correct per-iteration code.
2231
+ if (node.type === 'MemberExpression' && !node.computed) {
2232
+ collectIdentifiers(node.object, into);
2233
+ if (node.property?.type === 'MemberExpression')
2234
+ collectIdentifiers(node.property, into);
2235
+ return;
2236
+ }
2237
+ if ((node.type === 'Property' || node.type === 'PropertyDefinition' || node.type === 'MethodDefinition') && !node.computed) {
2238
+ collectIdentifiers(node.value, into);
2239
+ return;
2240
+ }
2107
2241
  if (node.type === 'Identifier' && typeof node.name === 'string')
2108
2242
  into.add(node.name);
2109
2243
  for (const key of Object.keys(node)) {
package/dist/ir.d.ts CHANGED
@@ -203,6 +203,21 @@ export declare class RuntimeStatement {
203
203
  source: string | null;
204
204
  constructor(raw: string, ast?: ESTreeNode | null, source?: string | null);
205
205
  }
206
+ /**
207
+ * A `break` targeting the enclosing reactive loop region.
208
+ *
209
+ * A statement-mode loop body is emitted as a per-item render closure (map) or
210
+ * an inline real loop (for/while). A `break` inside a nested `if` region is
211
+ * emitted inside a branch closure and cannot `break` the outer loop. The IR
212
+ * generator rewrites `if (c) break; rest` into `if (c) { <BreakSignal> } else
213
+ * { rest }`; codegen lowers this node to "set the region's break flag and stop
214
+ * emitting the current item" (map) or "set the flag; the loop checks it at the
215
+ * top of each pass" (inline loops). The server emits a native `break;` since
216
+ * its regions are inline.
217
+ */
218
+ export declare class BreakSignal {
219
+ constructor();
220
+ }
206
221
  export declare class ForLoop {
207
222
  init: string;
208
223
  condition: Expression;
@@ -223,5 +238,5 @@ export declare class HeadBlock {
223
238
  children: IRNode[];
224
239
  constructor(children: IRNode[]);
225
240
  }
226
- export type IRNode = StaticNode | TextNode | DynamicBinding | OpaqueDynamicRegion | MapRegion | WhileLoop | SwitchBlock | TryCatch | ForLoop | TrackDecl | RuntimeStatement | ComponentRef | ComponentCall | ServerBlock | ClientBlock | HeadBlock | SlotNode | PropSlot | PropSlotRender;
241
+ export type IRNode = StaticNode | TextNode | DynamicBinding | OpaqueDynamicRegion | MapRegion | WhileLoop | SwitchBlock | TryCatch | ForLoop | TrackDecl | RuntimeStatement | BreakSignal | ComponentRef | ComponentCall | ServerBlock | ClientBlock | HeadBlock | SlotNode | PropSlot | PropSlotRender;
227
242
  //# sourceMappingURL=ir.d.ts.map
package/dist/ir.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ir.d.ts","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEjD,qBAAa,UAAU;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;gBAEH,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,EAAE,GAAG,GAAE,UAAU,GAAG,IAAW,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW;CAM1G;AAED,qBAAa,MAAM;IACjB,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;;OAMG;IACH,aAAa,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;gBAGxB,UAAU,EAAE,WAAW,EAAE,EACzB,OAAO,GAAE,MAAM,EAAO,EACtB,aAAa,GAAE,GAAG,CAAC,MAAM,CAAa,EACtC,WAAW,GAAE,MAAM,GAAG,IAAW,EACjC,MAAM,GAAE,MAAM,GAAG,IAAW,EAC5B,YAAY,GAAE,MAAM,EAAO,EAC3B,aAAa,GAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAM,EAC9D,eAAe,GAAE,MAAM,EAAO;CAWjC;AAED,qBAAa,WAAW;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,YAAY,GAAG,WAAW,CAAC;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;gBAGrB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE;QACJ,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,IAAI,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;QAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB;CAeT;AAED,qBAAa,UAAU;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;gBAGzB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,EAC7C,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,GAAE,UAAU,GAAG,IAAW;CAQpC;AAED,qBAAa,QAAQ;IACnB,KAAK,EAAE,MAAM,CAAC;gBAEF,KAAK,EAAE,MAAM;CAG1B;AAED,qBAAa,cAAc;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,UAAU,CAAC;gBAEX,UAAU,EAAE,UAAU,EAAE,IAAI,GAAE,MAAM,GAAG,WAAoB,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW;CAKtG;AAED,qBAAa,mBAAmB;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,EAAE,CAAC;gBAEb,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,cAAc,GAAE,MAAM,EAAO;CAK5F;AAED,qBAAa,SAAS;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;gBAEb,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,UAAU,GAAG,IAAW,EAAE,aAAa,GAAE,MAAM,GAAG,IAAW,EAAE,cAAc,GAAE,MAAM,EAAO;CAQxL;AAED,qBAAa,YAAY;IACvB,aAAa,EAAE,MAAM,CAAC;gBAEV,aAAa,EAAE,MAAM;CAGlC;AAED,qBAAa,aAAa;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC;IAC7C,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;gBAGxB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,UAAU,CAAA;KAAE,EAAE,EAC5C,QAAQ,GAAE,MAAM,EAAO,EACvB,WAAW,GAAE,UAAU,EAAO,EAC9B,KAAK,SAAK,EACV,UAAU,GAAE,MAAM,GAAG,IAAW;CASnC;AAED,qBAAa,QAAQ;;CAEpB;AAED;;;;;;;;GAQG;AACH,qBAAa,QAAQ;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;gBAEH,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;CAI7C;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,QAAQ,EAAE,MAAM,CAAC;gBAEL,QAAQ,EAAE,MAAM;CAG7B;AAED,qBAAa,SAAS;IACpB,SAAS,EAAE,UAAU,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;gBAEP,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,SAAS,GAAE,OAAe;CAKtF;AAED,qBAAa,UAAU;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;gBAEH,IAAI,EAAE,MAAM,EAAE;CAG3B;AAED,qBAAa,WAAW;IACtB,YAAY,EAAE,UAAU,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;gBAE9C,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAIhG;AAED,qBAAa,QAAQ;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;gBAElB,YAAY,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,cAAc,GAAE,MAAM,GAAG,IAAW;CAK9F;AAED,qBAAa,SAAS;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;gBAED,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAK/D;AAED,qBAAa,gBAAgB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;gBAEV,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,UAAU,GAAG,IAAW,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW;CAKrF;AAED,qBAAa,OAAO;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;gBAEX,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,KAAK,GAAG,QAAgB;CAOxH;AAED,qBAAa,WAAW;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACP,QAAQ,EAAE,MAAM,EAAE;CAG/B;AAED,qBAAa,WAAW;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACP,QAAQ,EAAE,MAAM,EAAE;CAG/B;AAED,qBAAa,SAAS;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACP,QAAQ,EAAE,MAAM,EAAE;CAG/B;AAED,MAAM,MAAM,MAAM,GACd,UAAU,GACV,QAAQ,GACR,cAAc,GACd,mBAAmB,GACnB,SAAS,GACT,SAAS,GACT,WAAW,GACX,QAAQ,GACR,OAAO,GACP,SAAS,GACT,gBAAgB,GAChB,YAAY,GACZ,aAAa,GACb,WAAW,GACX,WAAW,GACX,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,cAAc,CAAC"}
1
+ {"version":3,"file":"ir.d.ts","sourceRoot":"","sources":["../src/ir.ts"],"names":[],"mappings":"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"}
package/dist/ir.js CHANGED
@@ -254,6 +254,21 @@ export class RuntimeStatement {
254
254
  this.source = source;
255
255
  }
256
256
  }
257
+ /**
258
+ * A `break` targeting the enclosing reactive loop region.
259
+ *
260
+ * A statement-mode loop body is emitted as a per-item render closure (map) or
261
+ * an inline real loop (for/while). A `break` inside a nested `if` region is
262
+ * emitted inside a branch closure and cannot `break` the outer loop. The IR
263
+ * generator rewrites `if (c) break; rest` into `if (c) { <BreakSignal> } else
264
+ * { rest }`; codegen lowers this node to "set the region's break flag and stop
265
+ * emitting the current item" (map) or "set the flag; the loop checks it at the
266
+ * top of each pass" (inline loops). The server emits a native `break;` since
267
+ * its regions are inline.
268
+ */
269
+ export class BreakSignal {
270
+ constructor() { }
271
+ }
257
272
  export class ForLoop {
258
273
  init;
259
274
  condition;
@@ -1 +1 @@
1
- {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAOzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AA6DrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CA4EjJ;AAobD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAwD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAuFD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkCzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
1
+ {"version":3,"file":"server-jsgen.d.ts","sourceRoot":"","sources":["../src/server-jsgen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAQzE,OAAO,EAAkG,KAAK,WAAW,EAAE,MAAM,mCAAmC,CAAC;AA6DrK,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,GAAE,OAAe,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CA+EjJ;AAqbD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAwD1F;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAuFD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkCzE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYhG"}
@@ -1,4 +1,4 @@
1
- import { Expression, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
1
+ import { Expression, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, BreakSignal, ServerBlock, ClientBlock, HeadBlock, SlotNode, PropSlot, PropSlotRender, } from '@vesk/compiler/src/ir';
2
2
  import { isStaticIR, collectTrackedNames, transformTracked, transformTrackedInit, semicolonizeStatement } from '@vesk/compiler/src/client-codegen';
3
3
  import { unwrapTrackCall, stripTrackGeneric, hasTopLevelComma, skipWhitespace, findBalancedEnd, startsWithIdentifier, callsIdentifier } from '@vesk/compiler/src/scan';
4
4
  import { escapeHtml, indent, exprJS, extractTopLevelNames, extractRuntimeNames, buildParamInit, VOID_ELEMENTS, __vskHydrate, __vskMarkerless, __vskImportedNames, setVskImportedNames, setVskForceClaim, takeVskForceClaim, nextVskId, } from '@vesk/compiler/src/server-utils';
@@ -125,6 +125,10 @@ export function irNodeToJS(node, importedNames, isAsync = false, tracked) {
125
125
  }
126
126
  return semicolonizeStatement(transformTracked(node, tracked || new Map()));
127
127
  }
128
+ // SSR regions are emitted inline inside a real loop, so a break signal is a
129
+ // native `break;` — it exits the loop the item body was emitted into.
130
+ if (node instanceof BreakSignal)
131
+ return `break;`;
128
132
  if (node instanceof SlotNode) {
129
133
  // Markerless mode drops the slot boundary comments: SSR output is plain
130
134
  // HTML and slot claims scope structurally (createLayoutSlot subject to the
@@ -343,15 +347,16 @@ function mapRegionToJS(node, isAsync = false, tracked) {
343
347
  }
344
348
  const loopArr = arrVar || arr;
345
349
  if (node.indexVariable) {
346
- lines.push('let __i = 0;');
350
+ const counter = `__i${nextVskId()}`;
351
+ lines.push(`let ${counter} = 0;`);
347
352
  lines.push(`for (const ${item} of ${loopArr}) {`);
348
- lines.push(indent(`const ${node.indexVariable} = __i;`));
353
+ lines.push(indent(`const ${node.indexVariable} = ${counter};`));
349
354
  for (const n of bodyForItem) {
350
355
  const code = irNodeToJS(n, null, isAsync, tracked);
351
356
  if (code)
352
357
  lines.push(indent(code));
353
358
  }
354
- lines.push(indent('__i++;'));
359
+ lines.push(indent(`${counter}++;`));
355
360
  lines.push(`}`);
356
361
  }
357
362
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/compiler",
3
- "version": "0.2.60",
3
+ "version": "0.2.62",
4
4
  "type": "module",
5
5
  "description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@sveltejs/acorn-typescript": "^1.0.0",
23
- "@vesk/types": "^0.2.60",
23
+ "@vesk/types": "^0.2.62",
24
24
  "acorn": "^8.15.0",
25
25
  "esrap": "^2.3.0",
26
26
  "is-reference": "^3.0.0",