@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.
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +236 -10
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +135 -1
- package/dist/ir.d.ts +16 -1
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +15 -0
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +9 -4
- package/package.json +2 -2
|
@@ -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,
|
|
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"}
|
package/dist/client-codegen.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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"}
|
package/dist/ir-generator.js
CHANGED
|
@@ -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":"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"}
|
|
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;
|
|
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"}
|
package/dist/server-jsgen.js
CHANGED
|
@@ -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
|
-
|
|
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} =
|
|
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(
|
|
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.
|
|
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.
|
|
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",
|